如何在Solaris 10 UNIX框中解析HTML文件以将所有值放入<;td>;元素转换为CSV文件


How do you parse HTML file on Solaris 10 UNIX box to put all values within <td> elements into CSV file?

我非常熟悉PHP,包括命令行,半熟悉BASH脚本,没有Perl或其他语言的经验,但愿意使用任何有效的语言。

我试图解析的HTML文件有700000多行,61MB。我无法更改构建HTML表的源代码,只能通过wget下载整个表http://10.1.1.2/file.pl

以下是我试图解析的HTML代码的示例格式:

<HTML>
  <HEAD>
    <TITLE>Objects</TITLE>
    <STYLE type="text/css">
    a:hover
    {
    color:red
    }
    </STYLE>
    </HEAD>
  <BODY>
  <IMG src="http://10.1.1.2/images/logo.gif"/>
  <BR/><BR/>
  <TABLE border="0">
    <TR>
      <TH>Objects</TH>
    </TR>
    <TR>
      <TD><HR style="width:227px"></TD>
    </TR>
  </TABLE>
  <table border=1 cellpadding=5 cellspacing=0><tr><th><b>Subtype</b></th><th><b>Object</b>    </th></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/10/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/13/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/13/3/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/3/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/3/0/0-5</td></tr>
    ... 700,000 more lines ...
  </table>        </BODY>
</HTML>

我想要的CSV:

Subtype,Object
10GigEthernet,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0-5

我非常感谢你能提供的任何帮助!提前谢谢。

来自@shellter代码的结果:

# wget http://10.1.1.2/reports/file.pl
--2012-01-19 06:56:59--  http://10.1.1.2/reports/file.pl
Connecting to 10.1.1.2... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified
Saving to: `file.pl'
    [          <=>                          ] 61,000,000  1.01M/s   in 58s     
2012-01-19 06:58:00 (1.01 MB/s) - `file.pl' saved [61000000]
# sed -n '/<'/td>/{
>            s@<tr><td>@@;
>            s@</td>@XaYbZc@;
>            s@<td>@@;
>            s@</td></tr>@@;
>            s/XaYbZc/,/
>            s/^    //
>            p
>           }' file.pl > routerList.csv
# ls -l
total 203408
-rw-r--r--   1 root     root     61000000 Jan 19 06:58 file.pl
-rw-r--r--   1 root     root     42708247 Jan 19 06:58 routerList.csv
# head routerList.csv
10GigEthernetn,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernetn,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernetn,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernetn,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernetn,SNFCCAMK34T-TenGigE0/3/0/0-5

虽然我不得不同意大多数评论,比如"使用DOM或XPATH等",在这种情况下,你很幸运,你想要处理的所有数据都在一行。如果数据中的任何地方都有换行符,那么这将不起作用,而且基本上不可能得到一个有效的解决方案。因此,对于这些问题,请尝试此

 wget http://10.1.1.2/file.pl
 sed -n '/<'/td>/{
           s@<tr><td>@@;
           s@</td>@XaYbZc@;
           s@<td>@@;
           s@</td></tr>@@;
           s/XaYbZc/,/
           s/^    //
           p
          }' file.pl > routerList.csv
cat routerList.csv
10GigEthernet,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0-5

sed脚本使用"@"字符作为匹配/替换节分隔符。

首先,我们取行上的第一个<tr><td>并将其删除,

然后,我们取第一个</td>,并将其替换为XaYbZc作为临时标记。

拆下剩余开口<td>

移除尾部</td></tr>

将临时XaYbZc替换为","

拆下管路前部的4个空间。

打印缓冲区。(完成!)

我希望这能有所帮助。

我会放弃使用正确的方法(使用真正的解析器),只使用正则表达式进行处理。

这(在Perl中)是脆弱且容易出错的,但应该尽可能快。。。

print "$1,$2'n" while $html =~ /<tr><td>([^<]+)<'/td><td>([^<]+)/g;

这可能对您有用:

 sed '1i'Subtype,Object'$''n''/^'s*<tr><td>/!d;s/'s*<tr>'|<'/tr>'s*//g;s/<td>'([^<]*')<'/td>/'1,/g;s/.$//' file
Subtype,Object
10GigEthernet,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0-5

Perl及其XML::LibXML模块快速且肮脏(它不是Perl的标准配置,但通常很容易安装,一旦你知道如何安装CPAN模块):

/tmp % xpath -He '//td//text()' test.html | perl -pe '$x=1-$x and s#$/#,#'
10GigEthernet,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0-5

这里,xpath是我编写的一个简单的Perl脚本,用于使用xpath从XML/HTML文档中选择内容。第二个Perl命令是将结果重新格式化为两列格式的快速而肮脏的方法,如果您的文档中有其他类型的<td/>,而您不希望出现在输出中,那么这将失败。

因此,这可能不会完全满足你现在的需求,但特别是如果你预计未来必须进行更多此类选择,你可能想写一个可以稍后调整的脚本,在这种情况下,这是一个可能的起点。

到目前为止,所有的答案都说"你应该用正确的方式来做",然后展示如何用"错误的方式"来做。这是一个正确的方式的例子。此版本使用DOM解析器(特别是Mojo::DOM,尽管其他版本的工作方式类似)和Text::CSV

#!/usr/bin/env perl
use strict;
use warnings;
# Use this for real
#use Mojo::UserAgent;
#my $ua = Mojo::UserAgent->new;
#my $dom = $ua->get('http://10.1.1.2/file.pl')->res->dom;
# Use this for test
use Mojo::DOM;
my $dom = Mojo::DOM->new(do { local $/; <DATA> });
# Common code (test and real)
use Text::CSV;
my $csv = Text::CSV->new;
my $output;
sub append_row {
  return unless @_;
  $csv->combine(@_) or die $csv->status();
  $output .= $csv->string() . "'n"; 
}
my $table = $dom->find('table')->[1];
append_row( $table->find('th')->pluck('all_text')->each );
$table->find('tr')->each(sub{
  append_row( $_->find('td')->pluck('text')->each );
});
print $output;

__DATA__
<HTML>
  <HEAD>
    <TITLE>Objects</TITLE>
    <STYLE type="text/css">
    a:hover
    {
    color:red
    }
    </STYLE>
    </HEAD>
  <BODY>
  <IMG src="http://10.1.1.2/images/logo.gif"/>
  <BR/><BR/>
  <TABLE border="0">
    <TR>
      <TH>Objects</TH>
    </TR>
    <TR>
      <TD><HR style="width:227px"></TD>
    </TR>
  </TABLE>
  <table border=1 cellpadding=5 cellspacing=0><tr><th><b>Subtype</b></th><th><b>Object</b>    </th></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/10/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/13/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/13/3/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/3/0/0</td></tr>
    <tr><td>10GigEthernet</td><td>SNFCCAMK34T-TenGigE0/3/0/0-5</td></tr>
    ... 700,000 more lines ...
  </table>        </BODY>
</HTML>

这导致

Subtype,Object
10GigEthernet,SNFCCAMK34T-TenGigE0/10/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/13/3/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0
10GigEthernet,SNFCCAMK34T-TenGigE0/3/0/0-5

很像其他的,但处理各种边缘情况。在我看来,使用现代DOM(甚至XPath)解析器,以正确的方式进行解析比编写正则表达式更容易,而且可以避免以错误的方式进行操作所带来的所有陷阱;那么为什么不先用正确的方法呢?

相关文章: