【问题标题】:Parsing prices from HTML returns blank or no value?从 HTML 解析价格返回空白或没有值?
【发布时间】:2016-07-27 19:29:30
【问题描述】:

我没有看到任何成功,因为无论我做什么它都只返回空白值。

这是我的代码:

require 'rubygems'
require 'nokogiri'
require 'open-uri'

PAGE_URL = "http://www.oficinadosbits.com.br/produto18064/EVGA_GeForce_GTX_980_04G-P4-2983-KR.html"

page = Nokogiri::HTML(open(PAGE_URL))

price = page.xpath("/html/body/div[1]/div/div/table[1]/tbody/tr[1]/td[3]/table[1]/tbody/tr[2]/td[3]/table[1]/tbody/tr/td[1]/font[2]/table/tbody/tr[1]/td/span").text

puts price

我尝试使用 CSS 和 Mechanize,但没有成功:

require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'mechanize'

mechanize = Mechanize.new

page = mechanize.get("http://www.oficinadosbits.com.br/produto18064/EVGA_GeForce_GTX_980_04G-P4-2983-KR.html")

doc = page.parser

price = doc.xpath("/html/body/div[1]/div/div/table[1]/tbody/tr[1]/td[3]/table[1]/tbody/tr[2]/td[3]/table[1]/tbody/tr/td[1]/font[2]/table/tbody/tr[1]/td/span").text

puts price

当我使用时:

puts price.size

最后它返回一个零。它怎么读到零值?

我想了解为什么会发生这种情况,以及如何解决它以便能够解析价格。

我从 Firebug 的“复制 xpath”选项中获得了 xpath。

【问题讨论】:

  • 欢迎来到 Stack Overflow。我们不在乎您的体验,我们只需要经过深思熟虑的问题来显示您的努力。 “How to Ask”解释了一切。不要使用基于浏览器的工具来获取 HTML 或确定选择器。浏览器与 HTML 混淆,因此它显示的内容通常不可信。此外,使用“复制 xpath”会导致选择器又长又脆弱。相反,使用wgetcurl 或Nokogiri 本身来检索页面并在浏览器外部查看它。然后找出文档中用于导航到所需节点的路径点。
  • 另外,当询问您的代码时,我们需要最少的输入数据和您的预期输出在问题本身。请阅读“minimal reproducible example”。它可以帮助我们帮助您,也可以帮助那些在未来尝试解决相同问题的人。

标签: ruby nokogiri screen-scraping mechanize


【解决方案1】:

/html/body/div[1]/div/div/table[1] 中没有 tbody。但你可以自己检查一下。

page.xpath("/html/body/div[1]/div/div/table[1]")
# => lots of output
page.xpath("/html/body/div[1]/div/div/table[1]/tbody")
# => whoopsie.

问题是,FireBug 的“复制 XPath”将为您提供 DOM 的 XPath,因为它在您请求它的那一刻在浏览器中,由于各种原因,它可能与源文档的 DOM 不同:例如DOM 被 JavaScript 改变,或者某些节点被浏览器自动插入。

【讨论】:

  • 我完全相信 FireBug 的“复制 XPath”。正如我所说,我是一个初学者,并且认为问题可能是其他问题。非常感谢这些提示!
【解决方案2】:

如果您想获得以下价格:

"GeForce GTX 980 4GB GDDR5 256bits - Game Grátis - EVGA 04G-P4-2983-KR" 

您可以将 Nokogiri 与 CSS 选择器一起使用:

doc = Nokogiri::HTML(open("http://www.oficinadosbits.com.br/produto18064/EVGA_GeForce_GTX_980_04G-P4-2983-KR.html"))
price = doc.css("html > body > div")[0].css("div > div > table[1] > tr")[0].css("td[3] > table")[1].css("tr > td")[1].css("span")[0].text

【讨论】:

  • 哦。我的。 css(...)[0]at(...) 相同,因此请利用 at 及其变体。您选择节点的方法非常脆弱,因为页面中的更改会破坏它。相反,找到路点并使用它们来定位您想要的节点。请参阅@pguardiario 的回答。
  • 非常感谢您的解释!我看到@pguardiario 的解决方案非常干净。对 html 进行细微更改是否安全?
