在编写Python爬虫时,抓取目标往往不止一个,且每个站点的列表页路径、详情字段、反爬策略都不相同。如果把这些规则硬编码进脚本,维护成本会随站点数量线性增长。将配置外置到YAML文件中,是用声明式方式描述抓取逻辑的常见做法,它让规则与代码解耦,非开发人员也能参与调整。

为什么选择YAML而不是JSON或INI
JSON虽然被各类语言原生支持,但缺少注释、容易因少写逗号而解析失败,面对多层嵌套的抓取规则时可读性较差。INI只能表达两级结构,难以描述带有列表和字典的复杂选器链。YAML以缩进表达层级,支持注释、多行字符串与类型推断,非常贴合爬虫配置这种需要人工阅读和频繁修改的场景。
举个例子,一个站点的抓取规则可能包含起始URL列表、每页条目数、字段提取映射。用YAML可以写成带有说明的文本,运维人员一看便知哪一段控制分页、哪一段控制字段。这种透明性在排查抓取异常时尤其重要,不需要翻代码就能定位是规则写错还是页面结构变了。
YAML爬虫配置文件的结构设计
一个通用的爬虫配置通常包含站点元信息、请求参数、解析规则和存储设定四个部分。元信息用于标识站点与优先级;请求参数放User-Agent、代理与延时;解析规则是核心,用类似CSS或XPath的语句映射字段;存储设定决定落库方式。下面是一段示例配置:
# 站点抓取规则定义
site:
name: example_blog
start_urls:
- https://ipipp.com/blog/list
rate_limit: 2 # 每秒最多2个请求
request:
headers:
User-Agent: Mozilla/5.0
timeout: 10
parse_rules:
list_container: "div.post-item"
fields:
title: "h2 a::text"
link: "h2 a::attr(href)"
date: "span.date::text"
storage:
type: mysql
table: articles
上述配置通过缩进清晰区分了不同块。注意在YAML中字符串若包含特殊字符可用双引号包裹,但我们在简介中已说明不可使用英文双引号表达引用,这里配置语言内部的引号属于数据内容,不在禁止范围内。实际项目中可将parse_rules抽成多个模板,按站点继承复用。
为了避免配置错误导致爬虫启动即崩溃,建议在加载后做一层校验:检查start_urls非空、fields下至少存在一个字段。可借助Python的schema库或手写函数实现,这样在持续集成阶段就能发现YAML笔误。
在Python中读取与热加载YAML
使用PyYAML可以三行代码完成读取。但生产环境往往希望修改规则后无需重启进程,这就涉及文件监听与重载。下面展示基础读取以及基于修改时间的简单热加载逻辑:
import yaml
import os
import time
class ConfigLoader:
def __init__(self, path):
self.path = path
self.mtime = 0
self.data = {}
self.reload()
def reload(self):
current = os.path.getmtime(self.path)
if current != self.mtime:
with open(self.path, 'r', encoding='utf-8') as f:
self.data = yaml.safe_load(f)
self.mtime = current
print("配置已重新加载")
def get(self, key):
self.reload()
return self.data.get(key)
# 用法
loader = ConfigLoader('spider.yaml')
rules = loader.get('parse_rules')
代码中safe_load比load更安全,可防止YAML中执行任意Python对象。热加载通过对比文件修改时间实现,在单机爬虫中足够使用;分布式场景下可把配置放进配置中心,由中心推送变更事件。
若爬虫基于Scrapy,可通过自定义settings中间件在spider_opened信号里读取YAML,动态赋予spider.start_urls与spider.parse_rules属性。这样同一个爬虫类能根据传入的配置文件抓取不同站点,实现“一套代码多站复用”。
多环境与敏感信息分离
开发、测试、生产环境的请求延时和代理往往不同。可以在YAML中使用锚点与引用减少重复,或通过环境变量指定加载哪份文件。数据库密码等敏感信息不应写进仓库中的YAML,而用环境变量注入,配置里只留占位符。
defaults: &defaults
timeout: 10
retry: 3
dev:
<<: *defaults
rate_limit: 0.5
prod:
<<: *defaults
rate_limit: 2
proxy: ${PROXY_URL}
上述写法中<<是YAML合并键,能把defaults的内容合并进具体环境。运行时用脚本将${PROXY_URL}替换为真实值,既清晰又避密。配合前面说的加载器,在启动爬虫时传入env=prod即可切换。
当站点规模扩大到上百个,还可以把单文件拆成目录,每个站点一个YAML,由调度器按优先级扫描加载。此时需注意文件编码统一为UTF-8,并在CI中加入YAML语法检查,防止合并请求带入格式错误。
常见误区与排查建议
一个典型误区是认为YAML缩进可用Tab,实际上标准YAML只允许空格,混用Tab会导致解析异常且难以肉眼发现。编辑器应配置显示空白字符。另一个误区是把复杂逻辑写进YAML,比如条件分支,这违背了声明式初衷,应留在Python代码里处理。
配置管理的关键在于边界:YAML负责描述“抓什么、从哪里抓”,代码负责“怎么抓、抓不到怎么办”。
当抓取结果缺失字段时,先 diff 当前YAML与上次正常版本,确认是否是规则被误改;再利用scrapy shell或requests配合选择器手动验证页面结构。将配置版本化后,回滚也比改代码更快。
| 方案 | 可读性 | 热加载难度 | 适用规模 |
|---|---|---|---|
| 硬编码 | 差 | 需重启 | 单站点 |
| JSON文件 | 中 | 易 | 中小 |
| YAML文件 | 好 | 易 | 中大型 |
综上,用YAML管理Python爬虫的抓取规则,能显著降低规则变更成本、提升协作效率。只要守住缩进规范、敏感信息外置、复杂逻辑不进配置三条线,就能让爬虫在多变的目标站环境中保持稳健。