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



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

/href="https?:[^"]*|href='https?:[^']*/i
然后,我可以使用gsub来删除href部分:
str.gsub(/href=['"]/)

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

谢谢!

使用正则表达式对于快速和肮脏的脚本是好的,但是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

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

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

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

From the docs:

从字符串中提取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可以用许多不同的方式进行格式化,并且仍然可以在页面上正确呈现或有效地传输数据。当涉及到接受不良标记时,浏览器是非常宽容的。另一方面,Regex模式在非常有限的"可接受"范围内工作,这个范围是由您对标记变化的预期程度来定义的,或者相反,当出现意外模式时,您对模式可能出错的方式的预期程度来定义的。

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

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上经常被问到的问题,这是最有名的答案。为什么会这样呢?因为构造一个健壮的正则表达式来处理真实世界的HTML变体(有些是有效的,有些是无效的)是非常困难的,而且最终比一个简单的解析解决方案要复杂得多,而这个解析解决方案将适用于浏览器中呈现的几乎所有页面。

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

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

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

你能把组放在你的正则表达式吗?这将减少您的正则表达式为1而不是2。

相关内容

  • 没有找到相关文章

最新更新