ruby web-scraping web-crawler nokogiri dry

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 valor href , 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.