一个完美的互联网会以客户选择的格式交付数据,无论是 CSV、XML、JSON 等。但现实情况是,互联网有时会挑逗你,提供可用的数据,但通常是以 HTML 或 PDF 文档的形式——这些格式是为数据显示而设计的,而不是为数据交换而设计的。因此,过去的屏幕抓取——提取显示的数据并将其转换为请求的格式——在今天仍然具有现实意义。
Perl 拥有出色的屏幕抓取工具,其中就包括下面抓取程序中描述的 HTML::TableExtract
包。
抓取程序概述
屏幕抓取程序主要包含两个部分,它们按如下方式组合在一起:
- 文件 data.html 包含要抓取的数据。本例中的数据源自一个正在翻新的大学网站,它探讨了大学学位的收入是否能证明其成本是合理的。这些数据包括中位数收入、百分位数以及有关计算、工程和文科等研究领域的其他信息。要运行抓取程序,data.html 文件应托管在 Web 服务器上,在我这里是一个本地 Nginx 服务器。独立的 Perl Web 服务器(如
HTTP::Server::PSGI
或HTTP::Server::Simple
)也可以胜任。 - 文件 scrape.pl 包含抓取程序,该程序使用了
Plack/PSGI
包中的功能,特别是 Plack Web 服务器。抓取程序从命令行启动(如下所述)。用户在浏览器中输入 Plack 服务器的 URL (localhost:5000/
),然后会发生以下情况:- 浏览器连接到 Plack 服务器(
HTTP::Server::PSGI
的一个实例),并向抓取程序发出 GET 请求。URL 末尾的单个斜杠 (/
) 标识了此程序。(即使使用者没有这样做,现代浏览器也会添加闭合斜杠。) - 然后,抓取程序向 data.html 文档发出 GET 请求。如果请求成功,应用程序将使用
HTML::TableExtract
包从文档中提取相关数据,将提取的数据保存到一个文件中,并进行一些基本的统计度量,这些度量代表了对提取数据的处理。一个如下所示的 HTML 报告将返回到用户的浏览器。
- 浏览器连接到 Plack 服务器(

图 1:抓取程序的最终报告
从用户的浏览器到 Plack 服务器,再到托管 data.html 文档的服务器(例如,Nginx)的请求流量可以描述如下:
GET localhost:5000/ GET localhost:80/data.html
user's browser------------------->Plack server-------------------------->Nginx
最后一步只涉及 Plack 服务器和用户的浏览器:
reportFinal.html
Plack server------------------>user's browser
上面的图 1 显示了最终报告文档。
抓取程序的详细信息
源代码和数据文件 (data.html) 可从我的网站下载,压缩文件中包含一个 README 文件。以下是各个部分的快速摘要,后续将进行详细说明:
data.html ## data source to be hosted by a web server
scrape.pl ## main source code, run with the plackup utility (see below)
Stats::Controller.pm ## handles request routing, data extraction, and processing
Stats::Util.pm ## utility functions used in Controller.pm
report.html ## HTML template used to generate the report
rawData.dat ## the extracted data
Plack/PSGI
包附带一个名为 plackup
的命令行实用程序,可用于启动抓取程序。以 %
作为命令行提示符,启动抓取程序的命令是:
% plackup scrape.pl
plackup
命令启动一个独立的 Plack Web 服务器,用于托管抓取程序。抓取代码处理请求路由,从 data.html 文档中提取数据,生成一些基本的统计度量,然后使用 Template::Recall
包为用户生成 HTML 报告。由于 Plack 服务器无限期运行,因此抓取程序会打印进程 ID,该 ID 可用于终止服务器和抓取应用程序。
Plack/PSGI
支持 Rails 风格的路由,其中 HTTP 请求根据两个因素被分派到特定的请求处理程序:
- HTTP 请求方法(谓词),例如 GET 或 POST。
- 所请求资源的统一资源标识符(URI 或名词);在本例中,是 URL
http://localhost:5000/
中的独立结尾斜杠 (/
),用户在浏览器中输入该 URL 以启动抓取程序。
抓取程序仅处理一种类型的请求:针对名为 /
的资源的 GET 请求,并且此资源是我的 Stats::Controller
包中的屏幕抓取和数据处理代码。下面回顾一下 Plack/PSGI
路由设置,它位于源文件 scrape.pl 的顶部:
my $router = router {
match '/', {method => 'GET'}, ## noun/verb combo: / is noun, GET is verb
to {controller => 'Controller', action => 'index'}; ## handler is function get_index
# Other actions as needed
};
请求处理程序 Controller::get_index
只有高级逻辑,将屏幕抓取和报告生成细节留给 Util.pm 文件中的实用程序函数,如下节所述。
屏幕抓取代码
回想一下,Plack 服务器将针对 localhost:5000/
的 GET 请求分派到抓取程序的 get_index
函数。此函数作为请求处理程序,然后开始检索要抓取的数据、抓取数据以及生成最终报告的工作。数据检索部分由一个实用程序函数负责,该函数使用 Perl 的 LWP::Agent
包从托管 data.html 文档的任何服务器获取数据。获得数据文档后,抓取程序调用实用程序函数 extract_from_html
来执行数据提取。
data.html 文档恰好是格式良好的 XML,这意味着可以使用 Perl 包(如 XML::LibXML
)通过显式 XML 解析来提取数据。但是,HTML::TableExtract
包非常吸引人,因为它绕过了繁琐的 XML 解析,并且(用极少的代码)交付了一个包含提取数据的 Perl 哈希。HTML 文档中的数据聚合通常出现在列表或表格中,而 HTML::TableExtract
包的目标是表格。以下是用于数据提取的三个关键代码行:
my $col_headers = col_headers(); ## col_headers() returns an array of the table's column names
my $te = HTML::TableExtract->new(headers => $col_headers);
$te->parse($page); ## $page is data.html
$col_headers
指的是 Perl 字符串数组,每个字符串都是 HTML 文档中的列标题。
sub col_headers { ## column headers in the HTML table
return ["Area",
"MedianWage",
...
"BoostFromGradDegree"];
}
在调用 TableExtract::parse
函数之后,抓取程序使用 TableExtract::rows
函数迭代提取数据的行——不包含 HTML 标记的数据行。这些行作为 Perl 列表,被添加到名为 %majors_hash
的 Perl 哈希中,可以将其描述如下:
- 每个键标识一个研究领域,例如计算或工程。
- 每个键的值是七个提取数据项的列表,其中七是 HTML 表格中的列数。对于计算,带有注释的列表是:
name median % with this degree income boost from GD / / / / (Computing 55000 75000 112000 5.1% 32.0% 31.0%) ## data items / \ \ 25th-ptile 75th-ptile % going on for GD = grad degree
包含提取数据的哈希被写入本地文件 rawData.dat。
ForeignLanguage 50000 35000 75000 3.5% 54% 101%
LiberalArts 47000 32000 70000 9.7% 41% 48%
...
Engineering 78000 54000 104000 8.2% 37% 32%
Computing 75000 51000 112000 5.1% 32% 31%
...
PublicPolicy 50000 36000 74000 2.3% 24% 45%
下一步是处理提取的数据,在本例中是使用 Statistics::Descriptive
包进行基本的统计分析。在上面的图 1 中,统计摘要显示在报告底部的单独表格中。
报告生成代码
抓取程序的最后一步是生成报告。Perl 提供了生成 HTML 的选项,而 Template::Recall
就是其中之一。顾名思义,该软件包从 HTML 模板生成 HTML,该模板是标准 HTML 标记和自定义标签的混合体,这些标签充当从后端代码生成的数据的占位符。模板文件是 report.html,而感兴趣的后端函数是 Controller::generate_report
。以下是代码和模板如何交互的:
报告文档(图 1)包含两个表格。顶部表格通过迭代生成,因为每一行都具有相同的列(研究领域、第 25 个百分位的收入等等)。在每次迭代中,代码都会创建一个哈希,其中包含特定研究领域的值:
my %row = (
major => $key,
wage => '$' . commify($values[0]), ## commify turns 1234 into 1,234
p25 => '$' . commify($values[1]),
p75 => '$' . commify($values[2]),
population => $values[3],
grad => $values[4],
boost => $values[5]
);
哈希键是 Perl 裸字,例如 major
和 wage
,它们表示先前从 HTML 数据文档中提取的数据值列表中的项目。相应的 HTML 模板如下所示:
[ === even === ]
<tr class = 'even'>
<td>['major']</td>
<td align = 'right'>['p25']</td>
<td align = 'right'>['wage']</td>
<td align = 'right'>['p75']</td>
<td align = 'right'>['pop']</td>
<td align = 'right'>['grad']</td>
<td align = 'right'>['boost']</td>
</tr>
[=== end1 ===]
自定义标签用方括号括起来。顶部和底部的标签分别标记要呈现的模板区域的开始和结束。其他自定义标签标识后端代码的各个目标。例如,标识为 major
的模板列与以 major
作为键的哈希条目匹配。以下是后端代码中将数据绑定到自定义标签的调用:
print OUTFILE $tr->render('end1');
引用 $tr
指的是 Template::Recall
实例,而 OUTFILE
是报告文件 reportFinal.html,它是从模板文件 report.html 以及后端代码生成的。如果一切顺利,用户将在浏览器中看到 reportFinal.html 文档(参见图 1)。
抓取程序借鉴了优秀的 Perl 包,例如 Plack/PSGI
、LWP::Agent
、HTML::TableExtract
、Template::Recall
和 Statistics::Descriptive
,以处理通常很混乱的屏幕抓取数据任务。这些包可以很好地协同工作,因为每个包都针对特定的子任务。最后,可以扩展抓取程序以聚类提取的数据:Algorithm::KMeans
包非常适合此扩展,并且可以使用保存在 rawData.dat 文件中的数据。
2 条评论