ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Scrapy项目结构详解:从目录骨架到settings.py核心配置

Scrapy项目结构详解:从目录骨架到settings.py核心配置

很多初学者在接触Scrapy的时候,第一件事往往是照着一篇教程建项目、写爬虫、跑数据,流程走通了就觉得“会用Scrapy了”。但真正到了项目复杂度上来,或者换了个环境、换了个反爬严格的站点,问题就接踵而至:爬取速度上不去、数据重复入库、日志找不到、换个机器跑不了。这些问题绝大多数都出在对Scrapy项目的目录结构和核心配置理解不透彻上——你只是“会跑”,但还没有“会调”。这篇内容我就从实际开发的角度,把Scrapy项目的骨架和settings.py这份核心配置彻底拆开讲清楚,适合刚入门想系统化理解Scrapy的开发者,也适合已经写了一阵子但总觉得配置混乱、无从下手的朋友。

我很早就想写这篇东西。因为我自己在带团队和接手别人项目时,见过太多类似的状况:项目结构随心所欲、配置参数一知半解、出了问题只能靠注释代码暴力排查。其实Scrapy的目录结构是有一套非常成熟的设计逻辑的,settings.py里每个参数也不是凭空出现的,花半小时搞清楚这两块,后面能省下数不清的排查时间。

1. 先搞懂Scrapy项目骨架:目录结构逐层拆解

1.1 项目是怎么来的:scrapy startproject 背后发生了什么

Scrapy项目可以通过一条命令生成。在任意目录下执行:

scrapy startproject myspider

这里“myspider”是项目名,注意它同时也是后续配置中BOT_NAME的默认来源。命令执行完,文件系统里会出现这样一个基础结构:

myspider/ ├── scrapy.cfg └── myspider/ ├── __init__.py ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py └── spiders/ └── __init__.py

这个结构看起来简单,但它就是整个Scrapy项目运转的最小骨架。很多新人会忽略一个细节:外层目录名和内层目录名可以不一样,也可以一样。默认情况下二者同名,实际上外层目录是一个“容器”,内层目录才是真正的Python包。scrapy.cfg中通过module = myspider.settings指定了内层包的路径,后续启动爬虫时Scrapy是靠这个配置去定位项目内部模块的。

我见过有人手动改外层目录名导致scrapy命令无法识别项目的案例,原因就是没搞清楚这份对应关系。所以这里建议:默认结构能不动就不动,硬要改目录名,请同步修改scrapy.cfg中的module指向。

1.2 核心目录与文件的真实职责

逐一拆开看这些文件和目录,各自承担什么职责:

scrapy.cfg:项目入口配置文件。它本身不做业务逻辑,只负责告诉Scrapy命令行工具“项目在哪个Python包里”。一般内容如下:

[settings] default = myspider.settings [deploy] url = http://localhost:6800/

[deploy]段是Scrapy Cloud或自建Scrapyd部署时用到的,本地开发时基本不用管。但注意,如果以后要对接Scrapyd或调度平台,url是需要替换为实际部署地址的。

内层myspider/目录:这是一个标准的Python包,包名就是项目名。它必须包含__init__.py,否则Scrapy无法将其视为模块加载。

items.py:定义数据容器。通俗地说,它就是为“要抓的数据”做一个结构化模板。默认内容非常简洁:

import scrapy class MyspiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() pass

在实际开发中,你会在里面声明每一个字段,比如标题、链接、发布时间、正文等。Scrapy的Item机制有点像字典,但你可以在Field里附加元数据(比如用于Serializer/Validator的自定义属性),这让数据在Pipeline里流转时更可控。

spiders/目录:爬虫本体目录。里面每个.py文件对应一个爬虫,文件数可以任意扩展。spiders目录初始只包含__init__.py,真正开发时你就往里放爬虫文件。一个爬虫类通常继承scrapy.Spider,并定义name、allowed_domains、start_urls/start_requests、parse等方法。

有个常被忽略的细节:每个爬虫的name属性必须全局唯一。Scrapy同一次任务内,是靠name在项目中定位具体爬虫的,如果你在同一个项目里写了两个同名的spider,运行时会直接报错或加载混乱。

pipelines.py:数据管道,处理Item的后续去向。这里做的是“数据清洗、去重、存储”等后期工作。一个典型的Pipeline类至少实现process_item方法。需要强调的是,pipelines.py里可以定义多个Pipeline类,它们的执行顺序和开关完全由settings.py中的ITEM_PIPELINES统一控制。

