使用Ruby获取网页的所有链接

时间:2011-07-14 21:50:42

标签: ruby regex string nokogiri

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

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

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

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

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

谢谢!

5 个答案:

答案 0 :(得分:16)

使用正则表达式适用于快速而脏的脚本,但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

答案 1 :(得分:6)

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的真实变体的健壮的正则表达式,其中一些不是有效的,非常困难,并且最终比简单的解析解决方案更复杂,该解决方案几乎适用于将在浏览器中呈现的所有页面。

答案 2 :(得分:5)

我是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并提取所有包含网址的标记,或让它只检索文本并将其传递给URI.extract,或者让URI.extract完成所有操作。< / p>

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

解析器不像正则表达式那样工作。它构建了文档的内部表示,然后逐步完成。它并不关心文件/标记的布局方式,而是关注DOM的内部表示。 Nokogiri放松了它的解析来处理HTML,因为HTML因写得不好而臭名昭着。这有助于我们,因为大多数非验证HTML Nokogiri可以解决它。偶尔我会遇到一些写得很糟糕的东西,Nokogiri无法正确地解决它,所以我必须通过调整HTML来给它一点轻推,然后再把它传递给Nokogiri;我仍然会使用解析器,而不是尝试使用模式。

答案 3 :(得分:4)

为什么你不在你的模式中使用群组? e.g。

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

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

答案 4 :(得分:1)

你可以在正则表达式中加入群组吗?这会将正则表达式减少为1而不是2。