Python3.9爬虫项目部署:Scrapy+Redis分布式实战案例
Python3.9爬虫项目部署:Scrapy+Redis分布式实战案例
你是不是也遇到过这样的问题?单机爬虫抓取速度太慢,遇到反爬就卡住,数据量一大机器就扛不住。别担心,今天我就带你用Python3.9,结合Scrapy和Redis,搭建一个真正能投入生产的分布式爬虫系统。
这个方案的核心思路很简单:把爬虫任务拆分成多个部分,让多台机器同时干活,通过Redis这个“中央指挥部”来协调工作。这样不仅速度能提升好几倍,还能自动处理反爬、断点续爬这些头疼的问题。
我用的环境是Miniconda-Python3.9镜像,这个环境管理工具特别省心,能帮你创建独立的开发环境,避免各种包版本冲突。下面我就手把手带你从零开始,搭建一个完整的分布式爬虫项目。
1. 环境准备与快速部署
1.1 创建并激活Conda环境
首先,我们需要一个干净的Python3.9环境。打开终端,执行以下命令:
# 创建一个名为scrapy_redis的新环境,指定Python版本为3.9
conda create -n scrapy_redis python=3.9
# 激活这个环境
conda activate scrapy_redis
激活后,你的命令行提示符前面应该会显示(scrapy_redis),这表示你已经在这个环境里了。
1.2 安装必要的包
接下来安装我们需要的所有包。这里我建议用清华的镜像源,速度会快很多:
# 设置pip使用清华镜像源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 安装核心包
pip install scrapy scrapy-redis redis
简单解释一下这几个包是干什么的:
- scrapy:爬虫框架,相当于盖房子的脚手架
- scrapy-redis:让Scrapy能和Redis配合工作的插件
- redis:Python操作Redis的客户端库
安装完成后,可以检查一下版本:
python -c "import scrapy; print(f'Scrapy版本: {scrapy.__version__}')"
2. 项目创建与基础配置
2.1 创建Scrapy项目
现在我们来创建一个Scrapy项目,名字就叫distributed_spider:
# 创建项目
scrapy startproject distributed_spider
# 进入项目目录
cd distributed_spider
创建成功后,你会看到这样的目录结构:
distributed_spider/
├── scrapy.cfg # 项目配置文件
└── distributed_spider/
├── __init__.py
├── items.py # 定义要抓取的数据结构
├── middlewares.py # 中间件(处理请求和响应)
├── pipelines.py # 数据处理管道
├── settings.py # 项目设置
└── spiders/ # 爬虫文件存放目录
└── __init__.py
2.2 配置Redis连接
打开settings.py文件,我们需要修改几个关键配置。找到文件末尾,添加以下内容:
# 启用scrapy-redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 启用去重过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 允许暂停/恢复爬取
SCHEDULER_PERSIST = True
# Redis连接设置(根据你的实际情况修改)
REDIS_HOST = 'localhost' # Redis服务器地址
REDIS_PORT = 6379 # Redis端口
REDIS_PASSWORD = None # 如果有密码就填上
# 使用scrapy-redis的管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
# 其他重要设置
CONCURRENT_REQUESTS = 32 # 并发请求数(根据机器性能调整)
DOWNLOAD_DELAY = 1 # 下载延迟(秒),避免被封
ROBOTSTXT_OBEY = False # 不遵守robots协议(根据实际情况决定)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' # 设置User-Agent
3. 编写分布式爬虫代码
3.1 创建第一个分布式爬虫
在spiders目录下创建一个新文件example_spider.py,我们来写一个抓取书籍信息的示例爬虫:
import scrapy
from scrapy_redis.spiders import RedisSpider
from distributed_spider.items import BookItem
class BookSpider(RedisSpider):
"""分布式书籍爬虫"""
name = 'book_spider' # 爬虫名称,必须唯一
# Redis中的起始URL键名
redis_key = 'book_spider:start_urls'
def __init__(self, *args, **kwargs):
# 动态设置允许的域名
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
super(BookSpider, self).__init__(*args, **kwargs)
def parse(self, response):
"""解析书籍列表页"""
# 提取当前页的所有书籍链接
book_links = response.css('.book-item a::attr(href)').getall()
for link in book_links:
# 构建完整的URL
full_url = response.urljoin(link)
# 将详情页请求加入队列
yield scrapy.Request(
url=full_url,
callback=self.parse_book_detail,
meta={'original_url': full_url}
)
# 提取下一页链接(如果有的话)
next_page = response.css('.next-page::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)
def parse_book_detail(self, response):
"""解析书籍详情页"""
item = BookItem()
# 提取书籍信息
item['title'] = response.css('.book-title::text').get('').strip()
item['author'] = response.css('.author::text').get('').strip()
item['price'] = response.css('.price::text').re_first(r'[\d.]+')
item['description'] = ''.join(response.css('.description *::text').getall()).strip()
item['url'] = response.meta.get('original_url', response.url)
item['crawled_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 记录日志
self.logger.info(f"成功抓取书籍: {item['title']}")
yield item
3.2 定义数据结构
打开items.py文件,定义我们要抓取的数据结构:
import scrapy
class BookItem(scrapy.Item):
"""书籍信息Item"""
title = scrapy.Field() # 书名
author = scrapy.Field() # 作者
price = scrapy.Field() # 价格
description = scrapy.Field() # 描述
url = scrapy.Field() # 来源URL
crawled_time = scrapy.Field() # 抓取时间
3.3 配置数据处理管道
在pipelines.py中,我们可以添加数据处理逻辑。这里我写一个简单的管道,将数据保存到JSON文件:
import json
import os
from datetime import datetime
class JsonWriterPipeline:
"""将数据保存为JSON文件的管道"""
def __init__(self):
# 创建输出目录
self.output_dir = 'output'
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
# 生成带时间戳的文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
self.filename = os.path.join(self.output_dir, f'books_{timestamp}.json')
self.file = open(self.filename, 'w', encoding='utf-8')
self.file.write('[\n') # 开始JSON数组
self.first_item = True
def process_item(self, item, spider):
"""处理每个抓取到的item"""
line = json.dumps(dict(item), ensure_ascii=False)
if not self.first_item:
self.file.write(',\n')
self.file.write(' ' + line)
self.first_item = False
return item
def close_spider(self, spider):
"""爬虫关闭时调用"""
self.file.write('\n]')
self.file.close()
spider.logger.info(f'数据已保存到: {self.filename}')
记得在settings.py中启用这个管道:
ITEM_PIPELINES = {
'distributed_spider.pipelines.JsonWriterPipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
4. 启动Redis并运行爬虫
4.1 启动Redis服务器
如果你还没有安装Redis,可以快速安装一个:
# 使用Docker快速启动Redis(推荐)
docker run -d --name redis-server -p 6379:6379 redis
# 或者使用conda安装(Windows/Mac/Linux通用)
conda install -c conda-forge redis
# 然后启动Redis服务
redis-server
4.2 向Redis添加起始URL
启动Redis后,我们需要向其中添加起始URL。创建一个Python脚本add_start_urls.py:
import redis
import sys
# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 爬虫名称(必须和爬虫中的redis_key对应)
spider_name = 'book_spider'
redis_key = f'{spider_name}:start_urls'
# 添加起始URL(这里以豆瓣读书为例)
start_urls = [
'https://book.douban.com/tag/小说',
'https://book.douban.com/tag/编程',
'https://book.douban.com/tag/历史',
]
# 清空旧的URL(可选)
r.delete(redis_key)
# 添加新的起始URL
for url in start_urls:
r.lpush(redis_key, url)
print(f'已添加: {url}')
print(f'共添加了 {len(start_urls)} 个起始URL到 {redis_key}')
运行这个脚本:
python add_start_urls.py
4.3 启动分布式爬虫
现在可以启动爬虫了。打开多个终端窗口,在每个窗口中执行:
# 激活环境
conda activate scrapy_redis
# 进入项目目录
cd distributed_spider
# 启动爬虫(可以同时启动多个)
scrapy crawl book_spider
你会看到类似这样的输出:
2024-01-15 10:30:15 [scrapy.utils.log] INFO: Scrapy 2.11.0 started
2024-01-15 10:30:15 [scrapy.utils.log] INFO: Versions: lxml 5.1.0...
2024-01-15 10:30:15 [scrapy.crawler] INFO: Overridden settings:
...
2024-01-15 10:30:15 [scrapy_redis.scheduler] INFO: Reading start URLs from redis key 'book_spider:start_urls'
4.4 监控爬虫状态
你可以通过Redis命令行工具监控爬虫状态:
# 连接Redis
redis-cli
# 查看队列中的URL数量
127.0.0.1:6379> LLEN book_spider:start_urls
# 查看去重集合的大小(已抓取的URL数量)
127.0.0.1:6379> ZCARD book_spider:dupefilter
# 查看待处理的请求队列
127.0.0.1:6379> LLEN book_spider:requests
5. 实战技巧与问题解决
5.1 处理反爬策略
在实际爬取中,你可能会遇到反爬。这里分享几个实用的技巧:
# 在settings.py中添加这些配置
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 500,
}
# 使用随机的User-Agent
FAKEUSERAGENT_PROVIDERS = [
'scrapy_fake_useragent.providers.FakeUserAgentProvider',
'scrapy_fake_useragent.providers.FakerProvider',
'scrapy_fake_useragent.providers.FixedUserAgentProvider',
]
# 设置重试策略
RETRY_ENABLED = True
RETRY_TIMES = 3 # 重试次数
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]
# 使用代理IP(如果需要)
# PROXY_LIST = 'path/to/proxy_list.txt'
5.2 分布式部署实战
当你的爬虫需要多台机器协同工作时,可以这样部署:
-
主节点设置:
# 在主节点的settings.py中 REDIS_HOST = '你的Redis服务器IP' REDIS_PORT = 6379 # 所有爬虫节点都连接这个Redis -
启动多个爬虫节点:
# 在机器A上 scrapy crawl book_spider -s JOBDIR=jobs/book_spider_1 # 在机器B上 scrapy crawl book_spider -s JOBDIR=jobs/book_spider_2 # 在机器C上 scrapy crawl book_spider -s JOBDIR=jobs/book_spider_3 -
动态添加任务: 你可以随时向Redis中添加新的URL,所有爬虫节点会自动获取并处理:
import redis r = redis.Redis(host='Redis服务器IP', port=6379) r.lpush('book_spider:start_urls', '新的URL')
5.3 数据存储优化
除了保存到JSON文件,你还可以将数据存储到数据库。这里以MongoDB为例:
# 安装pymongo
pip install pymongo
# 创建MongoDB管道
import pymongo
class MongoPipeline:
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'scrapy_data')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
collection_name = item.__class__.__name__
self.db[collection_name].insert_one(dict(item))
spider.logger.debug(f"数据已保存到MongoDB: {item['title']}")
return item
def close_spider(self, spider):
self.client.close()
在settings.py中添加MongoDB配置:
MONGO_URI = 'mongodb://localhost:27017'
MONGO_DATABASE = 'book_data'
5.4 常见问题解决
问题1:爬虫启动后不工作
- 检查Redis是否正常运行:
redis-cli ping应该返回PONG - 检查起始URL是否已添加到Redis:
redis-cli LLEN book_spider:start_urls - 检查爬虫名称是否匹配
问题2:抓取速度太慢
- 调整并发数:
CONCURRENT_REQUESTS = 100 - 关闭下载延迟:
DOWNLOAD_DELAY = 0 - 增加超时时间:
DOWNLOAD_TIMEOUT = 30
问题3:遇到反爬被封IP
- 增加下载延迟:
DOWNLOAD_DELAY = 2 - 使用代理IP池
- 设置更真实的User-Agent
- 启用自动重试
6. 总结
通过这个实战案例,我们完成了一个完整的Scrapy+Redis分布式爬虫系统。让我帮你回顾一下关键要点:
核心收获:
- 环境搭建:使用Miniconda-Python3.9创建独立环境,避免包冲突
- 项目配置:正确配置Scrapy和scrapy-redis,让它们能协同工作
- 分布式架构:通过Redis实现任务分发和状态同步,多台机器可以同时爬取
- 实战技巧:学会了处理反爬、数据存储、监控调试等实际问题
这个方案的优势:
- 扩展性强:随时可以增加爬虫节点,提升抓取速度
- 容错性好:单个节点故障不影响整体运行
- 管理方便:通过Redis可以集中监控和控制所有爬虫
- 断点续爬:支持暂停和恢复,不怕中途中断
下一步建议:
- 尝试抓取其他网站,修改爬虫解析逻辑
- 添加更多数据处理管道,比如存到MySQL、Elasticsearch等
- 实现动态代理IP,应对更严格的反爬
- 添加监控告警,当爬虫异常时及时通知
- 考虑使用Docker容器化部署,更方便管理
分布式爬虫听起来复杂,但拆解开来就是几个核心组件:Scrapy负责抓取和解析,Redis负责协调和调度,你的代码负责业务逻辑。掌握了这个框架,你就能应对大多数爬虫需求了。
记住,爬虫不仅要能跑起来,还要跑得稳、跑得快、不被封。多实践、多调试,你会越来越熟练的。如果在实践中遇到问题,欢迎随时交流讨论!
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)