Scrapy框架调研文档

161 阅读15分钟

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差异的分述

无界面,全部命令行操作。

image.png pyspider可以WEB 界面编写调试脚本, 起停脚本, 监控执行状态, 查看活动历史, 获取结果产出。

image.png 不支持js渲染, 需要单独下载scrapy-splash(或者使用selenium)。

pyspider支持使用 PhantomJS 对 JavaScript 渲染页面的采集。

对接了 XPath、CSS 选择器、正则匹配。

pyspider可以用任何你喜欢的html解析包(内置pyquery)。

对千万级URL去重支持很好, 采用布隆过滤。

pyspider通过对比数据库中数据去重。

scrapy默认的debug模式信息量太大, 原生框架不容易调试, 可读性略差。

pyspider是界面中就可以编写爬虫,方便调试。

中间件设计,模块之间耦合度低,扩展性强,是一个成熟的框架,完成一个爬虫的时间较长。

pyspider是做简单的爬虫易上手、易批量编写, 但自定义程度相对scrapy低, 社区人数和文档都没有scrapy强。 架构图

image.png 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中调试,加断点等。

image.png 提升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变成一个可以在多个主机上同时运行的分布式爬虫。

image.png 如上图所示,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发送过了,如果没有则继续操作。这个核心的判重功能是这样实现的:

image.png 在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/

实际运行示例

image.png 【注】图中报错部分,表示目前的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页面:

image.png 爬虫运行页面的展示:

image.png 停止一个爬虫

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:”后的项目便没有了:

image.png 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

image.png 添加scrapyd远程服务

image.png 把项目放到projects文件夹下

image.png 打包项目并部署

可以点击上图中的编辑,在线编辑项目,如果项目没有问题,可以点击部署进行打包和部署,在部署之前要打包项目(打包成一个egg文件),可以部署到多台主机。

image.png 运行项目

部署完毕后,可以回到主机管理页面进行任务调度。点击调度即可进入任务管理页面,可以查看当前主机所有任务的运行状态:

image.png 通过点击运行,停止按钮来实现任务的启动和停止,同时可以展开任务条目查看日志详情。

image.png 创建定时任务

image.png 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/

image.png 新建spider

首先创建一个项目,假设输入一个相册:www.douban.com/photos/albu…,然后进入可编辑的界面,点击New spider。

image.png

image.png sample设置抽取规则

image.png 点击New sample,提取要抓取的字段。

image.png 选择工具

当点击一个元素时,自动选择最适合的提取工具。
选中一个元素。
添加一个元素。
移除一个元素。
添加重复的相似元素。
选中“➕”这个工具,点击你想要的元素,比如我选择了图片、名称两个字段。

image.png 默认字段名“fieldx”,可以修改为自己需要的字段名。另外,字段类型已经提供好了相应的数据清洗,比如我只要text ,或者只要number,选中之后右的数据栏就会相应的变化。右边提供了JSON格式化的数据。

image.png 同类选择

这些元素选择完成之后,点击“➖”右边的“列表”选项。

image.png 右侧JSON格式中也可以看出已经选中了同类型的多个元素。

正则获取

image.png 下拉框中的类型可以进行正则获取,如:选择number会只获取数字。

sample优先级

同一个页面可以有多个抽取规则sample,为了应对网页格式不统一、不规范的情况。优先级按照先抽取元素多的sample,再创建时间早的sample。

数据入库

portia没有自带入库方式,可以通过添加pipeline模块实现。

入库至MySQL的pipeline示例代码:

github.com/darkrho/dir…

如何使用pipeline的文档:

doc.scrapy.org/en/latest/t…

项目部署

可以直接在portia内进行项目部署至scrapyd

image.png

导出scrapy代码

image.png 示例

image.png 综述各框架作用

scrapy

爬虫框架。

scrapy-redis

虽然scrapy框架是异步加多线程的,但是我们只能在一台主机上运行,爬取效率还是有限的,scrapy-redis库为我们提供了scrapy分布式的队列,调度器,去重等等功能,有了它,我们就可以将多台主机组合起来,共同完成一个爬取任务,抓取的效率又提高了。

scrapyd

分布式爬虫完成之后,接下来就是代码部署,如果我们有很多主机,那就要逐个登录服务器进行部署,万一代码有所改动..........可以想象,这个过程是多么繁琐。scrapyd是专门用来进行分布式部署的工具,它提供HTTP接口来帮助我们部署,启动,停止,删除爬虫程序,利用它我们可以很方便的完成scrapy爬虫项目的部署。

gerapy

是一个基于Scrapyd,Scrapyd API,Django,Vue.js搭建的分布式爬虫管理框架。简单点说,就是用上述的scrapyd工具是在命令行进行操作,而gerapy将命令行和图形界面进行了对接,我们只需要点击按钮就可完成部署,启动,停止,删除的操作。