基于 Scrapy-Redis 的分布式爬虫实现

前面我们了解了 Scrapy-Redis 分布式爬虫的基本原理,这一节我们来动手实现一下分布式爬虫的对接。

准备工作

在本节开始之前,请确保已经实现了上一章最后一节针对 https:/antispider7.scrape.center/ 的爬取过程,同时对代理池和账号池有基本了解并能成功运行。

另外需要有至少两台主机,两台主机均安装了 Scrapy 环境和 Scrapy-Redis 库并能正常运行如上 Scrapy 爬虫项目,这些主机需要能够互相访问(比如这几台主机处于同一个局域网)。其中一台主机需要额外装好 Redis 数据库、代理池和账号池并能正常运行,其他主机需要能正常连接到该 Redis 数据库、代理池、账号池。

比如这里我准备了三台主机:这三台主机处于同一个局域网下,其代号和 IP 地址为分别为 A 主机(192.168.2.3)、B 主机(192.168.2.4)、C 主机(192.168.2.5),这三台主机均安装好了 Python 环境并能正常运行如上 Scrapy 爬虫项目,另外,在 A 主机上需要正常运行 Redis 数据库、代理池和账号池,并且 Redis 数据库能够被 B 主机和 C 主机正常连接,同时代理池和账号池的 API 能够被 B 主机和 C 主机正常访问。

接下来我就以这三台主机为示例来配置分布式爬虫,请读者根据实际情况类比此情形进行配置。

验证 Redis 连接

首先在 A 主机上运行 Redis 数据库,该 Redis 数据库没有设置密码:运行在 6379 端口上,因此连接地址就是 192.168.2.3:6379,在 B 主机和 C 主机上可以使用如下代码验证 Redis 能否正常连接:

from redis import strictRedis
conn=StrictRedis(host='192.168.2.3', port=6379)
print(conn.ping())

如果 Redis 能正常连接,那么输出结果如下:

True

如果无法正常连接,则会直接报错,错误信息类似如下:

该步骤需要确保 B 主机和 C 主机能正常连接 A 主机运行的 Redis 数据库。如不能连接成功.请检查 A 主机上的 Redis 服务是否正常运行或者检查防火墙、安全组关设置。

验证代理池和账号池连接

因为运行上一章爬取 https://antispider7.scrape.center/ 的代码需要配合代理池和账号池来进行,而这次我们需要实现分布式爬取,所以代理池和账号池也需要多台主机共享,因此代理池和账号池一定要运行在桌台主机上,这里我们仍然选用 A 主机。所以 A 主机上除了需要运行 Redis 数据库,还需要运行代理池和账号池:二者的 API 需要能被 B 主机和 C 主机正常访问。

对于代理池来说,比如 A 主机上的代理池运行在 5555 端口,则在 B 主机和 C 主机上访问 http://192.168.2.3:5555/random 应该能得到如图 16-3 所示的随机代理。

对于账号池来说,比如 A 主机上的账号池运行在 6777 端口,则在 B 主机和 C 主机上访问 http:/192.168.2.3:6777/antispider7/random 应该能得到如图 16-4 所示的随机 Token,该 Token 用于爬取站点数据。

图略

该步骤需要确保 B 主机和 C 主机都能正常连接 A 主机运行的代理池和账号池服务。如果不能正常访问,请检查 A 主机上的代理池和账号池服务是否正常运行或者检查防火墙、安全组等相关设置。

当然这里代理池和账号池的运行端口可以随意更换,自行配置,只要确保在 A 主机上正常启动代理池和账号池之后,B 主机和 C 主机能访问代理池和账号池的 API 服务即可。

配置 Scrapy-Redis

到此为止,A 主机提供了 Redis 数据库服务、同时提供了代理池和账号池服务,接下来我们只需要在代码里面修改一下调用方式就可以完成分布式爬虫的配置了,整个配置流程非常简单,只需要修改一下 settings.py 配置文件即可。

注意:这些配置需要分别在 A、B、C 主机上配置,内容完全一致,下面分别进行说明。

单机版爬取 https:/antispider7.scrape.center/ 的代码地址为: https://github.com/Python3WebSpider/ScrapyCompositeDemo 。接下来我们在此基础上将该单机版爬虫改写为分布式爬虫。

核心配置

首先最主要的是将调度器的类和去重的类替换为 Scrapy-Redis 提供的类,在 settings.py 里面添加如下配置即可:

SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

Redis 连接配置

接下来配置 Redis 的连接信息,这里有两种配置方式。

第一种方式是通过连接字符串配置。我们可以用 Redis 的地址、端口、密码来构造一个 Redis 连接字符串,支持的连接形式如下所示:

redis://[:password]@host:port/db
rediss://[:password]@host:port/db
unix://[:password]@/path/to/socket.sock?db-db

password 是密码,需要以冒号开头,中括号代表此选项可有可无,host 是 Redis 的地址,port 是运行端口,db 是数据库代号,其值默认是 0。

这里我们配置为 A 主机的 Redis 连接信息,该 Redis 是没有配置密码的,构造这个 Redis 的连接字符串如下所示:

redis://192.168.2.3:6379

直接在 settings.py 里面配置为 REDIS_URL 变量即可:

REDIS_URL = 'redis://192.168.2.3:6379'

第二种配置方式是分项单独配置。这个配置就更加直观了,如根据我的 Redis 连接信息:可以在 settings.py 中配置如下代码:

REDIS_H05T = '192.168.2.3'
REDIS_PORT = 6379
REDIS_PASSWORD = None

这段代码分开配置了 Redis 的地址,端口和密码,密码为空。

