之前帮电商公司做商品爬虫时,遇到过三个致命问题:爬了一半服务器断电,所有请求记录全丢,得重爬;重复爬取导致数据库存了上万条重复数据;爬取频率太高被封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'  # 按天分割日志

六、企业级爬虫最佳实践总结

  1. 断点续爬:小项目用JOBDIR,分布式项目用Scrapy-Redis;
  2. 数据去重:请求级用Redis去重,Item级用Pipeline+Redis/数据库唯一索引;
  3. 反爬策略:组合User-Agent池、代理池、下载延迟,动态页面用Scrapy-Splash;
  4. 监控运维:配置详细日志,用Prometheus+Grafana监控爬虫状态(比如请求成功率、爬取速度);
  5. 合规性:遵守robots协议,避免爬取敏感数据,控制爬取频率。

Scrapy的优势在于“可扩展性”——企业级爬虫不是堆功能,而是根据业务需求组合配置:爬资讯用基础反爬+断点续爬,爬电商需加代理池+动态页面渲染,爬金融数据要强化合规和监控。

如果你的Scrapy项目遇到断点续爬失效、反爬被封等问题,评论区说清楚场景,我会帮你调优——企业级爬虫的核心是“稳定+合规”,Scrapy刚好能满足这两点!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