middlewares.py:中间件集合,包括爬虫中间件和下载器中间件。它是Scrapy里很灵活的一层,主要用来插入自定义逻辑:比如修改请求头、处理代理、捕获异常重试、模拟登录后维持Cookie等。默认文件里只有两个Demo类,实际使用时都会重写或替换。

settings.py:核心配置文件,也是本文后半部分的主角。可以说,爬虫进程启动时几乎所有的运行参数都由它决定。它的默认内容有大量注释,绝大多数时候我们需要做的不是全盘重写,而是精准调整。

1.3 目录结构背后的设计思路

Scrapy把目录拆成这几个模块,不是为了显得“标准”,而是为了让数据处理流程有清晰的职责边界。用一个生活类比来理解:把爬虫系统想象成一家餐厅。

  • spiders/是点菜的顾客,负责决定“菜单”(要抓哪些URL)以及“怎么尝菜”(解析响应);
  • items.py是点菜用的菜单模板,规定了一道菜必须具备哪些要素(字段);
  • pipelines.py是后厨,顾客(spider)把菜(Item)交给后厨,后厨负责清洗(数据清洗)、烹饪(数据加工)、装盘(入库/导出);
  • middlewares.py是餐厅的服务员/安保,在点菜和上菜的路上做各种辅助处理(加调料、验身份、处理突发状况);
  • settings.py就是餐厅的规章制度,说了算出几桌菜(并发数)、上菜间隔多久(下载延迟)、后厨有几条流水线(Pipeline顺序)。

这种分层最直接的好处是,各模块可以独立替换。今天你想把数据从本地JSON改成写入MySQL,只需要改Pipeline和它的配置,完全不用动spider;明天你想给所有请求换UA和代理,也只需要在下载器中间件里动刀。低耦合,这是Scrapy能成为“成熟框架”而不是“脚本”的根本原因。

理解了目录结构,还有个隐藏收益:当你要读别人的项目时,先看目录结构就能判断出这个项目的开发水平。一个组织良好的项目,spiders目录下文件是按业务分组的,pipelines.py里可能拆出多个类,middlewares.py逻辑清晰。反之,所有代码堆在一个文件的“一坨式”项目,后期基本没法维护。

2. 核心配置settings.py:这些参数直接决定爬虫行为和性能

2.1 基础配置:先搞清楚你是谁、能不能抓

先看几项几乎每个项目都要碰的基础配置。

BOT_NAME:默认值是项目名,它不是摆设。Scrapy会用这个值生成User-Agent的默认开头部分(形如BOT_NAME/版本),它也用于很多内部日志标识。很多教程不建议改它,但如果你希望日志里更清晰地识别项目,可以改成更有辨识度的名字,比如BOT_NAME = "zhihu_search_spider"。

USER_AGENT:默认是注释掉的。不设置的话,Scrapy会使用Scrapy/版本号这个默认UA把自己暴露出去。反爬严格的站点几乎是一眼识别,立刻拒绝。实际项目中这必然要改,通常做法是设置一个真实的浏览器UA:

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"

注意,这只是最基础的一层。站点如果按UA、Cookie、TLS指纹、浏览器行为等多维度联合检测,单改UA也没用,那就要上代理、上浏览器指纹伪装、上真实浏览器渲染,这些属于后话,但UA是底线,至少把你的爬虫从“裸奔”状态里救出来。

ROBOTSTXT_OBEY:默认值是True,意味着Scrapy会自动遵守目标站点的robots.txt协议。这是Scrapy的一个安全设计,但对真实采集任务来说,这个默认值经常是个坑。你辛辛苦苦写好爬虫,却发现某些页面返回403或0条数据,打开DEBUG日志一看,原来是Scrapy在访问前主动检查了robots.txt并拒绝。对于合法可控的采集任务,可以把它显式设成False。但这里我要多一句嘴:是否遵守robots.txt不只是技术问题,还涉及合规和道德问题。你在生产环境做数据采集时,至少应该评估目标站点的服务条款和robots策略,不能无脑绕过。

DOWNLOAD_TIMEOUT:默认是180秒。网络异常、目标服务器响应慢时,一个请求能卡很久。并发情况下,大量卡住的请求会拖垮整个抓取任务。实际使用中,我会根据目标站点的响应情况把它调低到20-30秒,让超时请求快速失败并进入重试流程。

2.2 并发与限速:别让服务器以为你在攻击它

这部分是性能调优的核心,直接决定你的爬虫是“快而稳”还是“快而崩”。

