解析 HTML 表以获得不同数量的列

Parse HTML tables for varying number of columns

我需要解析来自 HTML table 的监控数据以用于记录目的。

HTML文档中有多个table没有任何标识符,所以识别正确的TR需要凑合。

感兴趣的特定行是:

<TR>
    <TD>Signal to Noise Ratio</TD>
    <TD>35 dB</TD>
    <TD>35 dB</TD>
    <!-- MORE TDs continue here... -->
</TR>

因此,可以使用的 identifier/constant 是 TR 中的 "Signal to Noise Ratio" 字符串,用于识别文档中感兴趣的正确 TD。

此行中包含标识字符串的第一个元素之后的 TD 个元素的数量是可变的。我需要将这些元素中的所有整数存储为变量,类似于:

my %data;
my @keys = qw(SNR1 SNR2 SNR3 SNR4);

my $content = LWP::Simple::get("http://192.168.100.1/cmSignalData.htm")
    or die "Couldn't get it!";

if ( $content =~ /<TD>(.+?) dB<\/TD>/ ) {
    $data{SNR1} = ;
} 

for (@keys) {
    print "$_:" . $data{$_} . " ";
}
print "\n";

然后以完全相同的模式解析其他 table 中的其他 TR 元素。

不要使用正则表达式解析 HTML。使用 HTML 解析器。

CPAN 上有几个 HTML 解析器模块可用。我最喜欢的是 Mojo::DOM。您可以像下面这样使用它:

#!/usr/bin/perl
use strict;
use warnings;
use Mojo::DOM;

my $HTML = <<"EOF";
<table>
<TR>
<TD>Signal to Noise Ratio</TD>
<TD>35 dB</TD>
<TD>35 dB</TD>
</TR>
</table>
EOF

my $dom = Mojo::DOM->new( $HTML );

if ($dom->at('tr td')->text() eq 'Signal to Noise Ratio'){
   for my $e ($dom->find('td')->each) {
      if($e->text() =~ /(\d+)\sdB/){
          print ."\n";
      }
   }
}

有关 Mojo::DOMMojo::UserAgent 的 8 分钟视频教程,请查看 Mojocast Episode 5

您可以使用 XPath 查询轻松获得所需的值,因为您正在查找特定 td 节点之后同一级别的所有以下 td 节点。

下面是一个使用 HTML::TreeBuilder::XPath 模块的例子:

use HTML::TreeBuilder::XPath;

my $tree = HTML::TreeBuilder::XPath->new;
$tree->parse_file("yourfile.html");

my @snr = $tree->findvalues('//td[.="Signal to Noise Ratio"]/following-sibling::td');
$tree->delete;

@snr = map /^(\d+)/, @snr;
print join(', ', @snr);

XPath 是一种查询 HTML/XML 文档的树表示的语言,DOM (Document Object Model) tree.

查询详情:

//   # anywhere in the tree (*)
td   # a `td` element with the following "predicate" (embedded in square brackets):

[.="Signal to Noise Ratio"] # predicate: the text content of the current node (figured
                            # by a dot) is exactly "Signal to Noise Ratio"

/following-sibling::td # 'following-sibling::' is a kind of selector called "axis"
                       # that selects all nodes with the same parent node after the
                       # current element.
                       # 'td' selects only `td` elements in this node-set.

(*) 如果你愿意,你可以更明确。您可以从根元素 /html/body/center/table/tbody/tr/td

开始描述完整路径,而不是使用 //td

此方法需要构建文档树才能对其进行查询。这不是一种快速的方法,但主要优点是您使用 HTML 结构而不是通配文本方法。

请注意,您可以避免使用数组 map 来提取每个项目开头的数字。 XPath 有几个字符串函数,包括 substring-before:

//td[.="Signal to Noise Ratio"]/following-sibling::td/substring-before(text(), " dB")

如果性能很重要,您可以使用拉式解析器尝试另一种方法,例如 HTML::TokeParser::Simple。这写起来不太方便,但速度更快,因为没有要构建的 DOM 树,并且您将节省内存,因为您可以将 HTML 文件作为流读取并在需要时停止读取它而无需将整个文件加载到内存中。

这是一个使用 Mojolicious 的版本。它直接从您的 pastebin 存储库中提取 HTML

for 循环遍历所有表中的所有行。其中,@columns数组设置为行

中所有列(<td>个元素)的文本内容

检查第一个元素,首先它存在,其次它等于 Signal to Noise Ratio。如果是这样,则全局数组 @snr 设置为 @columns 余数中的十进制数,并且 last 停止搜索所需的行

use strict;
use warnings;
use 5.010;

use Mojo;

my $ua = Mojo::UserAgent->new;

my $dom = $ua->get('http://pastebin.com/raw.php?i=73H5peKW')->res->dom;

my @snr;

for my $row ( $dom->find('table tr')->each ) {
    my @columns = $row->find('td')->map('text')->each;
    next unless @columns;
    if ( shift @columns eq 'Signal to Noise Ratio' ) {
        @snr = map /(\d+)/, @columns;
        last;
    }
}

say "@snr";

输出

35 35 34 34 34 34 34 34