近期由于工作原因,需要一些数据来辅助业务决策,又无法通过外部合作获取,所以使用到了爬虫抓取相关的数据后,进行分析统计。在这个过程中,也看到很多同学爬虫相关的文章,对基础知识和所用到的技术分析得很到位,只是缺乏快速的实战系统搭建指导。
本文将简单归纳网页爬虫所需要的基础知识,着重于实现一套完整可用的小型网页爬取、分析系统,方便大家在有需要时,能够快速搭建系统,以用到实践中去。
关于网页爬虫的定义和用途,想必做技术的都有所了解,这里就不再赘述。目前, 大家使用爬虫的目的除搜索引擎属于无差别爬取外,其他多用于垂直领域或特定网站内容的爬取,本文以特定网站内容爬取作为切入点,当然,也可以应用于垂直领域。
一套合格的网页爬取、分析系统,大致分为:网页抓取、网页分析与链接发现、任务去重与调度、数据预处理与存储、防反爬虫策略、进度展示等几个重要方面。下边逐一做简单归纳介绍。
一、基础知识
(1)网页爬取
网页读取,即读取给定网页的完整内容,包含异步加载的内容,也就是完整地呈现到浏览器窗口的内容。
随着智能手机的普及,网页普遍分为 PC 端 和 移动设备端,由于不同端的网速、流量、设备速度、屏幕大小等原因,移动设备端多采用异步加载的方式来优化用户体验,timeline 类型的无缝翻页就是最佳的例子。这导致常用的 python requests, python urlib, wget, curl 等获取到的网页内容不完整,只有网页的骨架而无内容,内容需要等待 JS 异步加载。
这种问题的解决,我们一般使用带 JS 执行引擎的浏览器驱动来执行网页内的异步加载 JS,解决异步加载问题。常见的解决方案是 selenium 自动化浏览器测试组件配合 chromedriver 或 firfoxdriver 这些有界面浏览器来使用,如果是 linux 服务器命令行下,则可配合 phantomjs 这款无界面浏览器。
python selenium 安装:pip install selenium
phantomjs 下载地址:http://phantomjs.org/download.html
这里附上简单的应用示例代码:
from selenium import webdriver
browser = webdriver.Chrome() # 使用 ChromeDriver,需要安装
browser.get("http://www.baidu.com")
browser.find_element_by_id("kw").send_keys("selenium")
browser.find_element_by_id("su").click()
dir( browser ) # 查看所有属性和方法
print browser.page_source # 网页源码
browser.quit()
(2)网页分析与链接发现
网页分析,即将爬取到的网页内容进行分析,提取需要的内容。**链接发现**,即提取该网页中需要进一步爬取的 URI 地址,或者利用网页内信息构建 URI 地址。
网页分析所针对的内容,大致分为:结构化内容(如 HTML 和 JSON)、半结构化内容(如一条含 JSON 的 JS 语句),非结构化内容(如纯 txt)。(严格意义上说,结构化内容为固定的类似数据库二维表一样的内容,这里仅针对网页内容做适当的分类调整)
针对 HTML ,推荐使用 **pyquery** 进行分析。pyquery 的使用非常简单,用于爬虫时也无需用到高级特性,常用方法如下例所示:
from pyquery import PyQuery as pq
web = pq( "http://www.qq.com" )
print web("title").text() # 打印标题
print web("span#guess").text() # 打印 WWWQQCOM 标签区域的文本
print web("span.undis").text() # 打印 腾讯网 标签区域的文本
print web('a.qqlogo').attr('href') # 打印 腾讯网 的连接内容
针对 JSON,可使用 python 原生的 **json** 模块进行分析。
针对半结构化的内容,则需要特定的分析,一般格式固定,如添加定长的前缀和后缀,但此处无法通用,针对性强,比如含有 JSON 内容,只能固定暴力地将其提取出来再分析。
(3)任务去重与调度
主要是防止网页的重复抓取,比如 A 中包含了 B 的地址,B 中又包含了返回 A 的地址,如果不做去重,则容易造成爬虫在 A 和 B 间死循环的问题。但同时也要注意去重的时间窗口,无限期的去重将导致网页内容无法重新爬取被更新。调度是从系统特性的角度出发,网页爬取的主要耗时是在 网络交互,等待一个网址进行 DNS 解析、请求、返回数据、异步加载完成等,需要几秒甚至更长的时间。小批量任务情况下,简单地使用多线程(thread)、多进程(subprocess)都可以解决问题,python 2.7,也可以使用 twitter 开源的 tornado 框架内的 coroutine 模块做协程,python 3.4 本身也提供了异步 async 关键字。
(4)数据存储与预处理、防反爬虫策略、进度展示
数据预处理,即筛掉无用的内容,并格式化有用数据,降低存储的压力和数据大小,也方便后期分析处理。一般网页抓取时,需要的是展现在用户面前的文字和图片信息,而网页内的 css 样式表、js 代码等则不那么关心,这时,同样推荐使用 pyquery 进行数据提取,简直方便好用(不过 pyquery 存在一些小 bug,标签解析在特定情况下易被 '>' 打断)。防反爬虫则可以搜索下"防爬虫""反爬虫"等关键字,看下实现原理,如果目标网站有,进行针对性破解即可,一般采用随机 User-Agent 和 降频等策略都可以绕过,挂代理换 IP 就会麻烦一些,不过大多数浏览器驱动也都支持。
(5)数据展示
这是额外的说明,爬取到数据后,进行数据统计分析之后,是要用来辅助决策的,要展示给老板或产品看的,如何直观地将成果展示出来呢?这时推荐使用 JS 的 Highcharts 组件进行数据展示。github 上有 Highcharts 的 python 封装,但使用起来比较麻烦,学习还需要耗费不少时间,这里封装了几个常用图表形式的简易 python 接口,如果需要其他类型的图,按照 highcharts 的文档进行和已有代码稍加扩展即可扩充,简单易用。(代码整理上传后贴链接)
二、常见爬虫实现
基础知识介绍完后,我们来搭建实际的系统。不管是自己动手,还是使用做好的框架或者产品,都需要知道自己的目的是什么,要达到什么样的目的,如果想加深知识学习,那无疑自己动手做一套是最合适的,如果是需要快速完成工作,最好是使用现成的框架或产品。
(1)自己动手
如果想自己开发一个的话,作者也是支持的,简单开发将基础组件联动起来,也可以完成任务,虽然坑比较多,尤其是异常环节处理以及编码问题的解决。但话说回来,经验不就是从踩过的坑中学习的吗?如果想在这方面有所作为,自己写或仿写都是必不可少的学习途径。
由于自己开发的起点层次有很多,最底层的可以从自己建 TCP 链接解析 http 协议开始,也可以从利用已有 http 开发库开始(求别说最底层应该从写操作系统或协议栈开始。。。)。
常见的使用 python 开发爬虫的**套路**:
**subrpocess/thread 做多进程任务分发 requests/selenium 网页抓取 pyquery 网页分析加链接生成 db 或 shelve 做结果存储 自定义数据统计分析 matlab/highcharts 做报表图。**
其中,网页获取可以用 urllib 来替代,pyquery 也可以用 beautifulsoup 或正则来替代,但这两者都不推荐,用起来比 requests pyquery 麻烦。
db 常用的就是 sqlite,shelve 可以用来存储 python 对象,如果你的数据分析也是 python 脚本实现,shelve 无疑可以降低不少解析时间。
matlab 做报表图是画报表后生成图片格式。这里也建议使用 highcharts 来做报表,只是 highcharts 生成的结果是展示成网页形式,动态渲染。
在常见的**报表知会**场景中大致分为两种:1、发定期邮件看走势;2、网页展示。如果需要定期邮件,公司内部有提供从 server 发送邮件/rtx 的工具,可以找运维要一下。但是该工具限制无法直接发送图片,通过将邮件做成 html 格式,将图片转为 base64 内嵌进 html 即可。
那么如何将 **highcharts 生成的报表导出图片**呢?新版本的 highcharts 有提供接口,但并不是很好用,因为你的报表也不仅仅是一个图,多个图还要手工拼装,根据邮件客户端的不同,有可能展示的样式也会有变化。 这里我们仍然可以使用 phantomjs 来完成,原理就是使用浏览器对渲染后的页面进行整页截图。实现的原理也比较简单,使用 js 代码,控制浏览器直接以图片形式渲染网页,之后保存。由于该需求反响强烈,phantomjs 官网也提供了解决方案:http://phantomjs.org/screen-capture.html,即下载 rasterize.js,按照下面命令来执行截图。
这个命令的含义是使用 phantomjs 运行 rasterize.js 渲染 my_html.html 并将结果保存到 tmp.png 中。
$ phantomjs rasterize.js ./my_html.html ./tmp.png
生成截图的过程中有**可能遇到的坑**,在这里也提一下,希望后来的同学不会再因为这个问题浪费时间:首先,控制 phantomjs 进行截图的时候,有可能截图不完整,这是因为网页有一个动画绘制的过程(如 highcharts 图表页),可以修改 rasterize.js 内设置的默认 200ms 的超时渲染时间到 5000ms 甚至更长,保证网页加载完后再截图。另外,在公司环境下,爬虫多部署在 server 端的 linux 系统下,服务器系统很少安装字体文件,如果截图出的内容中文字缺失或跟本地预览样式不符,一般就是这个问题了。
(2) scrapy
如果到百度或者谷歌上搜 python 爬虫关键字的话,你肯定会看到有不少人推荐使用 scrapy。scrapy 是不错的爬虫库,或者说是爬虫框架,着重实现了上述的 网页爬取、任务去重调度功能,也提供网页内容分析,不过是 xpath 的形式。其他方面,如 结果存储 和 进度展示 需要开发者自己完成,也没有提供简便的频控防反爬虫策略的功能。
这个框架历史悠久,文档相当的齐全,社区也活跃,不再花大篇幅介绍,使用方面可以看 https://doc.scrapy.org/en/latest/intro/tutorial.html
(3) pyspider
pyspider,是近几年国人开发的一款爬虫产品,之所以提升到产品级别,是因为该框架提供了相当完善的爬虫全流程的功能。从网页爬取,到内容分析,再到频控,定时刷新,数据存储,分布式部署等,做得可圈可点,且相当易用,也是本文重点推荐的系统。
pyspider 简单的二次开发接口,同时自带了一个页面开发调试器。在实际的应用中,配合 phantomjs 进行页面渲染获取动态加载数据非常方便。
这里的我们先看使用方法,体验一下 pyspider 的强大和易用,再来介绍该框架的架构和实现方法。
github: https://github.com/binux/pyspider/
安装:
pip install pyspider
运行:
pyspider
如果已安装 phantomjs,则可使用 $ pyspider all 来配合使用。
访问、开发:
删除
Create project 后,直接点击 project ,即可进入**页面式的开发调试环境**,非常方便。
删除
我们以 douyu 的一个简单例子来介绍下**二次开发代码的含义**
删除
之后点击右上角 save 后,返回首页,修改 project status 和 rate/burst 后, 点击 run 即可执行:
删除
关于 rate/burst,这里是**采用令牌桶做的频控**,这里设置 0.1/5 的含义是:rate = 0.1 每秒发起 0.1 个请求,即 10s 一个请求,耗费一个令牌;burst = 5,最多并发发起 5 个请求,即耗费 5 个令牌,那么也意味着并发后,第 6 个请求,要等待 50s。
另外,pyspider 安装完即可用,默认采用 sqlite 作为数据库,单机部署,使用本机的 phantomjs 和 xmlrpc。单机性能不足以支撑时,也可以支持各模块的分布式部署。如果需要分布式部署,就需要了解下 pyspider 的架构情况,和基本的实现原理。
下面来简要地看一下pyspider 的架构:
删除
从图中可以看出,pyspider 主要的构成模块为 调度器 scheduler,网页爬取 fetcher,数据预处理与链接发现 processor,output 到数据库,还有 web 页面的进度监控、运营。
结合上述谈到的爬虫几大块,浅显地看一下 pyspider 的实现:
webui部分,使用 flask 模块实现。
fetcher部分使用 tornado 的 gen 模块内的 coroutine 做协程,当 fetch_type = 'js' 的时候则链接 phantomjs 进行数据的爬取,否则直接异步爬取。
processor处理阶段,提供了 pyquery 解析对象 repsonse.doc,也可以直接访问页面源码进行解析,链接发现需要用户自己完成,pyquery 也提供了方便的接口 reponse.doc('a') 即可筛选出所有的 标签对象。
任务调度,pyspider 采用数据库来存储需要的任务,taskid = md5sum( URL ) 为 primary key 保存每个任务链接上次执行的时间以及更新时间,以此方式去重和筛选出可执行的任务,放入内部执行队列中,由 fetcher 提取执行。表结构如下:
CREATE TABLE `taskdb_douyu_pro` (
taskid PRIMARY KEY,
project,
url, status,
schedule, fetch, process, track,
lastcrawltime, updatetime
);
CREATE INDEX `status_taskdb_douyu_pro_index` ON `taskdb_douyu_pro` (status);
**数据存储**,也是可配置的,但几十万链接的小量,还是可以用 sqlite 来存储。表结构如下:
CREATE TABLE `resultdb_douyu_pro` (
taskid PRIMARY KEY,
url,
result,
updatetime
);
三、一些遗留的问题及小技巧
1、pyspider 使用 phantomjs 抓取页面时发现,当请求量较大,会存在 phantomjs 有大量链接未关闭,从而停止响应。没有深入定位具体原因,采用暴力定时重启 phantomjs 的方式来解决了,有遇到的同学可以深入定位一下。
2、另外,selenium phantomjs 是可以通过执行 js 代码来操作浏览器动作的,所以遇到翻页自动加载的情况,可以寻找页内的 more 元素传送 click() 事件。
3、如果目标网站量较少,不妨试一下手机端的站点,一般手机端站点为了优化用户体验,都提供了异步加载功能,提供异步加载,则很大可能是使用 ajax 进行 json 明文形式的查询和结果返回,可以通过 chrome 的 F12 或 safari 的响应式设计模式,记录请求 timeline,直接定位到网站自身提供的 restAPI 查询接口,要比解析网页事半功倍了。
作者:大数据首席数据师
链接:https://www.jianshu.com/p/81860b8bec4e