DRY使用nokogiri搜索网站的每个页面

我想搜索网站的每个页面。 我的想法是找到保留在域内的页面上的所有链接,访问它们并重复。 我必须采取措施,不再重复努力。

所以它很容易开始:

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”现在是活动页面中以“/”开头的链接数组(仅应为当前域上的链接)。

从这里我可以将这些链接提供给上面类似的代码,但我不知道确保我不重复自己的最佳方法。 我想我在访问时会开始收集所有访问过的链接:

 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 

我还在研究最后一点……但这看起来是正确的做法吗?

谢谢。

其他人建议您不要编写自己的网络爬虫。 如果性能和稳健性是您的目标,我同意这一点。 但是,它可以是一个很好的学习练习。 你写了这个:

“[…]但我不知道确保我不重复自己的最好办法”

递归是关键。 类似下面的代码:

 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 

简而言之:

  • 跟踪您使用Set看到的页面。 这不是通过href值,而是通过完整的规范URI。
  • 使用URI.join将可能相对路径转换为相对于当前页面的正确URI。
  • 使用递归来继续抓取每个页面上的每个链接,但如果您已经看过该页面,则会挽救。

你错过了一些东西。

本地引用可以以/开头,但也可以从/开始...甚至没有特殊字符,这意味着该链接在当前目录中。

JavaScript也可以用作链接,因此您需要在整个文档中搜索并找到用作按钮的标记,然后解析URL。

这个:

 links = nf.xpath '//a' #find all links on current page main_links = links.map{|l| l['href'] if l['href'] =~ /^\//}.compact.uniq 

可以写得更好:

 links.search('a[href^="/"]').map{ |a| a['href'] }.uniq 

一般来说,不要这样做:

 ....map{|l| l['href'] if l['href'] =~ /^\//}.compact.uniq 

因为它很尴尬。 map的条件导致结果数组中的nil条目,因此不要这样做。 使用selectreject减少符合条件的链接集,然后使用map进行转换。 在您的使用中,使用CSS中的^=进行预过滤使其更加容易。

不要将链接存储在内存中。 如果您崩溃或停止代码,您将失去所有进度。 相反,至少要在磁盘上使用类似SQLite数据库的东西作为数据存储。 创建一个独特的“href”字段,以避免重复点击同一页面。

使用Ruby的内置URI类或Addressable gem来解析和操作URL。 它们可以节省您的工作,并且当您开始编码/解码查询并尝试规范化参数以检查唯一性,提取和操作路径等时,它们将以正确的方式执行操作。

许多网站在URL查询中使用会话ID来标识访问者。 如果您开始,然后停止,然后重新开始,或者如果您没有返回从网站收到的cookie,那么该ID可以使每个链接不同,因此您必须返回cookie,并确定哪些查询参数是重要的,哪些将要抛弃你的代码。 保留第一个并在存储链接时将第二个丢弃,以便以后解析。

使用像Typhoeus和Hydra这样的HTTP客户端并行检索多个页面,并将它们存储在数据库中,使用一个单独的进程来解析它们并提供URL以解析回数据库。 这会对您的整体处理时间产生巨大影响。

尊重网站的robots.txt文件,并限制您的请求以避免殴打他们的服务器。 没有人喜欢带宽占用并且在没有许可的情况下消耗大量站点的带宽或CPU时间是获得注意然后被禁止的好方法。 您的网站将在此时达到零吞吐量。

这是一个比你似乎意识到的更复杂的问题。 使用图书馆和Nokogiri可能是要走的路。 除非你使用Windows(像我一样),否则你可能想看看Anemone