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

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

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

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

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

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

谢谢!

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

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

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

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

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

我是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因写得不好而臭名昭着。 这有助于我们,因为大多数非validationHTML Nokogiri可以解决它。 偶尔我会遇到一些写得很糟糕的东西,Nokogiri无法正确地解决它,所以我必须通过在将它传递给Nokogiri之前调整HTML来轻微推动它; 我仍然会使用解析器,而不是尝试使用模式。

你能把团体放在你的正则表达式吗? 这会将正则表达式减少到1而不是2。