【解决方案3】:

该页面上有大量有用的 CSS:

page.at('[itemprop=price]').text
#=> "R$ 3.459,90"

【讨论】:

  • 哇,好简单!这个真的帮助了我。您介意向我解释为什么在这种情况下使用括号而不是括号吗?谢谢!
  • 当然,属性总是放在括号中。如果您对此不熟悉,请阅读一些 css 文档。
【解决方案4】:

Nokogiri 支持 XPath 和 CSS 选择器。我通常使用 CSS 是为了可读性和简单性,但 XPath 也很重要,因为它具有强大的功能。

考虑这段代码:

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)
<html>
  <body>
    <p id="p1" class="paragraphs" foo="bar">some text</p>
    <p id="p2" class="paragraphs" foo="baz">some text</p>
  </body>
</html>
EOT

我们可以通过标签找到节点:

p_nodes = doc.search('p')
p_nodes.class             # => Nokogiri::XML::NodeSet
p_nodes.size              # => 2
p_nodes.map(&:to_html)    # => ["<p id=\"p1\" class=\"paragraphs\" foo=\"bar\">some text</p>", "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"]

使用search 返回一个NodeSet,它类似于一个数组。在这个例子中,它找到了两个&lt;p&gt; 标签。

与使用at相比:

p_node = doc.at('p') 
p_node.class                            # => Nokogiri::XML::Element
p_node = doc.at('p#p2').to_html         # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"
p_node = doc.at('p.paragraphs').to_html # => "<p id=\"p1\" class=\"paragraphs\" foo=\"bar\">some text</p>"

at 相当于获取search 找到的第一个元素,但它返回一个元素/节点。一个节点可以包含更多的节点/标签,一个 NodeSet 将是一个节点数组,在所有情况下,一个节点就像指向文档的指针,对于导航很有用。

doc.at('body').at('p') # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>
doc.at('body > p')     # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>
doc.at('p')            # => #<Nokogiri::XML::Element:0x3fd431448c6c name="p" attributes=[#<Nokogiri::XML::Attr:0x3fd431448c08 name="id" value="p1">, #<Nokogiri::XML::Attr:0x3fd431448bf4 name="class" value="paragraphs">, #<Nokogiri::XML::Attr:0x3fd431448be0 name="foo" value="bar">] children=[#<Nokogiri::XML::Text:0x3fd431448384 "some text">]>

注意节点的地址如何

Nokogiri::XML::Element:0x3fd431448c6c

在上述结果中保持不变。

在此基础上,要在 HTML 中查找节点,我们可以使用各种参数进行导航:

doc.at('p[foo="baz"]').to_html # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"

doc.search('p[foo="baz"]').size # => 1
doc.search('p[foo="baz"]').first.to_html # => "<p id=\"p2\" class=\"paragraphs\" foo=\"baz\">some text</p>"

这样做的要点是,我们应该检查 HTML,找到让我们获得所需信息的特定节点,然后编写最小选择器以到达那里。如果 HTML 发生变化,长选择器更有可能中断。

最后,小心使用浏览器检查代码,因为它们会弄乱表格。我创建了一个文件,其中包含:

<html>
  <body>
    <table>
      <tr>
        <td>foo</td>
      </tr>
    </table>
  </body>
</html>

在 Firefox、Opera 或 Safari 中打开它并检查页面会导致 HTML 已被修改:

<html>
  <head></head>
  <body>
    <table>
      <tbody>
        <tr>
          <td>foo</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

不要信任浏览器,而是使用wgetcurl 或 Nokogiri 自己的命令行:

