J’ai besoin une version de quelques sites réalisés avec Rails en version statique pour une présentation offline.
J’aurai pu utiliser wget, mais franchement j’avais envie et besoin d’un outil plus personnalisable. Ou bien étendre les controller pour récupérer la sortie mais trop de problème et pas assez de souplesse. C’est là qu’intervient Anemone.
La mise en place est simple (via gem bien sûr), et la prise en main l’est tout autant, comme le montre l’exmple de leur site :
require 'anemone'
Anemone.crawl("http://www.example.com/") do |anemone|
anemone.on_every_page do |page|
puts page.url
end
end
C’est assez rudimentaire, mais en couplant avec des outils puissant comme Hpricot, et une surcharge de la classe URI, on en sort un outil vraiment personnalisé et puissant.
J’ai pu réécrire les chemins statiques de mon code en chemin relatif pour une visualisation offline en surchargeant la classe URI
require 'uri'
require 'rubygems'
require 'anemone'
require 'hpricot'
OUTPUT_DIR = File.join(File.dirname(__FILE__), 'export', 'sites')
module UriExt
def last_item_of_path_is_used_for_filename
self.query || (File.basename(self.path) =~ /^(.*)\..*$/)
end
def to_static_filename
filename = ''
if self.query
if (File.basename(self.path) =~ /^(.*)\..*$/)
filename = $1
else
filename = File.basename(self.path)
end
filename += '-' + self.query_to_filename_ext + '.html'
else
if (File.basename(self.path) =~ /^(.*)\..*$/)
filename = File.basename(self.path)
else
filename = 'index.html'
end
end
filename
end
def query_to_filename_ext
self.query ? self.query.gsub(/[=;&]/, '-') : nil
end
def build_relative_path(with_host=true)
if self.path !~ /^\/$/
items = with_host ? [self.host] : []
original_path = (self.path =~ /^\/(.*)$/ ? $1 : self.path)
items_original_path = original_path.split('/')
items_original_path.shift if items_original_path.first == '/'
items_original_path.pop if self.last_item_of_path_is_used_for_filename
items += items_original_path
File.join(items)
end
end
def complete_path_filename(with_host=true)
items = []
relative_path = self.build_relative_path(with_host)
items << relative_path if relative_path
items << self.to_static_filename
File.join(items)
end
def create_dir
relative_path = self.build_relative_path
if relative_path
FileUtils.mkdir_p(File.join(OUTPUT_DIR, relative_path))
end
end
def create_file(content)
self.create_dir
File.open(File.join(OUTPUT_DIR, self.complete_path_filename), 'w') do |file|
file.write(content)
end
end
def build_relative_path_for_html
if self.path !~ /^\/$/
(self.path =~ /^\/(.*)$/ ? $1 : self.path)
else
'index.html'
end
end
def root_back_relative_path(an_absloute_path)
if self.path !~ /^\/$/
return File.join((['..'] * ((self.path.split('/')).length - 1)).join('/'), (an_absloute_path =~ /^\/(.*)$/ ? $1 : an_absloute_path))
else
(an_absloute_path =~ /^\/(.*)$/ ? $1 : an_absloute_path)
end
end
end
URI::HTTP.send(:include, UriExt)
URI::Generic.send(:include, UriExt)
Anemone.crawl("http://www.example.com") do |anemone|
# cache pages
anemone.storage = Anemone::Storage.PStore('arnaudkozlinski.pstore')
# avoid JPG crawl (just a particular case for my website)
# it's here only for th example
anemone.focus_crawl { |page| page.links.select{ |uri| uri.path !~ /\.(jpg|JPG)$/ } }
anemone.on_every_page do |page|
puts page.url
# if craw a html page parse and modify it with Hpricot
if page.headers['content-type'].join('') =~ /^text\/html/
doc = Hpricot(page.body)
# image source rewriting
doc.search('img').each do |img|
if img.attributes['src']
uri_img = URI.parse(img.attributes['src'])
img['src'] = page.url.root_back_relative_path(uri_img.path)
end
end
....
# stylesheet source rewriting
doc.search('link').each do |script|
if script.attributes['href']
uri_script = URI.parse(script.attributes['href'])
script['href'] = page.url.root_back_relative_path(uri_script.path)
end
end
....
end
end
Petit bémol, reste le parsing des feuilles de style CSS pour la réécriture des url() vers les images de style. Mais comme les url des feuilles de style sont déjà en relatif (c’est ma règle en tout cas), je n’ai pas eu besoin de parser, juste copier les images au bon endroit. D’autant que je n’ai pas trouvé le Hpricot du CSS.
Ça peut paraître long à première vue, mais quand on maîtrise Ruby, c’est largement moins prise de tête que d’utiliser un web spider automatique, car il y a toujours des cas particuliers et par expérience aucun ne permet d’obtenir un résultat parfait.