注意,如果配置了 REDIS_URL,那么 Scrapy-Redis 将优先使用 REDIS_URL 连接,会覆盖上面的 3 项配置。如果想要分项单独配置,请不要配置 REDIS_URL。

在本项目中,我们选择的是配置 REDIS_URL。

配置调度队列

此项配置是可选的,默认使用 PriorityQueue:如果想要更改配置,可以配置 SCHEDULER_QUEUE_CLASS 变量,如下所示:

SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue:FifoQueue'
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'

以上 3 行任选其一配置,即可切换爬取队列的存储方式。

在本项目中不进行任何配置,我们使用默认配置,即 PriorityQueue。

配置持久化

此配置是可选的,默认是 False。Scrapy-Redis 默认会在爬取全部完成后清空爬取队列和去重指纹集合。

如果不想自动清空爬取队列和去重指纹集合,可以增加如下配置:

SCHEDULER_PERSIST = True

将 SCHEDULER_PERSIST 设置为 True 之后,爬取队列和去重指纹集合不会在爬取完成后自动清空,如果不配置,默认是 False,即自动清空。

值得注意的是,如果强制中断爬虫的运行,爬取队列和去重指纹集合是不会自动清空的。

在本项目中不进行任何配置,我们使用默认配置。

配置重爬

此配置是可选的,默认是 False。如果配置了持久化或者强制中断了爬虫:那么爬取队列和指纹集合不会被清空,爬虫重新启动之后就会接着上次爬取。如果想重新爬取,我们可以配置重爬的选项:

SCHEDULER_FLUSH_ON_START = True

这样将 SCHEDULER_FLUSH_ON_START 设置为 True 之后,爬虫每次启动时,爬取队列和指纹集合都会清空。所以要做分布式爬取,我们必须保证只能清空一次,否则每个爬虫任务在启动时都清空一次,就会把之前的爬取队列清空,势必会影响分布式爬取。

注意,此配置在单机爬取的时候比较方便,分布式爬取不常用此配置。

在本项自中不进行任何配置,我们使用默认配置。

Pipeline 配置

此配置是可选的,默认不启动 Pipeline。Scrapy-Redis 实现了一个存储到 Redis 的 ItemPipeline,如果启用了这个 Pipeline,爬虫会把生成的 Item 存储到 Redis 数据库中。在数据量比较大的情况下,我们一般不会这么做。因为 Redis 是基于内存的,我们利用的是它处理速度快的特性,用它来做存储未免太浪费了,配置如下:

ITEM_PIPELINES = ('sarapy_redis.pipelines.RedisPipeline':300)

本项目不进行任何配置:即不启动 Pipeline。

到此为止,Scrapy-Redis 的配置就完成了。有的选项我们没有配置,但是这些配置在其他 Serapy 项目中可能会用到,要根据具体情况而定。

配置代理池和账号池

在 middlewares.py 中,我们需要修改代理池和账号池的请求地址,之前我们使用的是 localhost,但这里我们需要统一修改为上文提到的 A 主机的地址。

在 ProxyMiddleware 修改 proxypool_url 内容如下:

proxypool_url = 'http://192.168.2.3:5555/random'

在 AuthorizationMiddleware 修改 accountpool_url 内容如下:

accountpool_url = 'http://192.168.2.3:6777/antispider7/random'

注意这里需要根据实际情况修改为你的 A 主机的代理池和账号池地址。

运行

以上修改需要同时在 A、B、C 三台主机上执行,三台主机上的代码是完全一样的。修改完毕后,我们便完成了分布式爬虫的配置了,这样三台主机就共享了同一个 Redis 爬取队列,同时共享了一个代理池和账号池,共同完成协同爬取。

接下来我们就可以运行一下实现分布式爬取了,在每台主机上都执行如下命令:

scrapy crawl book

主机的运行顺序不分先后,每台主机启动了此命令后,就会从配置的 A 主机的 Redis 数据库中调度 Request 并利用 Request 的指纹集合进行去重过滤。同时每台主机占用各自的带宽和处理器,不会互相影响、爬取效率成倍提高。

每台主机运行过程中的输出结果类似这样:

值得注意的是,第一个启动的 Spider 会检测到爬取队列为空,这时候就会调用 start_requests 方法生成初始 Request,后续启动的 Spider 如果检测到爬取队列不为空,就会直接从当前爬取队列中获取 Request 进行爬取,这样,只有第一个 Spider 的 start_requests 方法会被调用,其他 Spider 在队列不为空的情况下是不会调用 start_requests 方法的,于是保证了多个 Spider 的协同爬取。

结果

一段时间后,我们可以用 RedisDesktop 观察 Redis 数据库的信息。这里会出现两个 Key:一个叫作 book:dupefilter,用来储存指纹:另一个叫作 book:requests,即爬取队列:如图 16-5 和图 16-6 所示。

(图略)

随着时间的推移,指纹集合会不断增长,爬取队列会动态变化。

另外值得注意的是,在爬取的过程中,去重指纹集合是不断增长的,如果中途想要中断所有的 Spider 重新进行爬取,需要先停止所有 Spider,然后手动从 Redis 中删除指纹集合和爬取队列,再重新运行。

至此,Scrapy 分布式的配置已全部完成,通过简单的配置,我们就完成了多主机多 Spider 的协同爬取。

总结

本节通过对接 Scrapy-Redis 成功实现了分布式爬虫,实现了多机协同爬取。

本节代码所在的地址为 https://github.com/Python3WebSpider/ScrapyCompositeDemo/tree/scrapy-redis , 注意这里是 scrapy-redis 分支而不是默认分支。