CONCURRENT_REQUESTS:默认16,表示Scrapy同时发出的最大请求数。提高它能显著提升抓取速度,但会增大对目标服务器的压力,也更容易触发反爬。这个参数不是越大越好,我一般在1到32之间调整,起步用4,稳定后再逐步加压观察。

CONCURRENT_REQUESTS_PER_DOMAIN:默认8,限制对同一个域名的最大并发数。如果只抓一个站点,这个参数比全局并发更值得关注。

DOWNLOAD_DELAY:默认0,表示相邻请求之间不等待。设置为0.5到2秒之间是常规操作,尤其针对小型站点和个人博客,一个礼貌的延时能大幅降低被封概率。这个参数是按域名生效的,并不影响不同域名之间的并发。

AUTOTHROTTLE_ENABLED:Scrapy内置的自动限速功能,默认False。建议生产环境开启:

AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 1.0 AUTOTHROTTLE_MAX_DELAY = 10.0 AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0

自动限速的工作原理是:Scrapy持续监测服务器的响应时间和请求延迟,动态调整每个请求的延时,以把并发维持在目标值附近。它比固定DOWNLOAD_DELAY更智能:服务器响应慢时自动放慢,响应快时自动提速。开启后,DOWNLOAD_DELAY会被忽略(其实是作为初始延时参考),所以二选一即可。

这里有个实操心得:如果你不确定目标站点能承受多大的抓取压力,最稳妥的做法是开启AUTOTHROTTLE并把目标并发设置成1.0到3.0,让Scrapy自己去试水温。很多“爬着爬着被封”的案例,都是因为手动设置了一个激进的DOWNLOAD_DELAY=0反而没有保护作用。

2.3 中间件、管道与扩展:链路配置的枢纽

在settings.py中,中间件和管道的配置格式非常相似,都是“优先级数字”字典。

ITEM_PIPELINES:控制数据管道的启用顺序。例如:

ITEM_PIPELINES = { "myspider.pipelines.DuplicatesPipeline": 100, "myspider.pipelines.MySqlPipeline": 300, }

后面的数字决定执行顺序:数字越小越先执行。可以把它想象成一条流水线上工人的先后次序。如果去重管道先处理,那么重复数据根本不会流到MySQL管道,从源头就截断了。这个优先级的安排需要按照业务逻辑仔细排布:清洗在前、去重在中间、入库在最后,各步骤井井有条。

这里我必须要提一个新人很容易踩的坑:光在pipelines.py里写了类,忘记在ITEM_PIPELINES里注册,数据完全不会经过管道,Item在spider里yield出去后就直接丢了,日志里还看不到任何错误。你不信可以试试,yield掉之后去数据库里查,一条记录都不会有。

DOWNLOADER_MIDDLEWARES:下载器中间件,在请求发送前和响应返回后插入逻辑。典型用途是代理切换、UA轮换。常见配置方式:

DOWNLOADER_MIDDLEWARES = { "myspider.middlewares.ProxyMiddleware": 400, "myspider.middlewares.RandomUserAgentMiddleware": 500, "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None, }

注意这里的None用法很关键:它表示关闭某个Scrapy内置中间件。比如你实现了自己的UA中间件,就不想让默认的UserAgentMiddleware和它冲突。中间件优先级数字范围通常是0-1000,数值越大越靠近下载器端,不同的处理阶段影响相对位置,这个要看官方文档的详细图示,但实际开发中只要不和其他中间件冲突,大致范围即可。

SPIDER_MIDDLEWARES:爬虫中间件,在Item进入Pipeline前、异常产生、请求生成等时机介入。它的使用频率比下载器中间件低,但在某些特殊场景(比如统一给Spider返回的Item打标记、统一处理Spider内部异常)用起来非常顺手。一般不需要频繁改动,默认值即可。

2.4 存储输出与日志:运行痕迹管理

FEEDS:Scrapy 2.x以后推荐的导出配置方式,可以直接将Item输出到JSON、CSV、JsonLines等格式。例如:

FEEDS = { "data/items.json": {"format": "json", "encoding": "utf8", "overwrite": True}, }

使用FEEDS配置的好处是,你可以在不写Pipeline的情况下就把数据导出来,适合快速调试。但注意,如果你同时配置了FEEDS和Pipeline(比如写数据库),数据会被处理两次。前期调试阶段我想推荐JsonLines格式,每行一条记录,追加写、不易损坏,比一个巨大的JSON文件好处理多了。如果数据量大,我强烈建议写文件用JsonLines而不是JSON。

LOG_LEVEL:默认是DEBUG。实际跑大规模任务时,日志量会非常大,线上环境建议设为INFO或WARNING。把日志输出到文件并配合Loguru或自定义Formatter,有助于后期排查:

LOG_LEVEL = "INFO" LOG_FILE = "logs/spider.log" LOG_FILE_APPEND = True

设置了LOG_FILE之后,控制台仍然有输出,同时文件里会记录一份完整日志。任务跑崩了,回头翻日志是最高效的排查手段。我见过太多同事在任务挂掉之后,一脸茫然地打开控制台找历史输出,其实全在日志文件里,这就是配置习惯的问题。

RETRY_ENABLED:默认True,允许请求失败后自动重试。配合如下参数更清晰:

RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]

