最近上面让爬一些数据,以为可以尝试学习使用python,奈何最终让我用PHP实现,减少开发时间…然后在网上找,果然已经有大佬分享----phpspider,还有一些php爬虫框架,但是考虑到是国人开发,代码可读性高(全中文注释,注释清晰,详细),并且在github上有近3K的star,最终决定使用phpspider。
作者很有意思,代码注释风趣,并且各种情况都做了兼容,只需要轻轻配置一下,不需要我们做太多的操作,甚至还给出了好几个demo供我们研究。
简单学习xpath后,过了一遍文档,开始尝试。我需要爬的是一个商标分类,群组,商品的网站,从源码和js里分析后各页面后,添加配置:
$config = [
'name'=>'商标类',
'tasknum'=>1,//进程数
'log_show'=>true,//是否显示日志
'domains'=>[//定义爬取域
'test.com',
'www.test.com',
],
'scan_urls'=>[//入口页面
'http://www.test.com/ncl/01.html'
],
'content_url_regexes'=>[//内容
'http://www.test.com/category/search.html\?intCls=\d+'
],
'max_try'=>5,//最大尝试次数
'db_config'=>[
'host'=>'127.0.0.1',
'user'=>'root',
'pass'=>'123',
'port'=>3306,
'name'=>'dataname',
],
];
经过分析,我爬取的页面的列表没有实际意义,因此没有写list_url_regexes配置,有列表可以增加此配置,可大幅提升爬虫效率!
商标分类有45,每个类下又有n个群组,每个群组下有多个商品,分析页面后得知,45类有有45个url,因此需要把这些url添加到带爬队列中:
$spider = new phpspider($config);
$spider->on_start = function($phpspider){
$db_config = $phpspider->get_config("db_config");
db::set_connect('default',$db_config);
db::init_mysql();
for($i=1;$i<46;$i++){
if($i<10){
$i = '0'.$i;
}
$phpspider->add_url('http://www.test.com/category/search.html?intCls='.$i);
}
};
顺便把数据库也初始化,方便后面操作。
再来写群组和商品的爬去,经分析,发现每个页面的群组都是相同的div和class,并且商品也在这个div里,保存在商品中,添加配置fileds
'fields'=>[
[
'name'=>'parent',
'selector'=>"//div[@class='result-data']",
]
]
这样就能拿到所有同一分类下(同一页面),不同群组的div和包含的商品的html,查看文档,文档上介绍说,fields中可以使用children,是一个树形结构(并且可以一直加下去,有兴趣可查看源码,代码中是递归操作),结合文档示例,修改fields配置:
'fields'=>[
[
'name'=>'parent',
'selector'=>"//div[@class='result-data']",
'children'=>[
[
'name'=>'group',
'selector'=>'//div[@class="result-data-top"]',
],
[
'name'=>'item',
'selector'=>'//input[@type="checkbox"]/@value',
]
]
]
]
尝试运行后,发现只有每个类型(页面)都只拿到1个群组(group),和1个商品(item)!并且不加children时得到的html都是包含所有需要数据的!
怀疑是xpath不熟悉,写错了,根据文档的运行前测试后得知,xpath没有问题!
然后开始查看源码,最终发现这个判断
if (is_array($values))
{
if ($repeated)
{
$fields[$conf['name']] = $values;
}
else
{
$fields[$conf['name']] = $values[0];
}
}
else
{
$fields[$conf['name']] = $values;
}
好吧,原来在fields里还可以添加配置,repeated,否则就只会返回数组中的第一个,默认是false,修改为true,文档中也有描述,怪自己忽略忘记了这个配置…再次修改配置
'fields'=>[
[
'name'=>'parent',
'selector'=>"//div[@class='result-data']",
'repeated' => true,
'children'=>[
[
'name'=>'group',
'selector'=>'//div[@class="result-data-top"]',
'repeated' => true,
],
[
'name'=>'item',
'selector'=>'//input[@type="checkbox"]/@value',
'repeated' => true,
]
]
]
]
运行尝试,OK
现在开始处理数据,因为在我的数据库中,群组和商品是分开的,现在爬出来的数据,显然不适合参照demo中自动保存,因此需要自己修改处理!
查找文档,果然大佬已经准备好了,on_extract_page这个回调函数可以在抽取完后进一步处理字段:
$spider->on_extract_field = function($filename,$data){
if($filename == 'parent'){
/*中间就是处理代码,$data就是抽取出来的数据,数组格式*/
}
return $data;
};
完成!
全程不过100行左右代码,配置就将近一半了,不需要管底层逻辑,发送请求等等,算是懒人福音,以后有机会学习一下底层实现,此框架推荐!