我非常熟悉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)解析器,以正确的方式进行解析比编写正则表达式更容易,而且可以避免以错误的方式进行操作所带来的所有陷阱;那么为什么不先用正确的方法呢?