数据采集对于一些网站至关重要,在开发这种采集程序时会遇到如下一些问题:
一、单进程采集慢,采集任务的间隔时间无法控制。
二、数据下载部分和分析部分不分离导致可移植性不强,调试困难。
三、采集程序受机器性能瓶颈和网速瓶颈限制。
四、遭受数据源的封锁。
等等。。。。
这就要求采集程序必须足够智能化,有如下几点要求:
一、可以多机器分布运行,以适应大量数据的采集。
二、能够多并发采集,使采集任务的运行周期可控。
三、下载程序和分析程序分离,不仅是程序上的分离,也需要机器上的分离。
四、能够很容易的加入新的采集任务,很容易的调试。
五、对采集页面内容的分析能够模糊匹配。
六、下载时能够调用代理。
七、长期自动维护一个有效的代理列表
经过几次大的改动,我现在设计的基于linux和php的采集程序架构如下:
Snatch(主目录)
|-Lib(类库、函数、配置的目录)
| |-Config.inc.php(主程序变量配置)
| |-OtherConfig.inc.php(其他配置文件若干)
| |-Functions.inc.php(函数文件若干)
| |-Classes.inc.php(类库文件若干)
| |-ClassLocalDB.inc.php(连接本地数据库的操作类)
| |-ClassRemoteDB.inc.php(连接远程数据库的操作类)
| |-ClassLog.inc.php(写下载分析的日志)
|-Paser(分析器程序目录)
| |-WebSite1(针对WebSite1的分析程序目录)
| | |-WebSite1Paser1.php(针对WebSite1的分析程序1)
| | |-WebSite1Paser2.php(针对WebSite1的分析程序2)
| |-WebSite2(针对WebSite2的分析程序目录)
|
|-ProxyWebSite1(分析代理服务器列表的站点1,取得代理服务器地址并入库)
|
|-ProxyWebSite2(分析代理服务器列表的站点2,取得代理服务器地址并入库)
| |-... ...
|-Log(日志目录)
| |-WebSite1.log(WebSite1的下载及数据分析日志)
| |-WebSite2.log(WebSite2的下载及数据分析日志)
| |-... ...
|-Files(下载后的文件保存目录)
|-Main.php(主入口程序,分配下载任务)
|-Assign.php(取得下载任务,分配给Down.php执行)
|-Down.php(进行下载并将下载保存的文件调出来分析)
|-DelOvertimeData.php(清除很老的下载文件)
|-ErrorNotice.php(监控下载程序,在其出错时发信通知相关人)
|-Proxy.php(校验数据库中的代理列表,分析其有效性及连接速度)
|-Fork(钩子程序,使下载和分析并发)
|-Main.sh(封装Main.php,使其在shell下运行不出现包含路径错误)
|-Assign.sh(封装Assign.php)
|-DelOvertimeData.sh(封装DelOvertimeData.php)
|-ErrorNotice.sh(封装ErrorNotice.php)
|-Proxy.sh(封装Proxy.php)
本地数据库表结构如下(简单介绍):
DownloadList表:
`ID` int(10) unsigned NOT NULL auto_increment, 自增ID
`ParentID` int(11) NOT NULL default '0',
父ID,也就是该记录由哪个下载记录衍生来的
`SiteName` char(32) NOT NULL default '', 采集网站的名称或代号
`LocalServerName` char(32) NOT NULL default '',
该采集任务由本地若干台机器里的哪一台来完成
`URL` char(255) NOT NULL default '', 需要下载的数据页地址
`FileName` char(64) NOT NULL default '', 下载后保存的文件名
`FileSize` int(11) NOT NULL default '0', 下载后文件的大小
`Handler` char(64) NOT NULL default '',
分析器的php文件路径,如./Paser/WebSite1/Paser1.php
`Status` enum('Wait','Download','Doing','Done','Dead') NOT NULL default
'Wait', 该任务的状态
`ProxyID` int(11) NOT NULL default '0',
该任务使用的代理ID,为0则不使用代理下载
`Remark` char(100) NOT NULL default '', 备注字段
`WaitAddTime` datetime NOT NULL default '0000-00-00 00:00:00',
记录加入进行等待的时间
`DownloadAddTime` datetime NOT NULL default '0000-00-00 00:00:00',
记录开始下载的时间
`DoingAddTime` datetime NOT NULL default '0000-00-00 00:00:00',
记录开始分析的时间
`DoneAddTime` datetime NOT NULL default '0000-00-00 00:00:00',
记录完成的时间
ProxyList表:
`ID` int(11) NOT NULL auto_increment, 自增ID
`Proxy` char(30) NOT NULL default '', 代理地址,如:
127.0.0.1:8080
`Status` enum('Bad','Good','Perfect') NOT NULL default 'Bad',
该代理状态
`SocketTime` float NOT NULL default '3',
本地连接该代理socket时间
`UsedCount` int(11) NOT NULL default '0', 被使用的次数
`AddTime` datetime NOT NULL default '0000-00-00 00:00:00',
代理被加入列表的时间
`LastTime` datetime NOT NULL default '0000-00-00 00:00:00',
代理被最后一次验证的时间
其它相关表:(略)
介绍几个文件(只介绍,不贴代码):
一、Main.php
<?php
// 1.1 包含文件
require_once('./Lib/Config.inc.php');
require_once('./Lib/ClassLocalDB.inc.php');
require_once... ... //其他一些
// 1.2 声明对象
$LocalDB = new LocalDB();
// 1.3 向DownloadList表里加入下载分析任务
//
注意根据DownloadList表里的相应数据来判断某SiteName的站点是否需要重新加派采集任务
... ...
$LocalDB->close();
?>
二、Assign.php
<?php
// 1.1 包含文件
require_once('./Lib/Config.inc.php');
require_once('./Lib/ClassLocalDB.inc.php');
require_once... ... //其他一些
$LocalDB = new LocalDB();
// 1.2
从DownloadList表里取得`Status`='Wait'且LocalServerName=本机代号的记录
... ...
// 1.3 循环
foreach($Rows as $Row)
{
// 1.3.1 得到一个文件名
do {
$FileName = "./Files/" . md5(rand()) . ".html";
} while(file_exists($FileName));
// 1.3.2 更新记录 update DownloadList,
FileName是$FileName,Status是Download,DownloadAddTime是当前时间
// 1.3.3 调用Down.php下载并进行后续任务
system("./Fork ./Down.php $Row[ID] > /dev/null";
}
$LocalDB->close();
?>
三、Down.php
<?php
// 1.1 包含文件
... ...
$LocalDB = new LocalDB();
// 1.2
利用Down.php程序接收的ID参数,从DownloadList表里取得该记录,调用wget下载
//
也可以调用curl下载或php功能自身来下载,也可以不下载,下载部分由其他机器来做。
//
如果其他机器来下载,则专职下载的机器不运行1.4的分析部分。
//
根据该记录的ProxyID值来判断是否使用代理,或使用什么代理地址来下载
... ...
// 1.3 更新该记录在DownloadList表里的相应数据
// 1.4 分析下载后的数据入库
system("php $data[Handler] $ID \"$data[FileName]\"";
// 1.5 更新DownloadList表里相应数据并关闭数据库连接
... ...
?>
四、Proxy.php (维护有效的代理列表)
方法有两种:
1、对代理地址的代理端口进行socket连接。设定连接超时为3秒(3秒仍旧连不上的代理就别要了)
如果连接上了,计算连接时间,并更新该代理记录的数据SocketTime字段,判断其Status是Bad,
Good,还是Perfect
2、对于非Bad的代理,进行下载文件的实验,如果没使用代理下载的文件和使用代理下载的文件一样,则该代理真实有效。
程序略
多台机器分布式采集:
只有一台运行Main.sh,2分钟运行一次。
其他机器运行Assign.sh,1分钟一次,Assign.php会根据DownloadList表里的LocalServerName字段来取回任务并完成它。
LocalServerName值由Main.php加载采集任务时分配。这个也可以根据各采集机器负载情况来自动调整。
日志:
采集分析的日志写如Log目录,以便方便的查看到是否采集到数据,分析程序是否有效,在出现错误时也可以找到错误的可能地点和时间。
有点复杂,我只写了大体思路,页面分析部分没有涉及,但是也非常重要。
后台管理也没谈。
架起来之后很爽,只要你采集的机器多,建一个qihoo没问题。
我给以前公司做的采集就是这个架构,采集sina, tom,
163等等一共143个频道的内容。
对某几个网站收费数据的精确采集和分析也用的这个(当然,需要模拟登录)。
还是相当稳定的。
1 Comment
分布式系统构想(会不断完善)
Saturday, 22. July 2006, 06:25:22
distributed system, 分布式系统
采用php做web页面显示。
用户通过访问该页面,发出请求,php处理请求,比如形成SQL语句,发给c或者java的守护进程。
守护进程利用其语言优势(系统和数据库方面的特性),进行逻辑层数据处理。
返回结果给php。
php将结果显示给客户。
目前我采用php+javabridge+lucene+mysql,,只是一个很小的应用。
这种系统模型主要面向跨国企业,分支机构繁多的银行机构等。
可以整合内部ERP系统,在线网站,内部CMS等系统整合。
将提供数据库服务的化为数据类,提供身份验证化为验证类,每个服务化为服务类,入口化为入口类。管理请求动向的化为管理类。
任何需求都可以通过一定的入口经过验证类后,管理类分析该需求,转发给服务类,服务类根据特性,调用数据类,并整合信息返回给管理类,管理类将其返回给入口类,最终返回给用户。
每个类别可能是一台服务器也可能是多台服务器。为保证数据安全,快捷,稳定,一般它们都处于一个内网。
对于负载严重的采用一定措施来达到负载均衡。数据做好备份。
以上为服务端。
客户端根据服务类的公开接口,一般为webservice。选择不同开发语言快速开发。
这样的分布式系统可以承受大容量的访问量,并且容易扩展新功能。比如验证类只做了身份验证这块功能,而我需要过滤一些信息。因为这个过滤可能有很多种复杂过滤,为提高性能,我增加一台服务器专门做过滤。
程序这里只需要在转发给管理类之前信息通过它过滤下。可能就是一个异地函数的包含和调用也就是两条语句。
如何充分利用现有语言的特性,来建立一个快速稳定的分布系统。这才是关键。
而不是一味抱紧一种语言,一种系统。