【问题标题】:DRY search every page of a site with nokogiri使用 nokogiri DRY 搜索网站的每一页
【发布时间】:2013-06-06 19:04:33
【问题描述】:

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

所以它很容易开始:

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

我仍在努力解决最后一点……但这似乎是正确的方法吗?

谢谢。

【问题讨论】:

  • 有什么问题?如何跟踪访问过的链接?
  • 不要编写自己的网络爬虫。有很多表现良好的可供选择。

标签: ruby web-scraping web-crawler nokogiri dry


【解决方案1】:

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

“[…] 但我不知道确保自己不会重复自己的最佳方式”

递归是这里的关键。类似于以下代码:

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。
  • 使用递归来继续抓取每个页面上的每个链接,但如果您已经看过该页面,则退出。

【讨论】:

  • 感谢您的启发性帖子!在这里可以学到很多东西!谢谢。
  • 我喜欢你的方法,set & uri gems 很有帮助。感谢您的努力和分享您的知识!我很感激!我仍然不明白这一切,但我会的!
【解决方案2】:

你错过了一些东西。

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

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,并找出哪些查询参数是重要的,哪些会扔掉你的代码。当您存储链接以供以后解析时,保留第一个并丢弃第二个。

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

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

【讨论】:

  • 这很有帮助;谢谢。有很多需要考虑、学习和实施!
  • +1 表示robots.txt 和节流(我的答案都不包括)。
【解决方案3】:

这是一个比您似乎意识到的更复杂的问题。使用库和Nokogiri 可能是要走的路。除非您使用 Windows(如我),否则您可能需要查看 Anemone

【讨论】:

  • 我在研究这个时看到了对海葵的引用;我去看看,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-06
  • 1970-01-01
相关资源
最近更新 更多