Scrapy 版本管理与 API 稳定性:A.B.C 语义、弃用策略与源码级实现解读
本文基于 Scrapy 仓库中的 版本管理文档,系统讲解 Scrapy 的版本号构成规则(A.B.C 三段式)、开发版本的命名约定、API 稳定性承诺以及"至少一年"的弃用策略。同时结合当前仓库源码(版本号加载机制、ScrapyDeprecationWarning 警告体系、create_deprecated_class 弃用类工厂等)与 发布说明,帮助你准确判断何时升级 Scrapy、升级前需要检查哪些弃用项,以及为什么以单下划线开头的成员不应被依赖。
版本号结构:A.B.C 三段式
Scrapy 的版本号由三个数字组成,写作 A.B.C,每一段的含义如下:
| 段 | 名称 | 含义 |
|---|---|---|
| A | 主版本号(major) | 很少变化,代表非常重大的改动 |
| B | 发布号(release) | 包含大量改动,包括新功能,也可能包含破坏向后兼容性的变更,但官方力求将此类情况降到最低 |
| C | 修复号(bugfix) | 仅包含 bug 修复 |
原文档给出的例子是:1.1.1 是 1.1 系列的第一个 bugfix 发布版本(可直接用于生产环境)。
非兼容性变更必须写进发布说明
一个关键承诺是:所有破坏向后兼容性的变更都会在 发布说明(release notes) 中被明确提及,可能需要在升级前给予特别关注。这一点在当前仓库中可以直接验证:docs/news.rst 的每个版本条目下都固定设有 Backward-incompatible changes、Deprecations、Deprecation removals 三个小节。例如最新版本 Scrapy 2.18.0 的条目就明确列出了"移除 zope.interface 运行时接口标记"、"Crawler.stats 等属性在爬取开始前的行为从返回 None 变为抛出 RuntimeError"等破坏性变更;而更旧的版本条目中则能看到 "Removed the --output-format/-t command line option, deprecated in ..." 这类"弃用一年后移除"的记录——这正是下节弃用策略在实践中的体现。
开发版本不遵循三段式
开发版(development release)不使用三段式版本号,而是以 dev 作为后缀发布,例如 1.3dev。原文档还附有一条重要的历史注记:
在 Scrapy 0.* 系列中,Scrapy 曾使用奇数版本号表示开发版(如 0.13 之后的下一个开发版用 0.15 表示)。自 Scrapy 1.0 起不再沿用这一惯例。
与之配套的另一条约定是:从 Scrapy 1.0 开始,所有发布版都应被视为生产就绪(production-ready),不存在"奇数版不稳定"的说法。
当前仓库的版本号是如何实现的
结合源码可以看到,版本号的定义与读取链路非常简洁:
- 版本号本身集中存储在 scrapy/VERSION 文件中,当前仓库中的内容为
2.18.0。 - 包初始化文件 scrapy/init.py 在导入时读取该文件并解析:
__version__ = (pkgutil.get_data(__package__, "VERSION") or b"").decode("ascii").strip()
version_info = tuple(int(v) if v.isdigit() else v for v in __version__.split("."))
version_info 的解析方式值得注意:它把能转成整数的段转成 int,其余部分(比如开发版的 1.3dev 这类非数字段)原样保留为字符串,因此 dev 后缀版本也能被正确解析。
这也解释了前文"开发版不遵循三段式"的说法:VERSION 文件是纯文本,既可以写 2.18.0,也可以写 2.19dev,加载逻辑对两者都兼容。命令行中可通过 scrapy version 命令(对应 scrapy/commands/version.py)随时查看当前安装环境的版本号。
API 稳定性:1.0 之后的核心承诺
API 稳定性(API stability)是 Scrapy 1.0 发布的主要目标之一。原文档给出两条判断准则:
- 单下划线前缀 = 私有。以单个下划线
_开头的函数或方法被视为私有实现,不应依赖其长期稳定。例如scrapy/core/downloader/下的_base_http.py、_base_streaming.py等模块名,以及 scrapy/utils/deprecate.py 中的_clspath()这类辅助函数,都属于此类——它们随时可能在下一个发布版中被重命名或删除。 - "稳定"不等于"完整"。稳定的 API 可以随版本增长出新的方法或功能,但已存在的成员应保持行为不变。换言之,Scrapy 承诺的是"不悄悄破坏",而不是"不扩展"。
弃用策略:至少一年的过渡期
原文档的弃用政策(Deprecation policy)可以概括为三点:
- 过渡期承诺:被弃用的 Scrapy 功能至少会被支持 1 年。举例:若某功能在 2020 年 6 月 15 日发布的版本中被弃用,则该功能在 2021 年 6 月 14 日及之前发布的版本中仍应可用。
- 移除时机:一年后的任何新版本可以移除该弃用功能。
- 移除必公示:每个版本中移除的所有弃用功能都会明确列在 发布说明 中。
在 docs/news.rst 中可以找到大量符合此策略的真实案例,例如:
CrawlerRunner的from_crawler()之外的旧式from_settings()用法被弃用(Deprecations 小节);- 若干在 "deprecated in Scrapy 2.5.0 / 2.8.0 / 2.9.0 / 2.10.0 / 2.11.0" 中弃用的 API,在后续版本的 "Deprecation removals" 小节中被正式移除。
这些记录表明"弃用 → 至少一年支持 → 移除并在发布说明中注明"的流程在项目中是被严格执行的。
源码级实现:弃用机制是如何落地的
文档中"弃用功能"的承诺,在代码层面有一套完整的实现体系,主要由 scrapy/exceptions.py 与 scrapy/utils/deprecate.py 两个文件承载。
自定义警告类别:ScrapyDeprecationWarning
Python 内置的 DeprecationWarning 默认被解释器静默(只在 __main__ 中显示),这会导致弃用提示"用户根本看不到"。Scrapy 为此定义了专门的警告类别:
class ScrapyDeprecationWarning(Warning):
"""Warning category for deprecated features, since the default
:exc:`DeprecationWarning` is silenced.
"""
所有弃用警告统一使用这个类别抛出,用户只需在 settings 或代码中对该类别做一次 warnings.filterwarnings 配置,即可精确控制弃用提示的显示策略,而不会被第三方库(如 Twisted)的弃用噪音干扰——事实上 scrapy/init.py 中就显式忽略了 Twisted 模块的 DeprecationWarning。
弃用类工厂:create_deprecated_class
当 Scrapy 需要重命名一个基类时(比如把 ScrapyClientContextFactory 改名迁移),直接删除旧类会瞬间破坏所有继承它的用户代码。scrapy/utils/deprecate.py 中的 create_deprecated_class() 解决了这个问题,其行为是:
- 旧类名被替换为一个"弃用壳"类,指向新的实现类;
- 用户代码若继承旧类名,会在首次子类化时收到一次性警告("warning only on first subclass, there may be others");
- 直接实例化旧类名时,每次都会收到警告;
- 关键的是,它重写了
__subclasscheck__/__instancecheck__,使得isinstance(sub(), OldName)/issubclass(sub, OldName)对新类的子类依然返回True,保证基于类型判断的既有代码不会立即失效。
该机制在仓库中有真实使用实例,例如 scrapy/core/downloader/contextfactory.py 中的 ScrapyClientContextFactory = create_deprecated_class(...),以及 scrapy/core/downloader/tls.py 中的 ScrapyClientTLSOptions 弃用壳。
路径迁移规则:update_classpath 与 DEPRECATION_RULES
当类被整体移动到新模块时(如从 scrapy.core.downloader.contextfactory 迁往 scrapy.core.downloader.tls),设置项里配置的字符串路径需要被重定向。scrapy/utils/deprecate.py 提供了基于前缀替换的路径迁移表:
DEPRECATION_RULES: list[tuple[str, str]] = []
def update_classpath(path: Any) -> Any:
"""Update a deprecated path from an object with its new location"""
for prefix, replacement in DEPRECATION_RULES:
if isinstance(path, str) and path.startswith(prefix):
new_path = path.replace(prefix, replacement, 1)
warnings.warn(
f"`{path}` class is deprecated, use `{new_path}` instead",
ScrapyDeprecationWarning,
stacklevel=2,
)
return new_path
return path
DEPRECATION_RULES 是一个 (旧前缀, 新前缀) 二元组列表:加载组件时若配置路径命中旧前缀,就自动改写为新路径并抛出 ScrapyDeprecationWarning,明确告知用户应该更新哪个设置。这意味着即使你的 settings.py 中仍写着旧模块路径,Scrapy 也不会直接报 ImportError,而是先"带着警告继续工作"——这正是"弃用至少支持一年"策略在配置加载层的直接体现。
其他弃用辅助函数
scrapy/utils/deprecate.py 中还提供了:
attribute(obj, oldattr, newattr, version):当用户访问已改名的属性时抛出警告(见 L16-L23);method_is_overridden(subclass, base_class, method_name):基于__code__对象同一性判断某方法是否被子类重写,供"仅在你重写了该旧接口时才提示弃用"的场景使用;argument_is_required(func, arg_name)(2.14 新增):判断函数参数是否必填,服务于签名演进时的兼容性检查;warn_on_deprecated_spider_attribute(...)(见 L228-L235):针对 Spider 上被弃用的属性(建议改用Spider.custom_settings或Spider.update_settings())的专项警告。
从源码结构看,这些工具函数共同构成了"弃用期"的完整基础设施:警告有独立类别、类有软迁移壳、路径有自动重定向、属性有访问陷阱——与文档承诺的"一年过渡期 + 发布说明公示"形成闭环。
实战建议:升级 Scrapy 前如何自查
基于文档承诺与仓库实现,升级前可执行如下检查:
- 确认当前与目标版本:
scrapy version(版本号来自 scrapy/VERSION)。 - 通读目标版本的发布说明:重点看 docs/news.rst 中对应版本的
Backward-incompatible changes与Deprecation removals小节。若你的代码使用了被移除项(例如旧的下划线接口、被删掉的命令行选项),必须先改造再升级。 - 打开弃用警告:在测试环境中将
ScrapyDeprecationWarning设为可见(如-W always::scrapy.exceptions.ScrapyDeprecationWarning或warnings.filterwarnings),运行完整爬取流程,收集所有命中项——这些就是你应当在新大版本之前完成的迁移清单。 - 不依赖下划线 API:审查项目代码与设置中是否引用了
_开头的函数/方法或内部模块(如scrapy.utils.deprecate._clspath)。按 API 稳定性约定,这些随时可能变化。 - 区分生产版与开发版:
x.y式发布可视为生产就绪;dev后缀版本仅用于尝鲜和提前反馈问题,不应直接上生产。
小结
Scrapy 的版本管理由三条规则支撑:A.B.C 三段式中"主版本极少动、发布号可能含破坏性变更、修复号仅修 bug";1.0 之后所有版本生产就绪、单下划线 API 不承诺稳定;弃用功能至少支持一年且移除必在发布说明中公示。仓库源码(VERSION 文件、ScrapyDeprecationWarning、deprecate 工具模块)证明这些承诺有具体的实现机制在背后保障。遵循"读发布说明、开弃用警告、不碰下划线 API"三步法,即可在 Scrapy 大版本升级时保持代码库的平稳迁移。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00