应使用lxml.etree配合cssselect替代硬编码XPath,结合预编译正则、dataclass结构化输出、requests.Session中间件机制,提升抽取器的可维护性、容错性与稳定性。

用 lxml + cssselect 替代硬编码 XPath
硬写 XPath 字符串(比如 '//div[@class="content"]//p[1]/text()')是维护噩梦:页面一改结构就报错,且不同网站的 class 名、嵌套层级千差万别。换成 lxml.etree 配合 CSS selector 能显著提升可读性和局部容错性——lxml 对 HTML 解析健壮,cssselect 支持伪类(如 :nth-of-type(1))、属性模糊匹配(如 [class*="post"]),比 XPath 更贴近前端开发习惯。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 统一用
etree.HTML(html_text, parser=etree.HTMLParser(recover=True))解析,避免因 malformed HTML 导致解析中断 - 把 selector 写进配置字典,例如:
{"title": "h1, .title, article > header h2"},多个 selector 用逗号分隔,取第一个非空结果 - 禁用
//开头的绝对路径,优先用相对、语义化选择器(如article .content p),降低对 DOM 根节点变动的敏感度
用 re.compile() 提前编译正则提取规则
很多字段(发布时间、价格、ID)本质是文本中混杂的结构化信息,靠 CSS 选不到,但又不能每次 re.search() 都重新编译正则——尤其当一个抽取器要跑几百个 URL 时,性能损耗明显。提前编译并缓存是必须的。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 把正则规则集中定义为字典:
{"publish_time": re.compile(r'发布时间[::]\s*(\d{4}-\d{2}-\d{2})')} - 对可能含中文标点、空格、换行的字段(如价格),用
re.DOTALL | re.IGNORECASE标志,避免漏匹配 - 避免在正则中写死具体数字位数(如
\d{4}),改用\d{2,4}或\d+,适应不同站点格式(如 “2023年” vs “23年”)
用 dataclass 定义结构化输出,不拼 dict
返回裸 dict(如 {"title": "...", "content": [...]})看似简单,但后续加字段、校验、序列化(JSON/CSV)时极易出错:键名拼错、类型不一致、缺字段没提示。用 @dataclass 强制字段声明,配合 field(default_factory=list) 或 Optional[str] 类型注解,能在线上运行时快速暴露问题。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每个网站对应一个继承自基类的 dataclass,复用通用字段(
url,timestamp),覆盖特有字段(如电商站加price: float) - 在
__post_init__中做轻量清洗(如 strip 空格、转 int),不要塞业务逻辑 - 导出 JSON 时直接调用
asdict(obj),不用手动构造 dict,避免键名不一致
用 requests.Session() + 可插拔中间件管理请求行为
不同网站对 User-Agent、Cookie、重试策略、延时要求差异极大。如果每个抽取器都重复写 requests.get(..., headers={...}, timeout=10),改起来极其分散。用 Session 统一配置,并设计中间件机制(比如自动处理 302 跳转、注入 Referer、失败后降级用备用 selector),才能真正减少维护量。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 初始化
Session后设置s.headers.update({"User-Agent": "Mozilla/5.0..."})和s.timeout = (3, 7)(连接+读取超时分离) - 把反爬应对逻辑拆成函数(如
add_retry_policy(s),inject_cookies(s, url)),按需组合调用,而不是堆在一个方法里 - 遇到 403/429 时,先检查是否被识别为爬虫(
response.headers.get("X-Robots-Tag")),再决定是 sleep、换 UA,还是跳过该 URL
通用抽取器最难的不是“抽得出来”,而是“抽得稳”——selector 的弹性、正则的宽容度、错误边界的定义(比如空 title 算警告还是致命错误),这些细节不显眼,但决定了三个月后你愿不愿意再打开这个脚本。


















