scrapy框架调研文档
介绍
scrapy 使用了 Twisted异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求 , 是纯python实现的爬虫框架。scrapy的结构组成非常的经典了,分成引擎(scrapy engine),调度器(scheduler),下载器(downloader),解析器(spider),数据持存储、处理(item pipeline)几个部分,其中引擎负责其他部分的通讯,信号、数据传递等。
虽然基本所有爬虫框架都是这么划分的,但是和pyspider比还是有所不同的。scrapy是一个进程集成所有内容,如果是需要分布式,依靠调度器的队列来分布式处理。这种情况就是分布式的每个节点上都包含引擎,下载,解析等。当然可以使用另外的策略,让scrapy只下载,通过其他方式来处理其他的部分。
pyspider则是将调度,下载,解析等模块按进程分开,使用消息队列进行连接,所以说pyspider的各个功能是相对独立的,分布式情况下,各个功能进程可以拆分开部署等,所以结构上分开的。另一个明显的区别是pyspider对不同任务的管理,相对于scrapy能够在更高一个层级的进行管理,pyspider中的webui管理相当于对多个项目进行管理,这一点在scrapy是没有这个层级的东西的,scrapyd配合相关的webui管理项目(比如gerapy)才是这个层级的一个实现。
在项目开发上,pyspider一般是单文件上进行开发,还没有原生中间件机制,如果需要利用模块化的方式重用一些功能,好像无法完全通过webui的那个管理系统实现,需要在终端部署依赖的各种重用功能的文件。所以其实比较偏向于大量简单爬虫项目的集中管理。而scrapy本身可以更方便的扩展,更容易完成复杂的需求。
理论上爬取的过程中都是在同一个线程中的,利用异步的机制实现下载和后处理不堵塞,所以在解析和存储过程中,如果数据库操作时间长,需要使用Twisted的数据库连接进行操作。在中间件等中使用time.sleep这种操作肯定也是会堵塞所有的操作的。
scrapy最好的设计是中间件设计,在下载、解析、存储中提供了可组合的模块化配置,为各种复用提供了帮助。
scrapy默认文件结构
project_name/
scrapy.cfg 项目的主配置文件(部署项目时会使用到)。
project_name/
init.py
items.py 设置数据存储模板,用于结构化数据。
middlewares.py:中间件(爬虫中间件,下载中间件)。
pipelines.py 数据处理行为,如:一般结构化的数据持久化。
settings.py 真正配置文件,如:设置请求头,设置是否遵守robot协议,递归的层数,并发数,延迟下载等。
spiders/ 爬虫目录,如:创建文件,编写爬虫规则
init.py
爬虫1.py
爬虫2.py 原生scrapy与pyspider差异的分述
无界面,全部命令行操作。
pyspider可以WEB 界面编写调试脚本, 起停脚本, 监控执行状态, 查看活动历史, 获取结果产出。
不支持js渲染, 需要单独下载scrapy-splash(或者使用selenium)。
pyspider支持使用 PhantomJS 对 JavaScript 渲染页面的采集。
对接了 XPath、CSS 选择器、正则匹配。
pyspider可以用任何你喜欢的html解析包(内置pyquery)。
对千万级URL去重支持很好, 采用布隆过滤。
pyspider通过对比数据库中数据去重。
scrapy默认的debug模式信息量太大, 原生框架不容易调试, 可读性略差。
pyspider是界面中就可以编写爬虫,方便调试。
中间件设计,模块之间耦合度低,扩展性强,是一个成熟的框架,完成一个爬虫的时间较长。
pyspider是做简单的爬虫易上手、易批量编写, 但自定义程度相对scrapy低, 社区人数和文档都没有scrapy强。 架构图
Scrapy Engine(引擎) : 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
Scheduler(调度器) : 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
Downloader(下载器) :负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理。
Spider(爬虫) :它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。
Item Pipeline(管道) :它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
Downloader Middlewares(下载中间件) :可以当作是一个能自定义扩展下载功能的组件。
Spider Middlewares(Spider中间件) :可以理解为是一个能自定义扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)。 安装
下载scrapy
Nginx
$ pip3 install Scrapy
新建项目
Nginx
$ scrapy startproject mySpider
创建脚本
在当前目录下输入命令,将在mySpider/spider目录下创建一个名为baidunews的爬虫,并指定爬取域的范围:
Ruby
$ scrapy genspider baidunews "news.baidu.com"
编写脚本
Python
*import* scrapy
*from* scrapy *import* cmdline
class BaidunewsSpider(scrapy.Spider):
name = 'baidunews'
allowed_domains = ['news.baidu.com']
start_urls = ['http://news.baidu.com/']
def parse(*self*, *response*):
*for* news_item *in* *response*.xpath('//*[@id="pane-news"]/ul[1]/li'):
title = news_item.xpath('a/text()').extract()
print(title)
*# pass*
# 加入以下语句,可以在IDE中直接调试脚本,无需用命令行
*if* __name__ == "__main__":
args = 'scrapy crawl baidunews'.split()
cmdline.execute(args)
常规启动方式
【注】其中,“baidunews”为爬虫脚本中对name的命名,和文件名无关。
Nginx
$ scrapy crawl baidunews
加入上述示例main方法中的代码后,可以将启动命令通过cmdline.execute()方法运行,然后直接在IDE中调试,加断点等。
提升scrapy爬取效率的方式
概述
在 settings.py 里把 DOWNLOAD_DELAY设置小一点(保证IP不被封的情况下)。
提高并发数( CONCURRENT_REQUESTS ) #也并不是越多越好,CPU调度有上限。
瓶颈在 IO 阻塞,所以很有可能 IO 跑满,但是 CPU 没跑满,所以可以开多个进程来跑。
在 settings.py 里面,可以把单 IP 或者单 domain 的 concurrent 线程数改成 16 或者更高,16 线程对一般的网站来说没问题,而且 scrapy 自己的调度和重试系统可以保证每个页面都成功抓取。
对于定向采集可以用正则取代xpath(或BeautifulSoup)。
加入代理ip。
搭建分布式爬虫。
scrapy的scheduler队列优先级问题
scrapy中的“queuelib.PriorityQueue”是一个优先级队列,使用的是开源的第三方queuelib。它的作用就是对request请求按优先级进行排序,这样我们可以对不同重要性的URL指定优先级(通过设置Request的priority属性)。优先级是一个整数,虽然queuelib使用小的数做为高优化级,但是由于scheduler入队列时取了负值,所以对于我们来说,数值越大优先级越高。
【注】scrapy-redis的scheduler也是数值越大优先级越高。
第三方的queuelib:
Python
class PriorityQueue(object):
"""
PriorityQueue的实现是由一个hash表组成,k是队列等级,v是队列,同时维护一个指针,指向最高的队列等级。
在这里数值越小,代表优先级越大。
* push(obj)入队列
* pop()出队列
* close()关闭
* __len__()
"""
def __init__(self, qfactory, startprios=()):
def push(self, obj, priority=0):
def pop(self):
def close(self):
def __len__(self):
延伸:中间件调用优先级
中间件优先级的调用顺序是在请求时,优先级值越小的优先级越高(更接近引擎),在响应时,优先级值越大的优先级越高(更接近下载器)。
scrapy-redis
简介
Scrapy-Redis是一个基于Redis的Scrapy分布式组件。它改变了scrapy的队列调度,将起始的网址从start_urls里分离出来,改为从redis读取,多个客户端可以同时读取同一个redis,从而实现了分布式的爬虫。
利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生的项目(items)存储以供后续处理使用。scrapy-redis重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。
如上图所示,scrapy-redis在scrapy的架构上增加了redis,基于redis的特性拓展了如下四种组件:Scheduler,Duplication Filter,Item Pipeline,Base Spider。
Scheduler
scrapy改造了python本来的collection.deque(双向队列)形成了自己的Scrapy queue,但是scrapy多个spider不能共享待爬取队列Scrapy queue,即scrapy本身不支持爬虫分布式,scrapy-redis 的解决是把这个Scrapy queue换成redis数据库(也是指redis队列),在同一个redis-server存放要爬取的request,便能让多个spider去同一个数据库里读取。
scrapy中跟“待爬队列”直接相关的就是调度器Scheduler,它负责对新的request进行入列操作(加入Scrapy queue),取出下一个要爬取的request(从Scrapy queue中取出)等操作。它把待爬队列按照优先级建立了一个字典结构,然后根据request中的优先级,来决定该入哪个队列,出列时则按优先级较小的优先出列。为了管理这个比较高级的队列字典,Scheduler需要提供一系列的方法。但是原来的Scheduler已经无法使用,所以使用Scrapy-redis的scheduler组件。
Duplication Filter
scrapy中用集合实现这个request去重功能,scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。这个核心的判重功能是这样实现的:
在scrapy-redis中去重是由Duplication Filter组件来实现的,它通过redis的set不重复的特性,实现了DuplicationFilter去重。scrapy-redis调度器从引擎接受request,将request的指纹存入redis的set检查是否重复,并将不重复的request push写入redis的 request queue。
引擎请求request(Spider发出的)时,调度器从redis的request queue队列里根据优先级pop 出⼀个request 返回给引擎,引擎将此request发给spider处理。
Item Pipeline
引擎将(Spider返回的)爬取到的Item给Item Pipeline,scrapy-redis 的Item Pipeline将爬取到的Item 存入redis的 items queue。修改过Item Pipeline可以很方便的根据 key 从 items queue提取item,从而实现 items processes集群。
Base Spider
不再使用scrapy原有的Spider类,重写的RedisSpider继承了Spider和RedisMixin这两个类,RedisMixin是用来从redis读取url的类。
当我们生成一个Spider继承RedisSpider时,调用setup_redis函数,这个函数会去连接redis数据库,然后会设置signals(信号):一个是当spider空闲时候的signal,会调用spider_idle函数,这个函数调用schedule_next_request函数,保证spider是一直活着的状态,并且抛出DontCloseSpider异常。一个是当抓到一个item时的signal,会调用item_scraped函数,这个函数会调用schedule_next_request函数,获取下一个request。
总结
总结一下scrapy-redis的总体思路:这套组件通过重写scheduler和spider类,实现了调度、spider启动和redis的交互。
实现新的dupefilter和queue类,达到了判重和调度容器与redis的交互,因为每个主机上的爬虫进程都访问同一个redis数据库,所以调度和判重都统一进行统一管理,达到了分布式爬虫的目的;当spider被初始化时,同时会初始化一个对应的scheduler对象,这个调度器对象通过读取settings,配置好自己的调度容器queue和判重工具dupefilter。
每当一个spider产出一个request的时候,scrapy引擎会把这个reuqest递交给这个spider对应的scheduler对象进行调度,scheduler对象通过访问redis对request进行判重,如果不重复就把他添加进redis中的调度器队列里。当调度条件满足时,scheduler对象就从redis的调度器队列中取出一个request发送给spider,让他爬取。
当spider爬取的所有暂时可用url之后,scheduler发现这个spider对应的redis的调度器队列空了,于是触发信号spider_idle,spider收到这个信号之后,直接连接redis读取strart_url池,拿去新的一批url入口,然后再次重复上边的工作。
scrapyd
简介
scrapyd是一个用来部署和运行scrapy项目的应用,由scrapy的开发者开发。其可以通过一个简单的Json API来部署(上传)或者控制你的项目。
scrapyd可以用来管理多个项目,并且每个项目还可以上传多个版本,不过只有最新的版本会被使用。
在安装并开启scrapyd之后,它将会挂起一个服务来监听运行爬虫的请求,并且根据请求为每一个爬虫启用一个进程来运行。scrapyd同样支持同时运行多个进程,进程的数量由max_proc和max_proc_per_cpu选项来限制。
安装scrapyd
【一般安装在服务器端】
Ruby
$ pip3 install scrapyd-client
安装scrapyd-client
【客户端】
Ruby
$ pip3 install scrapyd-client
运行scrapyd
Ruby
$ scrapyd
修改爬虫的scrapy.cfg文件
Makefile
[deploy:demo]
url = http://localhost:6800/
project = mySpider
其中,
[deploy:服务器名随意设置],一般情况用在需要同时发布爬虫到多个目标服务器时,这里是命名为demo,命名可以任意怎么都可以,只要能标识出来项目就可以。
url = [服务器地址]
project = [工程名称]
查看项目spider
Ruby
$ scrapyd-deploy -l
demo http://localhost:6800/
实际运行示例
【注】图中报错部分,表示目前的2.4.1版本,已经不建议使用‘scrapy.utils.http’。(如果安装2.5.0版本的scrapy会提示没有‘scrapy.utils.http’包)
发布爬虫
Ruby
$ scrapyd-deploy <target> -p <project> --version <version>
target就是前面配置文件里deploy后面的target名字。
project 可以随意定义,跟爬虫的工程名字无关。
version自定义版本号,不写的话默认为当前时间戳。
如:
Ruby
$ scrapyd-deploy demo -p mySpider
【注】爬虫目录下不要放无关的py文件,放无关的py文件会导致发布失败,但是当爬虫发布成功后,会在当前目录生成一个setup.py文件,可以删除掉。
(如果后期项目需要打包的话,可以根据自己的需要修改里面的信息,也可以暂时不管它。)
从返回的结果里面,可以看到部署的状态,项目名称,版本号和爬虫个数,以及当前的主机名称。
如:
Python
*# Automatically created by: scrapyd-deploy*
*from* setuptools *import* setup, find_packages
setup(
*name* = 'project',
*version* = '1.0',
*packages* = find_packages(),
*entry_points* = {'scrapy': ['settings = mySpider.settings']},
)
查看状态
到这一步,只是把爬虫项目上传到服务端,并没有启动,先运行命令查看服务端状态:
Ruby
$ curl http://localhost:6800/daemonstatus.json
{"node_name": "localhost", "status": "ok", "pending": 0, "running": 0, "finished": 0}
启动并运行完成一个爬虫后:
Ruby
$ {"node_name": "localhost", "status": "ok", "pending": 0, "running": 0, "finished": 1}
启动爬虫
Ruby
$ curl http://localhost:6800/schedule.json -d project=PROJECT_NAME -d spider=SPIDER_NAME
PROJECT_NAME:爬虫工程的名称
SPIDER_NAME:爬虫的名称
如:
Ruby
$ curl http://localhost:6800/schedule.json -d project=mySpider -d spider=baidunews
{"node_name": "localhost", "status": "ok", "jobid": "4fb2371e071811ecb3e58c8590b215e5"}
web页面:
爬虫运行页面的展示:
停止一个爬虫
Ruby
$ curl http://localhost:6800/cancel.json -d project=PROJECT_NAME -d job=JOB_ID
PROJECT_NAME:爬虫工程的名称
JOB_ID:爬虫的id
列出爬虫
Ruby
$ curl http://localhost:6800/listspiders.json?project=mySpider
{"node_name": "localhost", "status": "ok", "spiders": ["baidunews"]}
删除项目
Ruby
$ curl http://localhost:6800/delproject.json -d project=mySpider
{"node_name": "localhost", "status": "ok"}
这样“Available projects:”后的项目便没有了:
gerapy
简介
将通过scrapy爬虫框架写好的项目整合到Django的web环境进行统一管理的后台。简单理解为一个Admin后台进行控制我们写好的爬虫脚本,进行有针对性的网络数据采集(比如固定时间、固定间隔、或者一次性采集)方便管理,并且对项目进行简单的项目管理,如果了解Django的web开发,后期如果需要如报表功能可以基于这个框架自己增加Admin中的模块功能。
更方便地控制爬虫运行
更直观地查看爬虫状态
更实时地查看爬取结果
更简单地实现项目部署
更统一地实现主机管理
【注】gerapy 0.9.7 要求Django版本django<=2.2.24,>=2.2。
gerapy 下载
Ruby
$ pip3 install gerapy
初始化gerapy
Ruby
$ gerapy init
初始化数据库
要cd 到gerapy目录。
Shell
$ cd gerapy
$ gerapy migrate
会在gerapy目录下生产一个sqlite数据库,同时创建数据表。
创建账号
Ruby
$ gerapy createsuperuser
需要账号进行登录。
运行gerapy服务
Ruby
$ gerapy runserver
gerapy runserver 0.0.0.0:8000 【如果你是在本地,执行 gerapy runserver即可,如果你是在远程上,你就要改成前面这样来执行】
访问gerapy管理界面
浏览器输入:http://127.0.0.1:8000
添加scrapyd远程服务
把项目放到projects文件夹下
打包项目并部署
可以点击上图中的编辑,在线编辑项目,如果项目没有问题,可以点击部署进行打包和部署,在部署之前要打包项目(打包成一个egg文件),可以部署到多台主机。
运行项目
部署完毕后,可以回到主机管理页面进行任务调度。点击调度即可进入任务管理页面,可以查看当前主机所有任务的运行状态:
通过点击运行,停止按钮来实现任务的启动和停止,同时可以展开任务条目查看日志详情。
创建定时任务
portia
简介
portia是一个可视化爬虫工具,它允许你在不需要任何编程知识的情况下可视化地抓取网站。
安装
【注】除docker启动方式之外,还有其他方式。
Shell
$ docker run -i -t --rm -v <PROJECTS_FOLDER>:/app/data/projects:rw -p 9001:9001 scrapinghub/portia
不建议使用官方的最新镜像,有一些问题一直没有解决,使用其他版本比如:
Shell
$ docker run -d -v /Users/bruncedone/Data/portia:/app/slyd/data:rw -p 9001:9001 --name portia scrapinghub/portia:portia-2.0.5
目前使用的是
Shell
$ docker run -v ~/portia_projects:/app/data/projects:rw -p 9001:9001 scrapinghub/portia
操作界面
新建project
打开本地链接:http://127.0.0.1:9001/
新建spider
首先创建一个项目,假设输入一个相册:www.douban.com/photos/albu…,然后进入可编辑的界面,点击New spider。
sample设置抽取规则
点击New sample,提取要抓取的字段。
选择工具
| 当点击一个元素时,自动选择最适合的提取工具。 | |
|---|---|
| 选中一个元素。 | |
| 添加一个元素。 | |
| 移除一个元素。 | |
| 添加重复的相似元素。 | |
| 选中“➕”这个工具,点击你想要的元素,比如我选择了图片、名称两个字段。 |
默认字段名“fieldx”,可以修改为自己需要的字段名。另外,字段类型已经提供好了相应的数据清洗,比如我只要text ,或者只要number,选中之后右的数据栏就会相应的变化。右边提供了JSON格式化的数据。
同类选择
这些元素选择完成之后,点击“➖”右边的“列表”选项。
右侧JSON格式中也可以看出已经选中了同类型的多个元素。
正则获取
下拉框中的类型可以进行正则获取,如:选择number会只获取数字。
sample优先级
同一个页面可以有多个抽取规则sample,为了应对网页格式不统一、不规范的情况。优先级按照先抽取元素多的sample,再创建时间早的sample。
数据入库
portia没有自带入库方式,可以通过添加pipeline模块实现。
入库至MySQL的pipeline示例代码:
如何使用pipeline的文档:
项目部署
可以直接在portia内进行项目部署至scrapyd
导出scrapy代码
示例
综述各框架作用
scrapy
爬虫框架。
scrapy-redis
虽然scrapy框架是异步加多线程的,但是我们只能在一台主机上运行,爬取效率还是有限的,scrapy-redis库为我们提供了scrapy分布式的队列,调度器,去重等等功能,有了它,我们就可以将多台主机组合起来,共同完成一个爬取任务,抓取的效率又提高了。
scrapyd
分布式爬虫完成之后,接下来就是代码部署,如果我们有很多主机,那就要逐个登录服务器进行部署,万一代码有所改动..........可以想象,这个过程是多么繁琐。scrapyd是专门用来进行分布式部署的工具,它提供HTTP接口来帮助我们部署,启动,停止,删除爬虫程序,利用它我们可以很方便的完成scrapy爬虫项目的部署。
gerapy
是一个基于Scrapyd,Scrapyd API,Django,Vue.js搭建的分布式爬虫管理框架。简单点说,就是用上述的scrapyd工具是在命令行进行操作,而gerapy将命令行和图形界面进行了对接,我们只需要点击按钮就可完成部署,启动,停止,删除的操作。