一、概要
1.分布式爬虫概念
分布式爬虫是一种利用多台机器协同工作的网络爬虫系统,通过任务分解、并行处理和资源共享,高效抓取并处理海量网页数据。其核心在于将爬取任务分配到不同节点,避免单点性能瓶颈,同时支持动态扩展和容错机制,适用于大规模数据采集场景。
在爬虫众多框架中,scrapy-redis是最常用的实现分布式爬虫的框架,它区别于之前我们学习的scrapy,它将Redis作为他的调度器队列,所有的请求对象均从Redis获取,Redis的set集合会自动对所有请求对象进行去重,从实现了多台机器同时爬取和断点续爬的功能。
2.分布式爬虫流程
下图形象的展示了scrapy-redis的工作流程,可以看到和我们之前的scrapy的spider,schedule换了个方向,spider被放到了最上方。
下面这个图更加形象的展示四台主机同时连接Redis服务器进行爬虫的过程。当然我们也可以扩充到多台主机的情况。在scrapy-redis中,Redis服务端被称作master主机,而把用于跑爬虫程序的机器称为slave。
简要概括,scrapy-redis的工作流程如下:
- 调度器共享:所有爬虫实例共享同一个 Redis 中的请求队列,新发现的 URL 会被统一存入队列。
- 请求去重:利用 Redis 的集合结构(Set)对请求 URL 进行全局去重,避免重复爬取。
- 任务分配:各爬虫从 Redis 队列中竞争获取待爬 URL,实现负载均衡。
- 数据存储:爬取到的数据可统一存入 Redis 或其他存储系统(如数据库)。
- 断点续传:Redis 持久化保存爬取进度,支持暂停后恢复。
二、分布式爬虫准备
1.准备工作
我们准备三台电脑进行分布式爬取,(忽略图中的centos),其中远程Ubuntu16.04作为我们的Redis服务器(master端),不负责爬取,只负责数据的分配。而使用Windows和另外一台本地虚拟机Ubuntu16.04作为slaver端进行爬虫的执行。
2.Master端配置
首先我们在远程ubuntu服务器上安装Redis,命令如下:
sudo apt install redis-server
接下来我们尝试打开redis-cli,并ping,如果返回pong就说明安装成功了。命令如下:
redis-cli
ping
接下来我们要配置Redis,允许远程主机连接。操作如下:
sudo vi /etc/redis/redis.conf
第一步,将bind 127.0.0.1注释掉。
第二步,将protected mode从yes改成no。关闭保护模式。
然后按ESC,输入:wq!保存,输入下面命令重启Redis即可。
sudo systemctl restart redis-server
然后准备好Redis的主机IP地址,后续连接需要要到。
如果你是远程Redis的话,不要忘了关闭防火墙,并开放相应的6379端口。本地的话就不用了。
3.Slaver端配置
首先我们给两台slaver分别安装上scrapy和scrapy-redis,注意scrapy安装2.8以下的版本,这样才能和scrapy-redis兼容,scrapy-redis安装最新版本即可。输入以下命令即可安装。
注意!!!这里一定要重新安装twisted旧版本,因为新版本的scrapy不兼容。所以下图的命令还要再加一个。
pip install twisted==22.10.0
安装完成之后接下来我们测试一下两台slaver能否正常连接Redis服务器。
可以直接在slaver端装上Redis的图形化界面控制端,用来测试Redis的连接。当然,如果是ubuntu,也可以通过运行Python脚本来测试是否能够正常连接Redis。这里给出方法:
- 对于Windows
安装包:https://wwql.lanzout.com/b052o0lmf,密码:3rsj。
链接中包含redis图形化界面和redis安装包,我们只需要安装图形化界面即可。我这里临时测试,就不下载工具了,直接用PyCharm的自带的Redis链接工具连接了。如果你对PyCharm不太熟悉,建议直接使用图形化界面。
下面给出在PyCharm中链接Redis的方法:
- 对于Ubuntu
我们采用一个简单的Python脚本来测试是否能够正常连接Redis服务器。脚本如下,注意主机和其它选项要改成你自己的:
import redis
def test_redis_connection(host, port=6379, password=None, db=0):
"""
测试Redis连接
:param host: Redis服务器地址
:param port: 端口 (默认6379)
:param password: 密码 (可选)
:param db: 数据库编号 (默认0)
"""
try:
# 创建Redis连接
r = redis.Redis(
host=host,
port=port,
password=password,
db=db,
socket_timeout=3 # 3秒超时
)
# 发送PING命令测试连接
response = r.ping()
if response:
print(f"✅ 连接成功!Redis版本: {r.info()['redis_version']}")
return True
except Exception as e:
print(f"❌ 连接失败: {str(e)}")
return False
if __name__ == "__main__":
# 在此处填写Redis连接信息
HOST = "your_redis_host" # 替换为实际主机IP/域名
PORT = 6379 # 替换为实际端口
PASSWORD = None # 如有密码,替换为字符串(如: "mypassword")
DB = 0 # 替换为实际数据库编号
# 执行测试
test_redis_connection(
host=HOST,
port=PORT,
password=PASSWORD,
db=DB
)
三、分布式爬虫实现
scrapy-redis的分布式爬虫实现步骤可以总结为以下几步:
- 编写scrapy爬虫项目
- 更换默认spider父类为Redis类
- 修改配置文件setting.py
- 设置起始url的key 为[top250:start_urls]
1.爬虫生成
这里我们用最经典的豆瓣电影top250作为示例。首先在控制台输入下面命令生成爬虫项目。
#创建项目
scrapy startproject douban
cd douban
#创建Spider
scrapy genspider movie_rank movie.douban.com
生成的项目目录如下:
接着这里我直接复制使用之前已经写好了的豆瓣top250爬虫逻辑,建议先提前将基础爬虫逻辑用scrapy写完,然后修改成scrapy-redis即可。
具体的代码逻辑可以直接参考我之前的文章,scrapy爬取豆瓣250。
2.修改父类
打开movie_rank.py,导入Scrapy_redis的redis-spider,然后让爬虫类继承它。
3.修改setting.py
打开settings.py文件,将以下设置覆盖掉,使用scrapy-redis的配置,不建议将管道添加Redis,因为Redis适合存取常用数据。Redis地址要记得换成自己的。
# Obey robots.txt rules
# 关闭遵守协议
ROBOTSTXT_OBEY = False
# 持久化配置
SCHEDULER_PERSIST = True
# 使用scrapy - redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# scrapy - redis指纹过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# redis链接地址
REDIS_URL ='redis://127.0.0.1:6379/0'
# 任务的优先级别
SCHEDULER_QUEUE_CLASS ='scrapy_redis.queue.PriorityQueue'
# 存放的管道,可以开通第一个,数据会保存到redis,但不建议这么做
ITEM_PIPELINES = {
# 'scrapy_redis.pipelines.RedisPipeline': 300,
'douban.pipelines.DoubanPipeline': 301,
}
# 请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q = 0.9,*/*;q = 0.8',
'Accept - Language': 'en',
'User - Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
4.设置起始url的key
因为我们的爬取URL都是要从Redis获取的,所有我们不需要手动设置起始的URL,也就是START_URL,在scrapy-Redis中,只需要设置redis-key,scrapy就会自动去Redis中寻找起始的url。操作如下:
# 前面是爬虫名,后面是固定start_urls
redis_key = 'movie_rank:start_urls'
5.slaver1测试
接下来我们可以尝试运行slaver1的scrapy-redis项目。
scrapy crawl movie_rank
可以看到卡在了此处,因为程序正在等待我们向Redis中添加起始URL。
我们现在尝试使用Python脚本在Redis中添加Redis_key作为起始URL。
import redis
# 连接到Redis
r = redis.Redis(host='127.0.0.1', port=6379)
# 添加起始URL
r.lpush('movie_rank:start_urls', 'https://book.douban.com/top250')
可以看到爬虫自动读取到了我们添加的URL,不过抓取失败了,因为400错误码,我们的请求头有问题。在scrapy-Redis中,默认的请求头是scrapy-Redis,我们需要修改它。
我们在中间件中重写下载器中间件的请求头,示例代码如下:
class DoubanDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
request.headers.setdefault('User-Agent', self.get_random_ua())
print(request.headers)
return None
def get_random_ua(self):
# 随机 User-Agent 列表
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'
]
return random.choice(user_agents)
然后再设置中启用中间件,再次运行可以看到成功拿到数据了。
6.slaver2测试
接下来我们来调试slaver2,slaver2使用的是Ubuntu16.04操作系统,所以需要提前准备好Python解释器和其他配置项。这里就不多加赘述了。
然后将我们之前的项目全部打包放入任意位置。不需要做任何修改,直接运行爬虫即可。这里就不再演示,大家可以自己尝试一下。