ruby - DRY busca cada página de un sitio con nokogiri
web-scraping web-crawler (3)
Es un problema más complicado de lo que parece darse cuenta. Usar una biblioteca junto con Nokogiri
es probablemente el camino a seguir. A menos que esté usando Windows (como yo) es posible que desee buscar en Anemone
.
Quiero buscar todas las páginas de un sitio. Mi idea es encontrar todos los enlaces en una página que permanezca dentro del dominio, visitarlos y repetir. Tendré que implementar medidas para no repetir los esfuerzos también.
Entonces comienza muy fácilmente:
page = ''http://example.com''
nf = Nokogiri::HTML(open(page))
links = nf.xpath ''//a'' #find all links on current page
main_links = links.map{|l| l[''href''] if l[''href''] =~ /^///}.compact.uniq
"main_links" es ahora una matriz de enlaces desde la página activa que comienza con "/" (que deberían ser enlaces solo en el dominio actual).
Desde aquí puedo alimentar y leer esos enlaces en un código similar anterior, pero no sé cuál es la mejor manera de garantizar que no me repito. Estoy pensando en comenzar a recopilar todos los enlaces visitados mientras los visito:
main_links.each do |ml|
visited_links = [] #new array of what is visted
np = Nokogiri::HTML(open(page + ml)) #load the first main_link
visted_links.push(ml) #push the page we''re on
np_links = np.xpath(''//a'').map{|l| l[''href''] if l[''href''] =~ /^///}.compact.uniq #grab all links on this page pointing to the current domain
main_links.push(np_links).compact.uniq #remove duplicates after pushing?
end
Todavía estoy trabajando en esto último ... ¿pero parece este el enfoque adecuado?
Gracias.
Te faltan algunas cosas.
Una referencia local puede comenzar con /
, pero también puede comenzar con .
, ..
o incluso sin carácter especial, lo que significa que el enlace está dentro del directorio actual.
JavaScript también se puede usar como un enlace, por lo que deberá buscar en todo su documento y buscar etiquetas que se usen como botones, luego analizar la URL.
Esta:
links = nf.xpath ''//a'' #find all links on current page
main_links = links.map{|l| l[''href''] if l[''href''] =~ /^///}.compact.uniq
puede ser mejor escrito:
links.search(''a[href^="/"]'').map{ |a| a[''href''] }.uniq
En general, no hagas esto:
....map{|l| l[''href''] if l[''href''] =~ /^///}.compact.uniq
porque es muy incomodo El condicional en el map
da como resultado entradas nil
en la matriz resultante, así que no hagas eso. Use select
o reject
para reducir el conjunto de enlaces que cumplen sus criterios y luego use el map
para transformarlos. En su uso aquí, el prefiltrado con ^=
en CSS lo hace aún más fácil.
No almacene los enlaces en la memoria. Perderá todo el progreso si bloquea o detiene su código. En su lugar, como mínimo, use algo como una base de datos SQLite en el disco como un almacén de datos. Cree un campo "href" que sea exclusivo para evitar golpear repetidamente la misma página.
Use la clase URI incorporada de Ruby, o la gema direccionable, para analizar y manipular las URL. Le ahorran trabajo y harán las cosas bien cuando comience a codificar / descodificar consultas y trate de normalizar los parámetros para verificar la exclusividad, extraer y manipular rutas, etc.
Muchos sitios usan ID de sesión en la consulta URL para identificar al visitante. Esa ID puede hacer que cada enlace sea diferente si comienza, luego se detiene, luego comienza de nuevo, o si no está devolviendo las cookies recibidas del sitio, entonces tiene que devolver las cookies y descubrir qué parámetros de consulta son significativos y cuáles van a arrojar tu código. Guarde el primero y deseche el segundo cuando almacene los enlaces para un análisis posterior.
Use un cliente HTTP como Typhoeus con Hydra para recuperar varias páginas en paralelo y guárdelas en su base de datos, con un proceso separado que las analiza y alimenta las URL para analizarlas en la base de datos. Esto puede marcar una gran diferencia en el tiempo total de procesamiento.
Respete el archivo robots.txt del sitio y acelere sus solicitudes para evitar golpear su servidor. A nadie le gusta el ancho de banda y consumir una gran cantidad del ancho de banda de un sitio o el tiempo de CPU sin permiso es una buena manera de llamar la atención y luego prohibirlo. Su sitio tendrá un rendimiento cero en ese punto.
Otros le aconsejaron que no escriba su propio rastreador web. Estoy de acuerdo con esto si el rendimiento y la solidez son sus objetivos. Sin embargo, puede ser un gran ejercicio de aprendizaje. Usted escribió esto:
"[...] pero no sé cuál es la mejor manera de asegurarme de no repetirme"
La recursividad es la clave aquí. Algo como el siguiente código:
require ''set''
require ''uri''
require ''nokogiri''
require ''open-uri''
def crawl_site( starting_at, &each_page )
files = %w[png jpeg jpg gif svg txt js css zip gz]
starting_uri = URI.parse(starting_at)
seen_pages = Set.new # Keep track of what we''ve seen
crawl_page = ->(page_uri) do # A re-usable mini-function
unless seen_pages.include?(page_uri)
seen_pages << page_uri # Record that we''ve seen this
begin
doc = Nokogiri.HTML(open(page_uri)) # Get the page
each_page.call(doc,page_uri) # Yield page and URI to the block
# Find all the links on the page
hrefs = doc.css(''a[href]'').map{ |a| a[''href''] }
# Make these URIs, throwing out problem ones like mailto:
uris = hrefs.map{ |href| URI.join( page_uri, href ) rescue nil }.compact
# Pare it down to only those pages that are on the same site
uris.select!{ |uri| uri.host == starting_uri.host }
# Throw out links to files (this could be more efficient with regex)
uris.reject!{ |uri| files.any?{ |ext| uri.path.end_with?(".#{ext}") } }
# Remove #foo fragments so that sub-page links aren''t differentiated
uris.each{ |uri| uri.fragment = nil }
# Recursively crawl the child URIs
uris.each{ |uri| crawl_page.call(uri) }
rescue OpenURI::HTTPError # Guard against 404s
warn "Skipping invalid link #{page_uri}"
end
end
end
crawl_page.call( starting_uri ) # Kick it all off!
end
crawl_site(''http://phrogz.net/'') do |page,uri|
# page here is a Nokogiri HTML document
# uri is a URI instance with the address of the page
puts uri
end
En breve:
- Mantenga un registro de las páginas que ha visto usando un
Set
. Haga esto no por valorhref
, sino por el URI canónico completo. - Use
URI.join
para convertir rutas posiblemente relativas en el URI correcto con respecto a la página actual. - Use recursividad para seguir rastreando cada enlace en cada página, pero rescatando si ya ha visto la página.