$ nokogiri http://example.com
Your document is stored in @doc...
irb(main):001:0> @doc
=> #<Nokogiri::HTML::Document:0x3fd748d60740 name="document" children=[#<Nokogiri::XML::DTD:0x3fd748d41a5c name="html">, #<Nokogiri::XML::Element:0x3fd748d41750 name="html" children=[#<Nokogiri::XML::Text:0x3fd748d41534 "\n">, #<Nokogiri::XML::Element:0x3fd748d41430 name="head" children=[#<Nokogiri::XML::Text:0x3fd748d41200 "\n    ">, #<Nokogiri::XML::Element:0x3fd748d41138 name="title" children=[#<Nokogiri::XML::Text:0x3fd748d40f44 "Example Domain">]>, #<Nokogiri::XML::Text:0x3fd748d40d78 "\n\n    ">, #<Nokogiri::XML::Element:0x3fd748d40cb0 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d40c4c name="charset" value="utf-8">]>, #<Nokogiri::XML::Text:0x3fd748d40544 "\n    ">, #<Nokogiri::XML::Element:0x3fd748d40454 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d403dc name="http-equiv" value="Content-type">, #<Nokogiri::XML::Attr:0x3fd748d403c8 name="content" value="text/html; charset=utf-8">]>, #<Nokogiri::XML::Text:0x3fd748d3d934 "\n    ">, #<Nokogiri::XML::Element:0x3fd748d3d858 name="meta" attributes=[#<Nokogiri::XML::Attr:0x3fd748d3d7a4 name="name" value="viewport">, #<Nokogiri::XML::Attr:0x3fd748d3d77c name="content" value="width=device-width, initial-scale=1">]>, #<Nokogiri::XML::Text:0x3fd748d3ce1c "\n    ">, #<Nokogiri::XML::Element:0x3fd748d3cd68 name="style" attributes=[#<Nokogiri::XML::Attr:0x3fd748d3cd04 name="type" value="text/css">] children=[#<Nokogiri::XML::CDATA:0x3fd748d3c4bc "\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 50px;\n        background-color: #fff;\n        border-radius: 1em;\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        body {\n            background-color: #fff;\n        }\n        div {\n            width: auto;\n            margin: 0 auto;\n            border-radius: 0;\n            padding: 1em;\n        }\n    }\n    ">]>, #<Nokogiri::XML::Text:0x3fd748d3c0e8 "    \n">]>, #<Nokogiri::XML::Text:0x3fd748d39e38 "\n\n">, #<Nokogiri::XML::Element:0x3fd748d39d48 name="body" children=[#<Nokogiri::XML::Text:0x3fd748d39adc "\n">, #<Nokogiri::XML::Element:0x3fd748d39a00 name="div" children=[#<Nokogiri::XML::Text:0x3fd748d397bc "\n    ">, #<Nokogiri::XML::Element:0x3fd748d39460 name="h1" children=[#<Nokogiri::XML::Text:0x3fd748d39118 "Example Domain">]>, #<Nokogiri::XML::Text:0x3fd748d38f60 "\n    ">, #<Nokogiri::XML::Element:0x3fd748d38e84 name="p" children=[#<Nokogiri::XML::Text:0x3fd748d38c7c "This domain is established to be used for illustrative examples in documents. You may use this\n    domain in examples without prior coordination or asking for permission.">]>, #<Nokogiri::XML::Text:0x3fd748d38ab0 "\n    ">, #<Nokogiri::XML::Element:0x3fd748d389fc name="p" children=[#<Nokogiri::XML::Element:0x3fd748d38808 name="a" attributes=[#<Nokogiri::XML::Attr:0x3fd748d387a4 name="href" value="http://www.iana.org/domains/example">] children=[#<Nokogiri::XML::Text:0x3fd748d38344 "More information...">]>]>, #<Nokogiri::XML::Text:0x3fd748d38088 "\n">]>, #<Nokogiri::XML::Text:0x3fd748d35eb4 "\n">]>, #<Nokogiri::XML::Text:0x3fd748d35cfc "\n">]>]>
irb(main):002:0> @doc.at('a').to_html
=> "<a href=\"http://www.iana.org/domains/example\">More information...</a>"
irb(main):003:0> @doc.at('a')['href']
=> "http://www.iana.org/domains/example"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-08-12
    • 2023-01-04
    • 2023-03-12
    • 1970-01-01
    相关资源
    最近更新 更多