一个完美的互联网会以客户选择的格式交付数据,无论是 CSV、XML、JSON 等。但真实的互联网有时会让人捉摸不透,它会提供数据,但通常是以 HTML 或 PDF 文档的形式——这些格式是为数据显示而设计的,而不是为数据交换而设计的。因此,过去的屏幕抓取——提取显示的数据并将其转换为请求的格式——在今天仍然具有现实意义。
Perl 拥有出色的屏幕抓取工具,其中就包括下面“抓取程序”中描述的 HTML::TableExtract
包。
抓取程序概述
屏幕抓取程序有两个主要部分,它们按如下方式组合在一起:
- 文件 data.html 包含要抓取的数据。本例中的数据来源于一个正在翻新的大学网站,它探讨了大学学位带来的收入是否能证明学位成本的合理性。数据包括中位数收入、百分位数以及计算机、工程和人文科学等研究领域的其他信息。要运行抓取程序,data.html 文件应托管在 Web 服务器上,我这里使用的是本地 Nginx 服务器。诸如
HTTP::Server::PSGI
或HTTP::Server::Simple
等独立的 Perl Web 服务器也可以。 - 文件 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) 可从我的网站上的 ZIP 文件中获取,该文件包含一个 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,这意味着可以使用诸如 XML::LibXML
之类的 Perl 包通过显式的 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 条评论