【问题标题】:Getting all links of a webpage using Ruby使用 Ruby 获取网页的所有链接
【发布时间】:2011-10-05 17:38:00
【问题描述】:

我正在尝试使用 Ruby 检索网页的每个外部链接。我在这个正则表达式中使用String.scan

/href="https?:[^"]*|href='https?:[^']*/i

然后,我可以使用 gsub 删除 href 部分:

str.gsub(/href=['"]/)

这很好用,但我不确定它在性能方面是否有效。这可以使用还是我应该使用更具体的解析器(例如 nokogiri)?哪种方式更好?

谢谢!

【问题讨论】:

  • 请不要尝试用正则表达式解析 HTML,HTML 解析器会更好地为您服务。
  • 因为 HTML 解析比您想象的要复杂,而且有很多简单的正则表达式无法处理的损坏的 HTML:stackoverflow.com/questions/4231382/…
  • 实际上,在这种简单的情况下,我希望正则表达式解决方案比解析解决方案更健壮。不过,我会将 [^"] 替换为 [^" >]。我也希望它会快一点。但这有点取决于目标。如果这在一个必须工作多年的生产系统中使用,我会选择一个解析器,如果它是一个供自己使用的脚本,肯定是正则表达式。
  • 在所有情况下,我都希望一个简单的解析解决方案比一个简单的正则表达式解决方案更健壮。 :)
  • @markijbema,我们在 HTML 中经常看到的非人为问题是= 处的空格,或者缺少单引号,或者使用双引号。即使在一个创建者的单个文档中,这些事情也经常发生。可以编写更复杂的正则表达式来处理它,但是解析器会毫无问题地完成它。

标签: ruby regex string nokogiri


【解决方案1】:

使用正则表达式对于快速而肮脏的脚本来说很好,但 Nokogiri 使用起来非常简单:

require 'nokogiri'
require 'open-uri'

fail("Usage: extract_links URL [URL ...]") if ARGV.empty?

ARGV.each do |url|
  doc = Nokogiri::HTML(open(url))
  hrefs = doc.css("a").map do |link|
    if (href = link.attr("href")) && !href.empty?
      URI::join(url, href)
    end
  end.compact.uniq
  STDOUT.puts(hrefs.join("\n"))
end

如果您只需要该方法,请根据您的需要对其进行一点重构:

def get_links(url)
  Nokogiri::HTML(open(url).read).css("a").map do |link|
    if (href = link.attr("href")) && href.match(/^https?:/)
      href
    end
  end.compact
end

【讨论】:

  • 你能解释一下优点吗?该代码看起来比使用正则表达式和扫描更复杂。我也很想知道哪种解决方案更快。
  • @tokland,我想你想要 Nokogiri::HTML。另请注意仅提取绝对链接的要求。
【解决方案2】:

我是 Nokogiri 的忠实粉丝,但为什么要重新发明轮子呢?

Ruby 的 URI 模块已经有 extract 方法来做到这一点:

URI::extract(str[, schemes][,&blk])

来自文档:

从字符串中提取 URI。如果给出块,则遍历所有匹配的 URI。如果给定块或匹配的数组,则返回 nil。

require "uri"

URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
# => ["http://foo.example.com/bla", "mailto:test@example.com"]

您可以使用 Nokogiri 遍历 DOM 并提取所有具有 URL 的标签,或者让它只检索文本并将其传递给 URI.extract,或者让 URI.extract 完成所有工作。

而且,为什么要使用解析器,例如 Nokogiri,而不是正则表达式模式?因为 HTML 和 XML 可以以多种不同的方式进行格式化,并且仍然可以在页面上正确呈现或有效地传输数据。在接受不良标记时,浏览器非常宽容。另一方面,正则表达式模式在非常有限的“可接受性”范围内工作,该范围由您对标记变化的预期程度来定义,或者相反,您对模式可能出错的方式的预期程度呈现出意想不到的模式。

解析器不像正则表达式那样工作。它构建文档的内部表示,然后遍历它。它不关心文件/标记的布局方式,它在 DOM 的内部表示上工作。 Nokogiri 放宽了对 HTML 的解析,因为 HTML 以写得不好而臭名昭著。这对我们很有帮助,因为使用大多数非验证 HTML Nokogiri 可以修复它。偶尔我会遇到一些写得很糟糕的东西,以至于 Nokogiri 无法正确修复它,所以在我将它传递给 Nokogiri 之前,我必须通过调整 HTML 来稍微推动它;不过我还是会使用解析器,而不是尝试使用模式。

【讨论】:

    【解决方案3】:

    Mechanize 在底层使用 Nokogiri,但具有用于解析 HTML 的内置细节,包括链接:

    require 'mechanize'
    
    agent = Mechanize.new
    page = agent.get('http://example.com/')
    
    page.links_with(:href => /^https?/).each do |link|
      puts link.href
    end
    

    使用解析器通常总是比使用正则表达式解析 HTML 更好。这是 Stack Overflow 上的一个常见问题,this 是最著名的答案。为什么会这样?因为构建一个健壮的正则表达式可以处理现实世界的 HTML 变体,有些是有效的,有些不是,这是非常困难的,并且最终比一个简单的解析解决方案更复杂,该解决方案几乎适用于将在浏览器中呈现的所有页面。

    【讨论】:

    • 我同意,当您需要解析 html 时,您不想使用正则表达式。但在这种情况下,我认为一个正则表达式就足够了,因为你不会因为 html 的不规则性而遇到麻烦(因为不涉及递归)。你能想到一个(非人为的)例子,这个正则表达式(在我对问题的评论中提到我的改进)会失败吗?
    • 顺便说一句,我确实更喜欢你的解决方案,它简短易读,但我不太喜欢过于绝对的事实,比如“你不能用正则表达式触摸 html”。
    • @markijbema 我添加了一些解释。这是我见过的一个案例:<a href= "http://foo">foo</a>。有时也有换行符。
    【解决方案4】:

    为什么你不在你的模式中使用组? 例如

    /http[s]?:\/\/(.+)/i
    

    所以第一组已经是您搜索的链接。

    【讨论】:

      【解决方案5】:

      你能在你的正则表达式中加入组吗?这会将您的正则表达式减少到 1 而不是 2。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-02-23
        • 1970-01-01
        • 2012-02-04
        • 1970-01-01
        • 1970-01-01
        • 2018-03-26
        • 2020-09-13
        相关资源
        最近更新 更多