如何在 Ruby 中使用 Nokogiri 解析日期

How to parse a date using Nokogiri in Ruby

我正在尝试解析此页面并提取

之后开始的日期
>p>From Date:

我收到错误

Invalid predicate: //b[text() = '<p>From Date: ' (Nokogiri::XML::XPath::SyntaxError)

来自 "inspect element" 的 xpath 是

/html/body/div#timelineItems/table/tbody/tr/td/table.resultsTypes/tbody/tr/td/p

这是代码示例:

#/usr/bin/ruby

require 'Nokogiri'
noko = Nokogiri::HTML('china.html')
noko.xpath("//b[text() = '<p>From Date: ").each do |b|
puts b.next_sibling.content.strip
end

这是文件://china.html

<div class="snippet" data-lang="js" data-hide="false">
<div class="snippet-code">
<pre><code>    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

    <html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        
        <title>File </title>

    
      </head>
      <body>
        
            <div id ="timelineItems">
    <H2 id="telegram1"> Title </H2>
            <p><table cellspacing="0">
    <tr>
    <td width="2%">&nbsp;</td>
    <td width="75%">
    <table cellspacing="0" cellpadding="0" class="resultsTypes">
    <tr>
    <td width="5%" class="hide">&nbsp;</td>
    <td width="70%">
    <p>Template: <span class="bidi">ארכיון בן גוריון - מסמך</span></p>
    <p>Title: <a href="http://www.bing.com" title=""><span class="bidi">Meeting in China</span></a></p>

    <p>recipient: David Ben Gurion</p>
    <p>sender: Prime Minister of Union of Burma, Rangoon</p>
    <p>  Sub collection: <span class="bidi">התכתבות > תת-חטיבה מכתב</span></p>
    <p>From Date: 02/14/1936</p>
    <p>Link to file: <span class="bidi">תיק התכתבות  1956 ינואר</span></p>
    </td>
    </tr>
    <tr>
    <td colspan="2">
    </td>
    </tr>
    </table></td>
    <td class="actions">&nbsp;</td>
    </tr>
    </table>

    </p>
          </div>
          
    
    </body></html>

阿玛丹的回答 original.rb

#/usr/bin/ruby

require 'Nokogiri'
noko = Nokogiri::HTML('china.html')
date = noko.at_xpath("//p[starts-with(text(),'From Date: ')]").text()

puts date

formatted = date[/From Date: (.*)/, 1]

puts formatted
给出错误 original.rb:5:in '<main>': undefined method 'text' for nil:NilClass (NoMethodError)

from_date = noko.at_xpath('//p[starts-with(text(), "From Date:")]').text()
date = from_date[/From Date: (.*)/, 1]
# => "02/14/1936"

编辑:

