用Scrapy打造企业级爬虫:断点续爬+数据去重+反爬策略全配置
断点续爬:小项目用JOBDIR,分布式项目用Scrapy-Redis;数据去重:请求级用Redis去重,Item级用Pipeline+Redis/数据库唯一索引;反爬策略:组合User-Agent池、代理池、下载延迟,动态页面用Scrapy-Splash;监控运维:配置详细日志,用Prometheus+Grafana监控爬虫状态(比如请求成功率、爬取速度);合规性:遵守robots协议,避免爬取敏
之前帮电商公司做商品爬虫时,遇到过三个致命问题:爬了一半服务器断电,所有请求记录全丢,得重爬;重复爬取导致数据库存了上万条重复数据;爬取频率太高被封IP,业务直接停摆。后来用Scrapy的进阶功能一一解决:断点续爬保住了请求进度,数据去重过滤了重复项,反爬策略让爬虫稳定运行——这才是Scrapy作为“企业级爬虫框架”的价值所在。
这篇文章就把Scrapy企业级爬虫的核心配置拆解开,从断点续爬、数据去重到反爬策略,一步一步教你落地。
一、先搭基础:Scrapy项目初始化
先创建一个Scrapy项目(以爬取电商商品为例),后续所有配置都基于这个项目:
# 创建项目
scrapy startproject enterprise_spider
cd enterprise_spider
# 创建爬虫
scrapy genspider goods_spider goods.example.com
项目结构如下:
enterprise_spider/
├── enterprise_spider/
│ ├── __init__.py
│ ├── items.py # 数据模型
│ ├── middlewares.py# 中间件(反爬/代理)
│ ├── pipelines.py # 数据管道(去重/存储)
│ ├── settings.py # 核心配置(断点续爬/反爬)
│ └── spiders/
│ ├── __init__.py
│ └── goods_spider.py # 爬虫逻辑
└── scrapy.cfg
二、断点续爬:断电/崩溃后不重爬
企业级爬虫常要爬几十万甚至几百万条数据,一旦中断就得重爬,浪费大量时间。Scrapy的JOBDIR机制能保存请求队列和进度,实现断点续爬。
1. 配置断点续爬(settings.py)
# 启用断点续爬(指定保存进度的目录)
JOBDIR = 'jobs/goods_spider' # 目录需提前创建:mkdir -p jobs/goods_spider
# 确保请求调度器和去重器支持持久化
SCHEDULER = 'scrapy.core.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
SCHEDULER_PERSIST = True # 保存调度器状态
2. 启动爬虫时加载断点
# 第一次启动(无断点)
scrapy crawl goods_spider
# 中断后重新启动(加载断点)
scrapy crawl goods_spider # 自动读取JOBDIR目录的进度
3. 断点续爬避坑点
- 坑1:JOBDIR目录权限不足——确保Scrapy进程有读写权限(比如用
chmod 777 jobs); - 坑2:修改爬虫逻辑后断点失效——如果改了
start_urls或解析规则,建议清空JOBDIR目录再爬; - 坑3:爬取动态数据(比如商品价格)——断点续爬会保留旧请求,可能爬取过期数据,需定期清理JOBDIR;
- 进阶方案:用Redis做分布式断点续爬(Scrapy-Redis),多机器共享请求队列,后续会讲。
三、数据去重:从请求到Item的全链路去重
企业级爬虫最怕重复数据——不仅浪费存储,还会导致数据分析出错。要做“请求级+Item级”双层去重。
1. 请求级去重(Scrapy内置+Redis扩展)
(1)内置去重(settings.py)
Scrapy默认用RFPDupeFilter基于请求指纹去重,结合JOBDIR持久化:
# 默认去重配置(已在断点续爬中启用)
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
DUPEFILTER_DEBUG = False # 调试时可设为True,查看去重日志
(2)Redis分布式去重(适合多爬虫节点)
如果是分布式爬虫,用Redis存储请求指纹,实现跨节点去重:
# 安装Scrapy-Redis:pip install scrapy-redis
修改settings.py:
# 替换调度器和去重器为Redis版本
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True # 保存Redis中的请求队列
# Redis连接配置
REDIS_URL = 'redis://localhost:6379/0' # 或 REDIS_HOST/REDIS_PORT
2. Item级去重(Pipeline实现)
请求级去重防重复爬取,但同一商品可能通过不同URL爬取(比如商品详情页和促销页),需要Item级去重(基于商品ID等唯一标识)。
修改pipelines.py:
from scrapy.exceptions import DropItem
import redis
import json
class GoodsDuplicatePipeline:
def __init__(self, redis_url):
self.redis = redis.Redis.from_url(redis_url)
self.duplicate_key = 'spider:goods:duplicate' # Redis去重Key
@classmethod
def from_crawler(cls, crawler):
# 从settings获取Redis配置
return cls(redis_url=crawler.settings.get('REDIS_URL'))
def process_item(self, item, spider):
# 用商品ID作为唯一标识
goods_id = item.get('goods_id')
if not goods_id:
raise DropItem('Item缺少goods_id')
# 检查是否已存在
if self.redis.sismember(self.duplicate_key, goods_id):
raise DropItem(f'重复Item:{goods_id}')
# 添加到Redis,设置过期时间(可选,比如7天)
self.redis.sadd(self.duplicate_key, goods_id)
self.redis.expire(self.duplicate_key, 86400*7)
return item
# MySQL/MongoDB存储Pipeline(示例)
class GoodsStoragePipeline:
def process_item(self, item, spider):
# 存储逻辑(略)
return item
在settings.py启用Pipeline:
ITEM_PIPELINES = {
'enterprise_spider.pipelines.GoodsDuplicatePipeline': 100, # 先去重(优先级高)
'enterprise_spider.pipelines.GoodsStoragePipeline': 200, # 后存储
}
3. Item去重避坑点
- 坑1:唯一标识选得不对——必须用商品ID、URL等全局唯一字段,别用标题(可能重复);
- 坑2:Redis内存溢出——用Redis的Set去重时,定期清理过期数据(
expire),或用布隆过滤器; - 坑3:Pipeline顺序错——去重Pipeline的优先级要高于存储Pipeline(数字越小优先级越高)。
四、反爬策略:让爬虫稳定运行不被封
企业级爬虫要长期运行,必须过“反爬”这关。Scrapy通过中间件、请求配置实现反爬策略。
1. 请求头伪装(User-Agent池+Referer)
(1)User-Agent池(middlewares.py)
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random
class RandomUserAgentMiddleware(UserAgentMiddleware):
def __init__(self, user_agent_list):
self.user_agent_list = user_agent_list
@classmethod
def from_crawler(cls, crawler):
# 从settings读取User-Agent列表
return cls(user_agent_list=crawler.settings.get('USER_AGENT_LIST'))
def process_request(self, request, spider):
# 随机选一个User-Agent
user_agent = random.choice(self.user_agent_list)
request.headers['User-Agent'] = user_agent
return None
在settings.py配置:
# 禁用默认User-Agent中间件
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'enterprise_spider.middlewares.RandomUserAgentMiddleware': 543,
}
# User-Agent池(从https://useragents.me/获取最新)
USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0 Safari/537.36',
]
2. 代理IP池整合(middlewares.py)
频繁用同一IP爬取会被封,需用代理IP池轮换IP:
import random
from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
class RandomProxyMiddleware(HttpProxyMiddleware):
def __init__(self, proxy_list):
self.proxy_list = proxy_list
@classmethod
def from_crawler(cls, crawler):
return cls(proxy_list=crawler.settings.get('PROXY_LIST'))
def process_request(self, request, spider):
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
# 代理认证(如果需要)
# request.headers['Proxy-Authorization'] = 'Basic ' + base64.b64encode(b'user:pass').decode()
return None
在settings.py配置:
DOWNLOADER_MIDDLEWARES.update({
'enterprise_spider.middlewares.RandomProxyMiddleware': 544, # 优先级在User-Agent之后
})
# 代理IP池(可从阿布云、快代理等获取)
PROXY_LIST = [
'http://123.45.67.89:8080',
'http://98.76.54.32:8080',
'http://111.222.333.444:8080',
]
3. 下载延迟与自动节流
控制请求频率,避免给服务器压力:
# 下载延迟(每个请求间隔0.5-2秒随机)
DOWNLOAD_DELAY = 1
RANDOMIZE_DOWNLOAD_DELAY = True # 随机延迟,更像真人
# 自动节流(根据服务器响应调整速度)
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 0.5
AUTOTHROTTLE_MAX_DELAY = 5
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # 目标并发数
4. Cookies池与会话保持
部分网站用Cookies识别用户,需维护Cookies池:
# 在middlewares.py中添加Cookies中间件
class RandomCookiesMiddleware:
def __init__(self, cookies_list):
self.cookies_list = cookies_list
@classmethod
def from_crawler(cls, crawler):
return cls(cookies_list=crawler.settings.get('COOKIES_LIST'))
def process_request(self, request, spider):
cookies = random.choice(self.cookies_list)
request.cookies = cookies
return None
在settings.py启用并配置:
DOWNLOADER_MIDDLEWARES.update({
'enterprise_spider.middlewares.RandomCookiesMiddleware': 542,
})
# Cookies池(从浏览器或登录接口获取)
COOKIES_LIST = [
{'sessionid': 'abc123', 'csrf_token': 'def456'},
{'sessionid': 'xyz789', 'csrf_token': 'uvw012'},
]
5. 动态页面爬取(Scrapy-Splash)
爬取JS渲染的页面(比如Vue/React网站),用Scrapy-Splash整合Selenium/Playwright:
# 安装Scrapy-Splash:pip install scrapy-splash
配置settings.py:
SPLASH_URL = 'http://localhost:8050' # Splash服务地址(需启动Splash容器)
DOWNLOADER_MIDDLEWARES.update({
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
})
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
在爬虫中使用Splash:
def start_requests(self):
for url in self.start_urls:
yield SplashRequest(
url,
self.parse,
args={'wait': 2}, # 等待2秒加载JS
endpoint='render.html'
)
6. 反爬避坑点
- 坑1:代理IP质量差——用高匿代理,定期检测代理有效性(比如爬http://httpbin.org/ip验证);
- 坑2:请求频率太规律——开启
RANDOMIZE_DOWNLOAD_DELAY,避免固定间隔; - 坑3:忽略robots.txt——企业爬虫需遵守
ROBOTSTXT_OBEY = True(除非业务特殊); - 坑4:爬取太快导致429——启用
AUTOTHROTTLE,让Scrapy自动调整速度。
五、企业级爬虫完整配置示例(settings.py)
# 基础配置
BOT_NAME = 'enterprise_spider'
SPIDER_MODULES = ['enterprise_spider.spiders']
NEWSPIDER_MODULE = 'enterprise_spider.spiders'
ROBOTSTXT_OBEY = True
# 断点续爬(Redis分布式版)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_URL = 'redis://localhost:6379/0'
# 反爬配置
DOWNLOAD_DELAY = 1
RANDOMIZE_DOWNLOAD_DELAY = True
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 0.5
AUTOTHROTTLE_MAX_DELAY = 5
# User-Agent池
USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
]
# 代理IP池
PROXY_LIST = [
'http://123.45.67.89:8080',
'http://98.76.54.32:8080',
]
# 中间件
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'enterprise_spider.middlewares.RandomUserAgentMiddleware': 543,
'enterprise_spider.middlewares.RandomProxyMiddleware': 544,
'scrapy_splash.SplashMiddleware': 725,
}
# Pipeline(去重+存储)
ITEM_PIPELINES = {
'enterprise_spider.pipelines.GoodsDuplicatePipeline': 100,
'enterprise_spider.pipelines.GoodsStoragePipeline': 200,
}
# 日志配置(企业级监控用)
LOG_LEVEL = 'INFO'
LOG_FILE = 'logs/enterprise_spider.log'
LOG_ROTATION = 'daily' # 按天分割日志
六、企业级爬虫最佳实践总结
- 断点续爬:小项目用
JOBDIR,分布式项目用Scrapy-Redis; - 数据去重:请求级用Redis去重,Item级用Pipeline+Redis/数据库唯一索引;
- 反爬策略:组合User-Agent池、代理池、下载延迟,动态页面用Scrapy-Splash;
- 监控运维:配置详细日志,用Prometheus+Grafana监控爬虫状态(比如请求成功率、爬取速度);
- 合规性:遵守robots协议,避免爬取敏感数据,控制爬取频率。
Scrapy的优势在于“可扩展性”——企业级爬虫不是堆功能,而是根据业务需求组合配置:爬资讯用基础反爬+断点续爬,爬电商需加代理池+动态页面渲染,爬金融数据要强化合规和监控。
如果你的Scrapy项目遇到断点续爬失效、反爬被封等问题,评论区说清楚场景,我会帮你调优——企业级爬虫的核心是“稳定+合规”,Scrapy刚好能满足这两点!
更多推荐



所有评论(0)