PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI。采用Python语言编写,分布式架构,支持多种数据库后端,强大的WebUI支持脚本编辑器,任务监视器,项目管理器以及结果查看器。
pyspider是作者之前做的一个爬虫架构的开源化实现。主要的功能需求是:
1.抓取、更新调度多站点的特定的页面
2.需要对页面进行结构化信息提取
3.灵活可扩展,稳定可监控
而这也是绝大多数python爬虫的需求 —— 定向抓取,结构化化解析。但是面对结构迥异的各种网站,单一的抓取模式并不一定能满足,灵活的抓取控制是必须的。为了达到这个目的,单纯的配置文件往往不够灵活,于是,通过脚本去控制抓取是我最后的选择。
而去重调度,队列,抓取,异常处理,监控等功能作为框架,提供给抓取脚本,并保证灵活性。最后加上web的编辑调试环境,以及web任务监控,即成为了这套框架。
pyspider的设计基础是:以python脚本驱动的抓取环模型爬虫
通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性
通过web化的脚本编写、调试环境。web展现调度状态
抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展
以上来自pyspider官网
描述 | pyspider | scrapy |
---|---|---|
上手程度 | ★★脚本编写规则简单,立刻就能上手,but开发文档少,要干啥得自己看源代码 | ★文档全,要学习的相关知识较多 |
开发便利程度 | 只能在web端开发,界面简单,没有任何编辑功能(高亮,行号等),碰到报错在第1XX行时,慢慢找吧 (T。T) | ★可以使用任意IDE/编辑器进行编辑 |
数据处理 | 默认保存为json格式到数据库,可以复写on_result函数自行处理 | 需要自己保存数据 |
动态解析 | ★★可以直接调用PhamtomJS(需安装)动态解析网站,神器! | 只能人为解析动态加载的方式 |
自定义程度 | 自定义程度相对scrapy低,插件功能非常弱,需要自己编写调用 | ★★★预定义了众多接口(如中间件接口,默认Headers和cookies) |
URL去重 | PySpider用的是数据库来去重 | ★★★对千万级URL去重支持很好,采用布隆过滤(海量大数据处理单机方案) |
运行调度 | ★★★WEB界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出 | 需要使用scrapyd另外部署 |
开发时页面解析验证 | ★★★可以直接run要解析的网页任务,直接实时验证并获取结果,相当便利有木有 | 验证解析规则时,要开启爬虫(或自己保存页面源代码)进行验证,不方便 |
开发时验证爬取流程 | ★★★可以直接通过webUI查看任务的进行的步骤,实时验证 | 调试不方便,需要开启爬虫后查看DEBUG信息 |
运行报错时 | 报错影响执行 | 基于twisted框架,报错不会影响其他任务的进行 |
总结:
pyspider |
---|
轻量级框架,脚本代码和默认提取的数据都保存在数据库(用户根目录下)中,它提供了webUI更便于调度和开发的调试,可以搭建专门的爬虫服务器,远程开发和调试,但是功能有限,需要自己编写插件(如使用大量代理IP时),且定制插件需固定在指定绝对路径下,不方便项目的打包移植 |
scrapy |
---|
体积庞大,功能丰富,自定义程度高,可以根据需求任意修改,且便于移植,但开发时调试不如pyspider方便 |
描述 | 相同点 |
---|---|
运行模式 | parse->yield request->pipeline流程是所有爬虫的固有模式。 |
页面解析 | 都内置了XPath,CSS解析方法,也可以通过Lxml,BeautifulSoup等库解析,怎么处理页面是你自己决定的 |
数据处理 | 抓到了数据要怎么处理也是你自己决定的。 |
界面在线示例(下文实例可在本页执行)
开发手册
下载方式:
pip install pyspider
依赖:
apt-get install python python-dev python-distribute python-pip libcurl4-openssl-dev libxml2-dev libxslt1-dev python-lxml
status说明:
下面对区块进行说明:
整个页面分为两栏,左边是爬取页面预览区域,右边是代码编写区域。
左侧绿色区域:这个请求对应的 JSON 变量,在 PySpider 中,每个请求都有与之对应的 JSON 变量,包括回调函数,方法名,请求链接,请求数据等等。
绿色区域右上角Run:点击右上角的 run 按钮,就会执行这个请求,可以在左边的蓝色区域出现请求的结果。
左下 enable css selector helper: 抓取页面之后,点击此按钮,可以方便地获取页面中某个元素的 CSS 选择器。
左下 web: 即抓取的页面的实时预览图。
左下 html: 抓取页面的 HTML 代码。
左下 follows: 如果当前抓取方法中又新建了爬取请求,那么接下来的请求就会出现在 follows 里。
左下 messages: 爬取过程中输出的一些信息。
右侧代码区域: 你可以在右侧区域书写代码,并点击右上角的 Save 按钮保存。
右上 WebDAV Mode: 打开调试模式,左侧最大化,便于观察调试。
主要函数解释:
@config(priority=2) 这个是优先级设置。数字越小越先执行。
以爬取财经网财经热评为例:启动函数on_start:
@every(minutes=24 * 60)
def on_start(self):
self.crawl('http://comments.caijing.com.cn/hottopics/', callback=self.index_page)
调用内置函数self.crawl,生成response对象,传给回调函数index_page
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
# 选择所有href属性以http开头的a标签
for each in response.doc('a[href^="http"]').items():
# 判断该标签是否是《新闻评论》详情的url
if re.match('http://comments.caijing.com.cn/\d+', each.attr.href, re.U):
# 再次发送请求,回调函数为最终的解析函数
self.crawl(each.attr.href, callback=self.detail_page)
进入解析方法,解析页面。response.etree是内置方法,它生成一个html的etree对象
@config(priority=2)
def detail_page(self, response):
data = {
"url": response.url,
# 调用xpath提取title
"title": response.etree.xpath('//*[@id="cont_title"]/text()')[0]
}
return data
全部代码:
import re
import json
class Handler(BaseHandler):
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('http://comments.caijing.com.cn/hottopics/', callback=self.index_page, force_update=True)
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a[href^="http"]').items():
if re.match('http://comments.caijing.com.cn/\d+', each.attr.href, re.U):
self.crawl(each.attr.href, callback=self.detail_page)
elif re.match('http://comments.caijing.com.cn/hottopics/\d+.shtml', each.attr.href, re.U):
self.crawl(each.attr.href, callback=self.index_page, force_update=True)
@config(priority=2)
def detail_page(self, response):
etree = response.etree
print type(etree.xpath('//*[@id="cont_title"]/text()')[0].encode('utf-8'))
data = {
"url": response.url,
"title": etree.xpath('//*[@id="cont_title"]/text()')[0].encode('utf-8'),
"content":'\n'.join(etree.xpath('//*[@id="the_content"]/p/text()')).encode('utf-8'),
"post_time":etree.xpath('//*[@id="pubtime_baidu"]/text()')[0].encode('utf-8'),
"source":etree.xpath('//span[@id="source_baidu"]//text()')[0].encode('utf-8'),
}
return data
def on_result(self, result):
if not result:
return
sql = SQL()
sql.replace('article', result)