解释:获取文档 (//) 中任意位置的第一个节点 (#at_xpath),使得 ([...]) 文本内容 (text()) 以 ( starts-with(string, stringStart)) "From Date" ("From Date:"), 取其文本内容(#text()), 存入(=) 变量from_date ( from_date)。然后,使用与文字字符 "From Date: " 匹配的正则表达式 (/.../) 从该文本 (from_date) 中提取第一组 (#[regexp, 1]),后跟任意数字(*) 的任何字符 (.),将在第一个捕获组中捕获 ((...)),由 #[regexp, 1].

提取

此外,

Amadan's answer [...] gives an error

我没有注意到您的 Nokogiri 构造已损坏,正如铁皮人所解释的那样。 noko = Nokogiri::HTML('china.html') 行(这不是我的回答的一部分)将为您提供一个单节点文档,其中只有文本 "china.html",根本没有 <p> 节点。

你不能使用

noko = Nokogiri::HTML('china.html')

Nokogiri::HTMLNokogiri::HTML::Document.parse 的快捷方式。 The documentation 说:

.parse(string_or_io, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML) {|options| ... } ⇒ Object`

... string_or_io may be a String, or any object that responds to read and close such as an IO, or StringIO. ...

虽然 'china.html' 是一个字符串,但它不是 HTML。看起来你认为一个文件名就足够了,但是 Nokogiri 不会打开任何东西,它只理解包含标记的字符串,HTML 或 XML,或者响应read 方法。比较这些:

require 'nokogiri'

doc = Nokogiri::HTML('china.html')
doc.to_html
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>china.html</p></body></html>\n"

对比:

doc = Nokogiri::HTML('<html><body><p>foo</p></body></html>')
doc.to_html
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>foo</p></body></html>\n"

和:

doc = Nokogiri::HTML(open('http://www.example.org'))
doc.to_html[0..99]
# => "<!DOCTYPE html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset=\"utf-8\">\n    <met"

最后一个有效是因为 OpenURI 添加了读取 URL 到 open 的能力,它响应 read:

open('http://www.example.org').respond_to?(:read) # => true

继续问题:

require 'nokogiri'
require 'open-uri'

html = <<EOT
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>File </title>


  </head>
  <body>

        <div id ="timelineItems">
<H2 id="telegram1"> Title </H2>
        <p><table cellspacing="0">
<tr>
<td width="2%">&nbsp;</td>
<td width="75%">
<table cellspacing="0" cellpadding="0" class="resultsTypes">
<tr>
<td width="5%" class="hide">&nbsp;</td>
<td width="70%">
<p>Template: <span class="bidi">ארכיון בן גוריון - מסמך</span></p>
<p>Title: <a href="http://www.bing.com" title=""><span class="bidi">Meeting in China</span></a></p>

<p>recipient: David Ben Gurion</p>
<p>sender: Prime Minister of Union of Burma, Rangoon</p>
<p>  Sub collection: <span class="bidi">התכתבות > תת-חטיבה מכתב</span></p>
<p>From Date: 02/14/1936</p>
<p>Link to file: <span class="bidi">תיק התכתבות  1956 ינואר</span></p>
</td>
</tr>
<tr>
<td colspan="2">
</td>
</tr>
</table></td>
<td class="actions">&nbsp;</td>
</tr>
</table>

</p>
      </div>


</body></html>
EOT

doc = Nokogiri::HTML(html)

一旦文档被解析,使用

很容易找到特定的 <p> 标签
<table cellspacing="0" cellpadding="0" class="resultsTypes">

作为地标:

from_date = doc.at('table.resultsTypes p[6]').text
# => "From Date: 02/14/1936"

It looks like its going to be tougher pulling the title = "Meeting in China" and link = "bing.com"; since they are on the same line.

我正在使用 CSS selectors to define the path to the desired text. CSS is more easily read than XPath, though XPath is more powerful and descriptive. Nokogiri allows us to use either, and lets us use search or at with either. at is equivalent to search('some selector').first. There are also CSS and XPath specific versions of search and at, described in Nokogiri::XML::Node

title_link = doc.at('table.resultsTypes p[2] a')['href'] # => "http://www.bing.com"
title = doc.at('table.resultsTypes p[2] span').text # => "Meeting in China"

您正在尝试使用 XPath:

/html/body/div#timelineItems/table/tbody/tr/td/table.resultsTypes/tbody/tr/td/p

但是,它对您正在使用的 HTML 无效。

注意选择器中的 tbody。查看 HTML,紧跟在任一 <table> 标记之后,两者都没有 <tbody> 标记,因此 XPath 是错误的。我怀疑这是由您的浏览器生成的,它正在根据规范对 HTML 进行修复以添加 <tbody>,但是 Nokogiri 没有进行修复以添加 <tbody> 和 HTML 不匹配,导致搜索失败。所以,不要依赖浏览器定义的选择器,也不应该相信浏览器对实际 HTML 来源的看法。


与使用显式选择器相比,在标记中查找特定的路径点并使用它们导航到您想要的节点会更好、更容易、更智能。这是一个执行上述所有操作的示例,仅使用占位符以及 XPath 和 CSS:

的混合
doc.at('//p[starts-with(., "Title:")]').text  # => "Title: Meeting in China"
title_node = doc.at('//p[starts-with(., "Title:")]')
title_url = title_node.at('a')['href'] # => "http://www.bing.com"
title = title_node.at('span').text # => "Meeting in China"

因此,可以混合搭配 CSS 和 XPath。