给429加上重试,是应对限流的基本操作。当服务器返回429时,说明并发或频率超出了它的容忍范围,重试策略配合Retry-After头的读取会更友好。

DOWNLOADER_CLIENT_TLS_CERT、DOWNLOADER_CLIENT_TLS_KEY:如果目标站点需要双向TLS认证(一些政务接口或内部系统),这里配置客户端证书和私钥路径,属于相对少见的定制项,但真需要时你会庆幸Scrapy留了这些接口。

3. 从目录到配置:一次完整的爬虫开发流程

理解目录和配置光靠看是不够的。这一节我把一个极简但功能完整的爬虫开发流程走一遍,带着你从新建项目到配置跑通。

3.1 定义Item并打通链路

假设我们要采集某开放式文章平台一类的站点,标题、链接、发布时间三个字段就够。

首先打开items.py,写上字段定义:

import scrapy class ArticleItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() publish_time = scrapy.Field()

然后在spiders目录里新建article_spider.py:

import scrapy from myspider.items import ArticleItem class ArticleSpider(scrapy.Spider): name = "article" allowed_domains = ["example.com"] # 换用实际目标域名 start_urls = ["https://example.com/articles"] def parse(self, response): # 假设每个文章条目在 li.article-item 中 for sel in response.css("li.article-item"): item = ArticleItem() item["title"] = sel.css("h2.title::text").get() item["url"] = response.urljoin(sel.css("a::attr(href)").get()) item["publish_time"] = sel.css("span.date::text").get() yield item

此时如果你立刻运行,且在没有任何Pipeline的情况下,这些Item默认会被Scrapy的日志系统以DEBUG级别打印出来,其实数据并没有“丢失”,只是你当时没看出来。这也是我刚接触Scrapy时困惑的地方:数据明明抓到了,但没保存到任何地方。

3.2 配置Pipeline实现数据入库

现在定义一个最简单的“存到JSON文件”的Pipeline,放在pipelines.py中:

import json class JsonWriterPipeline: def open_spider(self, spider): self.file = open("output.jsonl", "w", encoding="utf-8") def close_spider(self, spider): self.file.close() def process_item(self, item, spider): line = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(line) return item

然后在settings.py中注册:

ITEM_PIPELINES = { "myspider.pipelines.JsonWriterPipeline": 300, }

这里优先级给了300,不是必须的,但保留足够大的数字空间是为了以后加前置处理步骤时不用重新排号。比如你想在写入文件之前先去重,就可以新增一个优先级100的去重管道,无需改现有配置。

此时运行scrapy crawl article,你会看到数据从响应进入Item,然后流经Pipeline,最终写入output.jsonl。这条链路的完整度已经够了。

3.3 测试与调整配置

有了基础链路之后,接下来就是根据抓取效果反复调settings.py。我常用的做法是:

  • 先以最小并发跑一次,确认字段解析正确、Pipeline写入正常,再把FEEDS或入库逻辑打开;
  • 逐步调高并发和降低延时,观察错误率和响应码。出现429或503,就说明频率太高,需要回退或开启自动限速;
  • 用scrapy shell URL做单页调试,这会省掉一次次跑完整爬虫的时间。
  • 写一个user_agent列表并实现轮换中间件,应对简单UA检测。

调试时还有一个实用技巧:在spider的parse里多打几条DEBUG日志,打印当前URL和解析到的字段数。我自己在开发新爬虫时一定会加:

self.logger.info("Parsed %s items from %s", len(results), response.url)

这样跑起来就能实时看到每个页面有没有解析出数据,而不必等到输出文件里去数行数。

4. 常见配置陷阱与排查技巧实录

和很多框架一样,Scrapy的配置坑并不在“参数多”上,而在“你以为你配了,但实际没生效”上。这些坑我都踩过,整理成速查表,希望能帮你绕过。

