HTML :: TreeBuilder :: XPath findvalue不起作用

时间:2015-01-30 15:23:03

标签: html perl xpath xhtml

我试图获得价值" Buffalo"来自以下HTML:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Scrape Test</title>
</head>

<body>
  <div id="holder-305857" class="event-holder holder-scheduled">
    <div id="305857" class="eventLine status-scheduled" rel="2015-01-29 21:30:00" itemtype="http://schema.org/SportsEvent" itemscope="">
      <meta itemprop="startdate" content="2015-01-29T21:30:00-05:00" />
      <meta itemprop="name" content="Buffalo Sabres@Edmonton Oilers" />
      <meta itemprop="url" content="http://www.sportsbookreview.com/betting-odds/nhl-hockey/buffalo-vs-edmonton-305857/" />
      <div class="score-content"></div>

      <div class="el-div eventLine-check">
        <input type="checkbox" class="checkBoxItem" /> <a href="http://www.sportsbookreview.com/nhl-hockey/matchups/20150129-70/" style="display:none">matchuplink</a>
      </div>

      <div class="el-div eventLine-rotation" itemprop="location" itemtype="http://schema.org/Place" itemscope="">
        <div class="eventLine-book-value">
          069
        </div>

        <div class="eventLine-book-value">
          070
        </div>
      </div>

      <div class="el-div eventLine-time" id="time-305857">
        <div class="eventLine-book-value">
          9:30p
        </div>
      </div>

      <div class="el-div eventLine-team">
        <div class="eventLine-value">
          <span class="team-name" rel="583">Buffalo</span>
        </div>

        <div class="eventLine-value">
          <span class="team-name" rel="579">Edmonton</span>
        </div><span class="options"><button class="options-btn"><span class="options">Options</span></button></span>
      </div>
    </div>
  </div>
</body>
</html>

我使用Perl尝试提取文本。具体来说,我使用以下代码:

use strict;
use warnings;

use HTML::TreeBuilder::XPath;

my $tree = HTML::TreeBuilder::XPath->new_from_file('html.html');

my $test_value = $tree->findvalue('//*[@id="305857"]/div[5]/div[1]/span');
print $test_value . "\n";

我希望印刷文字是&#34; Buffalo&#34;但事实并非如此。我很难过,你能帮忙吗? TIA

2 个答案:

答案 0 :(得分:2)

您的HTML文档实际上是一个具有默认命名空间的XHTML文档:

<html xmlns="http://www.w3.org/1999/xhtml">

//div这样的XPath表达式只发现div元素,如果它们不在命名空间中 - 您的div元素在命名空间中。

此外,不要让XPath表达式过于复杂,如ThisSuitIsBlackNot所示,如果可以依赖类名,则将其更改为//span[@class='team-name']/text()

您的问题有两种解决方案:您可以在Perl代码中声明此命名空间,然后在XPath表达式中声明前缀元素名称 - 或者忽略输入文档中的命名空间。

声明XHTML命名空间

此选项意味着使命名空间URI“http://www.w3.org/1999/xhtml”可用于XPath环境。我找不到有关如何使用HTML::TreeBuilder::XPath声明命名空间的任何解释。但您可以使用XML::LibXML代替use registerNs() to declare the namespace

忽略命名空间

以下XPath表达式无论文档中存在哪些名称空间都可以使用:

//*[local-name() = 'span' and @class='team-name']/text()

然而,它检索了两个文本节点:

Buffalo
-----------------------
Edmonton

您只能使用

访问“Buffalo”
(//*[local-name() = 'span' and @class='team-name']/text())[1]

编辑:回复您的评论:

  

对不起,我应该更清楚了。实际代码中有几个“团队名”类,因此查询不会有效。

是的,你应该更清楚。始终确保您在问题中包含的缩小样本能够准确反映实际数据中存在的所有复杂性。

然后,忽略表达式中的命名空间变得更加麻烦,但仍然可以完成:

//*[@id="305857"]/*[local-name() = 'div' and position() = 5]/*[local-name() = 'div' and position() = 1]/*[local-name() = 'span']

<强> EDIT2

  

您创建的表达式仍无法在我的PERL代码中使用。返回的值仍为空。有什么想法吗?

是的,还有很多其他问题。例如,就我所见,该页面上没有任何内容,ID为“305857”。此外,该页面是无效的XHTML,因为ID值不是唯一的,并且没有未转义的“&amp;”在它。

我现在用HTML::TreeBuilder写了一个完整的例子,所以没有什么可以出错。请注意,HTML::TreeBuilder::XPath似乎只是忽略了名称空间(与LibXML不同),因此您可以使用未加前缀的元素名称。

#!/usr/bin/perl
use strict;
use warnings;

use HTML::TreeBuilder::XPath;

my $tree = HTML::TreeBuilder::XPath->new_from_file('mypage.html');
my $value = $tree->findvalue('//*[@id="holder-305862"]/div[1]/div[5]/div[1]/span');
print $value;

结果,在修复HTML后使用您描述的URL的整个页面使其有效XHTML:

Buffalo

答案 1 :(得分:1)

Mathias是正确的,问题是您的文档无法验证。

这是一个可行的perl脚本,它使用HTML :: Tidy(需要安装tidyp)来清理文档,然后再解析它。

#!/usr/bin/perl
use strict;
use warnings;

use HTML::Tidy;
use HTML::TreeBuilder::XPath;

my $mess;

{
  open(F, "messy.html");
  local $/ = undef;
  $mess = <F>;
}

my $tidy = HTML::Tidy->new();
my $fresh = $tidy->clean($mess);

my $tree = HTML::TreeBuilder::XPath->new_from_content($fresh);

my $value = $tree->findvalue('//*[@id="305862"]/div[5]/div[1]/span');
print $value;