当Scrapy项目规模扩大时,必须拆分为独立模块以避免配置污染与逻辑耦合;需确认版本≥2.11、剥离settings.py直接导入、创建spiders/items/pipelines等标准目录,并通过环境变量或硬编码解耦配置。

当Scrapy项目爬取栏目超过5个、解析逻辑嵌套3层以上、中间件需适配4类反爬策略、且要支持开发/测试/生产三环境部署时,必须将原生单目录结构拆分为独立模块,否则后续新增爬虫将反复修改settings.py、items.py和pipelines.py,导致配置污染与逻辑耦合。
重构前准备:确认项目已满足模块化前提
进入项目根目录,执行 scrapy version 确认版本 ≥ 2.11;检查 scrapy.cfg 中 [settings] 段落的 default = myproject.settings 未被手动改写为绝对路径;【若 settings.py 被直接 import 到 spider 文件中,必须先剥离,否则模块导入会失败】。
删除 myproject/spiders/__init__.py(该文件在旧版Scrapy中存在,但模块化后由 spiders 包自身控制导入,留着反而干扰 Python 包发现机制)。
创建标准模块目录结构
在项目根目录下新建以下目录与空文件:
myproject/ → spiders/ → __init__.py + news_spider.py + mall_spider.pymyproject/ → items/ → __init__.py + base_item.py + product_item.pymyproject/ → pipelines/ → __init__.py + sqlite_pipeline.py + es_pipeline.pymyproject/ → middlewares/ → __init__.py + ua_rotate.py + proxy_retry.pymyproject/ → utils/ → __init__.py + url_filter.py
注意:items/ 和 pipelines/ 目录不能命名为 item/ 或 pipeline/,Scrapy 会因包名冲突报 ImportError: cannot import name 'Item' from 'scrapy.item'。
重写 settings.py 实现配置解耦
方法一:用环境变量动态加载模块路径
在 myproject/settings.py 顶部添加:
import osfrom scrapy.utils.project import get_project_settingsPROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))SPIDER_MODULES = [f'{os.getenv("SPIDER_PKG", "myproject.spiders")}']
方法二:硬编码模块路径(适合单环境快速验证)
将原 SPIDER_MODULES = ['myproject.spiders'] 替换为:
SPIDER_MODULES = ['myproject.spiders']ITEM_PIPELINES = { 'myproject.pipelines.sqlite_pipeline.SQLitePipeline': 300, 'myproject.pipelines.es_pipeline.ElasticsearchPipeline': 400,}DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.ua_rotate.UARotateMiddleware': 543, 'myproject.middlewares.proxy_retry.ProxyRetryMiddleware': 550,}
这一步做完后,Scrapy 就能自动发现 spiders/ 下所有以 Spider 结尾的类,无需再在 settings.py 里逐个声明爬虫名。
定义可复用的 BaseItem 与字段继承
第一步:在 myproject/items/base_item.py 中定义通用字段
import scrapyclass BaseItem(scrapy.Item): url = scrapy.Field() crawl_time = scrapy.Field(serializer=str) source_site = scrapy.Field()
第二步:在 myproject/items/product_item.py 中继承并扩展
from .base_item import BaseItemclass ProductItem(BaseItem): sku_id = scrapy.Field() price = scrapy.Field() stock_status = scrapy.Field()
第三步:在 spider 中直接使用子类,无需重复声明基础字段
def parse(self, response): item = ProductItem() item['url'] = response.url # 自动继承自 BaseItem item['sku_id'] = response.css('.sku::text').get() yield item
编写可插拔式中间件并启用
在 myproject/middlewares/ua_rotate.py 中写入:
import randomfrom scrapy import signalsclass UARotateMiddleware: def __init__(self, ua_list): self.ua_list = ua_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENT_LIST')) def process_request(self, request, spider): ua = random.choice(self.ua_list) request.headers['User-Agent'] = ua
然后在 settings.py 中添加:
USER_AGENT_LIST = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0.0.0',]
这样中间件就能读取配置列表,无需硬编码 UA 字符串——改配置即可生效,不用动代码。
运行指定模块下的某个爬虫
① 进入项目根目录(确保当前路径含 scrapy.cfg)
② 执行命令:scrapy crawl news_spider -s LOG_LEVEL=INFO
③ 若提示 Spider not found,检查 myproject/spiders/news_spider.py 中的 class 名是否以 Spider 结尾,且文件内无语法错误;【Scrapy 只扫描 class 名含 Spider 的类,不看文件名】
④ 成功后,日志中将显示 Spider opened: news_spider,表示模块加载完成。


