4.1 配置了Pipeline却没生效

这是出现频率最高的问题。大多数情况不是优先级数字写错,而是压根没有在settings.py里注册,或者拼写错误被当成另一个类。解决办法:先在Pipeline的__init__或process_item里加一个print或logger,跑一下看有没有输出。没有输出,说明Pipeline没有被加载,马上检查ITEM_PIPELINES中的字典键是否完全正确,以及项目名包名是否匹配。

还有一种隐蔽情况:你在settings里写的是"pipelines.JsonWriterPipeline": 300,但项目包名是myspider,正确的写法应该是"myspider.pipelines.JsonWriterPipeline": 300。Scrapy加载模块时严格基于Python包路径,少了一层包名就会静默失败。我建议统一用"项目名.模块名.类名"这样的完整路径。

4.2 ROBOTSTXT_OBEY和中间件“失灵”

有同学设置了自定义UserAgent,但看到实际请求还是Scrapy/2.x。原因就是刚才提到的,你自定义的UA中间件优先级和内置的UserAgentMiddleware冲突,或者没有在DOWNLOADER_MIDDLEWARES里把内置UA中间件关掉。解决办法:

DOWNLOADER_MIDDLEWARES = { "myspider.middlewares.CustomUserAgentMiddleware": 400, "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None, }

把内置的置为None,才会真正“禁用”它。

另外一个容易被忽视的:如果启用了AUTOTHROTTLE,DOWNLOAD_DELAY的配置是失效的。两者同时配置,你期望的“延时2秒”不会实现,别在看日志时傻眼。需要明确二选一:要么手动限速,要么自动限速。

4.3 数据重复和编码乱码

没有去重、反复爬取都指向一个问题:Pipeline缺少去重逻辑,或者本来有去重但优先级排错位置,导致后置的去重管道再也没机会执行。经验之谈,去重Pipeline要尽量排在前面(数字小),优先拦截重复数据,省去后面入数据库的开销。

编码乱码则大多与页面声明的charset和Response的encoding不一致有关。一个稳妥的兜底方案是,在Parse解析文本时,如果发现中文乱码,可以强制设置response编码:

if response.encoding and response.encoding.lower() != "utf-8": response = response.replace(encoding="utf-8")

这个操作在文本数据混杂多个编码来源时特别管用。

4.4 一条命令调试配置的小技巧

日常开发时,用一个很小的脚本直接导入settings验证当前配置项,比一遍遍跑爬虫快得多:

from scrapy.utils.project import get_project_settings settings = get_project_settings() print(settings.get("CONCURRENT_REQUESTS")) print(settings.get("ITEM_PIPELINES"))

这段Python命令在项目根目录执行,要注意的是,get_project_settings读取的是scrapy.cfg中default指定的settings模块,而不是你环境变量里任意的一个。如果你想临时覆盖某些配置,可以这样:

scrapy crawl article -s CONCURRENT_REQUESTS=1 -s DOWNLOAD_DELAY=5

命令行中带-s key=value的配置优先级最高,会临时覆盖settings.py里的值,适合快速测试某一组参数的真实效果——这招我在做限速测试时反复用。注意,命令行指定的值必须是字符串或能被Scrapy解析的简单值,对于字典类(如ITEM_PIPELINES)不适用。

4.5 关于日志和异常排查

如果你设置了LOG_FILE,又用Loguru之类的外部日志库同时写日志,可能会出现日志内容被截断或格式混乱的情况。最好的方式是用Scrapy自带的logging配置直接对接,或者干脆外部日志库完全接管,不要两边同时写。

还有一个我常推荐的做法:在Pipeline的process_item中包一层try/except,把单条数据处理的异常捕获并记录,而不是让错误直接打挂整个爬虫。数据采集任务里经常出现某条记录字段不完整、类型不匹配的情况,激进式崩溃会导致整个任务失败。温和处理单条脏数据,同时统计出错了多少条,在结束后统一检查,比一崩到底靠谱得多。

def process_item(self, item, spider): try: self.save(item) except Exception as e: spider.logger.error("Failed to save item: %s, error: %s", item, e) raise DropItem(f"Save failed: {e}") return item

注意,这里raise DropItem之后,后面的Pipeline不会再处理这条数据,因此如果你有多个Pipeline,这个异常处理放哪一步很关键。我的习惯是把“最终入库”放最后,之前负责清洗、去重,它们异常了可以抛DropItem直接丢弃,入库步骤只处理最终合格品。

说到这里顺便提一句Scrapy官方约定:Pipeline里处理不了的Item不是返回None,而是抛DropItem异常。很多人没用过这个机制,实际上它是链路上最优雅的“淘汰”方式。

5. 目录和配置的进阶扩充:中大型项目的组织思路

5.1 按业务分组:spiders目录的演进

单一爬虫项目里spiders目录下就是几个独立文件。但项目变大之后,爬虫数量可能达到几十个甚至上百个,按业务或平台分组是必然趋势。Scrapy支持子目录组织爬虫,前提是每个爬虫类的name要保持唯一,并且子目录要有__init__.py。

例如:

spiders/ ├── __init__.py ├── news/ │ ├── __init__.py │ ├── sina_news.py │ └── sohu_news.py ├── forums/ │ ├── __init__.py │ ├── tianya_forum.py │ └── 51job_forum.py

这样目录结构在大型项目里更容易维护。需要注意的坑是:爬虫类名可以重复,只要name不重复。Scrapy在加载爬虫时是以name为准的,别人看你的代码时却可能被重复的类名误导,所以还是建议类名和name保持一致。

5.2 把公共逻辑上提:utils和middleware分层

当多个爬虫共享某些工具函数,比如URL标准格式化、时间字段解析、代理切换逻辑等,应该把它们单独放到一个utils/目录,而不是每个爬虫里复制粘贴。目录结构可以演进为:

myspider/ ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py ├── spiders/ └── utils/ ├── __init__.py ├── common.py ├── url_helper.py └── time_converter.py

这种抽取在项目初期看似乎是“过度设计”,但当爬虫数量超过五个,重复代码带来的维护痛苦会成倍放大。我在实际项目里深有体会:同样的时间格式化逻辑,三个爬虫写了三份,目标站改版后日期格式变了,就得跑到三个文件里改。抽到utils里统一改一处,一切归位。

5.3 配置分层:不同环境不同settings

很多项目需要在本机测试和生产环境使用不同的配置参数,比如数据库地址、下载延时、并发数。Scrapy本身没有内置多环境配置支持,但可以用一个简单的方式实现:新建settings_dev.py和settings_prod.py,在scrapy.cfg里用环境变量指定默认settings。

[settings] default = myspider.settings

然后在命令行上指定加载哪个配置:

scrapy crawl article -s PROJECT_ENV=prod

settings.py顶部读取环境变量来切换数据库参数。这种简单方案比引入复杂的配置中心要轻量得多,也够大多数中小项目使用。关键是让团队成员形成习惯:开发环境永远不要连生产数据库,从配置源头就隔离。

6. 写在最后的实操心得

目录结构和配置这些内容,本质上没有太多“高深”的东西,但就是这些基础细节,决定了你的爬虫项目是“能跑”还是“好维护、好扩展、好排查”。

我自己这些年用Scrapy最大的体会就是:不要等到项目跑挂了才去翻配置,也不要照着一份模板配置直接梭哈。每加一个字段、一个Pipeline、一个中间件,都要想清楚它会出现在哪个环节、影响哪条链路。多看日志、多打印关键步骤、多用命令行临时覆盖参数做对比测试。这些习惯比任何“进阶技巧”都管用。

还有一个实操层面的建议:新建项目时,先在settings.py里把LOG_LEVEL设为DEBUG、FEEDS写成JsonLines格式、AUTOTHROTTLE暂时关掉,用最朴素的方式把数据抓下来看一遍。所有控制都掌握在手里之后,再逐步追加并发、开中间件、上Pipeline,每一步都能明确知道是自己哪个动作影响了结果。

另外,现在很多团队已经逐步把爬虫代码搬进容器里编排运行,Scrapy目录结构依然适用,但settings.py里的并发、超时、日志路径等参数,往往需要根据容器资源进行调整。这部分没有标准答案,只能依靠压测数据和线上监控来调,但理解每个参数的含义永远是第一步。

最后分享一个我一直在用的检查清单:上线新爬虫前,依次确认ROBOTSTXT_OBEY/UA设置是否符合预期,DOWNLOAD_TIMEOUT和RETRY策略是否已覆盖目标站点响应特性,ITEM_PIPELINES里的组件顺序是否符合“清洗-去重-存储”逻辑,日志是否落到固定文件,以及FEEDS或入库通道是否连通,最后再用-s方式临时压一次并发看看错误率。做一轮检查总比上线后盯着监控屏手忙脚乱强。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进