㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~ ㊙️本期爬虫难度指数:⭐⭐⭐⭐⭐(专家级) 🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。
全文目录:
-
- 🌟 开篇语
- 0️⃣ 前言(Preface)
- 1️⃣ 摘要(Abstract)
- 2️⃣ 背景与需求(Why)
-
- 2.1 为什么要爬这些数据
- 2.2 目标字段清单
- 2.3 本文采用的实战目标
- 3️⃣ 合规与注意事项
-
- 3.1 关于 robots.txt
- 3.2 控制访问频率
- 3.3 不采集敏感信息
- 3.4 正则审计不是事实校验
- 4️⃣ 技术选型与整体流程(What/How)
-
- 4.1 静态、动态、API 三种采集方式
- 4.2 整体流程
- 4.3 为什么选择 requests、BeautifulSoup、PyYAML、SQLite
- 5️⃣ 环境准备与依赖安装
-
- 5.1 Python 版本
- 5.2 安装依赖
- 5.3 推荐项目结构
- 6️⃣ 核心实现:请求层(Fetcher)
-
- 6.1 配置文件:config/crawler.yaml
- 6.2 请求层代码:regex_audit/fetcher.py
- 7️⃣ 核心实现:解析层(Parser)
-
- 7.1 示例 HTML:examples/source_page.html
- 7.2 数据模型:regex_audit/models.py
- 7.3 清洗层:regex_audit/cleaner.py
- 7.4 解析层代码:regex_audit/parser.py
- 8️⃣ 数据存储与导出(Storage)
-
- 8.1 字段映射表
-
- raw_records
- audit_records
- quarantine_records
- 8.2 去重策略
- 8.3 存储层代码:regex_audit/storage.py
- 9️⃣ 正则规则中心设计
-
- 9.1 规则配置:config/regex_rules.yaml
- 9.2 规则中心代码:regex_audit/rule_center.py
- 9.3 动态下发规则的工程思路
- 10️⃣ 审计器实现:字段级格式合法性判断
-
- 10.1 审计器代码:regex_audit/auditor.py
- 10.2 为什么使用 re.fullmatch
- 10.3 格式审计的态度
- 11️⃣ 工具函数与导出层
-
- 11.1 配置读取:regex_audit/utils.py
- 11.2 CSV 导出:regex_audit/exporter.py
- 12️⃣ 命令行入口:main.py
- 13️⃣ 运行方式与结果展示
-
- 13.1 启动命令
- 13.2 输出位置
- 13.3 运行输出示例
- 13.4 audit_result.csv 示例结果
- 13.5 quarantine_result.csv 示例结果
- 14️⃣ 完整 README 示例
- 运行
- 输出
- 注意事项
-
- 15.6 正则明明对,为什么匹配失败
- 15.7 统一社会信用代码正则能否判断真实有效
- 16️⃣ 进阶优化
-
- 16.1 并发采集
- 16.2 断点续跑
- 16.3 日志与监控
- 16.4 定时任务
- 16.5 规则灰度
- 16.6 多规则匹配
- 16.7 校验位算法扩展
- 17️⃣ 单元测试建议
-
- 17.1 安装 pytest
- 17.2 测试目录结构
- 17.3 test_cleaner.py
- 17.4 test_auditor.py
- 17.5 test_parser.py
- 18️⃣ 从 SQLite 迁移到 MySQL 的思路
-
- 18.1 MySQL 表结构示例
- 18.2 MySQL 版本存储层骨架
- 19️⃣ 数据质量视角下的指标设计
-
- 19.1 字段合规率
- 19.2 记录合规率
- 19.3 异常原因分布
- 19.4 字段异常排行
- 19.5 规则版本追溯
- 20️⃣ 一个更接近生产的规则配置设计
- 21️⃣ 关于“拦截脏数据入库”的边界
- 22️⃣ 生产环境中的数据流设计
- 23️⃣ 代码运行后的数据库检查
- 24️⃣ 进一步封装为命令行工具
- 25️⃣ Scrapy 版本迁移思路
- 26️⃣ Playwright 适用场景
- 27️⃣ 规则维护的现实经验
-
- 27.1 不要把规则写死在代码里
- 🌟 开篇语
- 中文标点符号、全半角与空白字符全链路规范化过滤器:从公开文本采集到 ETL 清洗中间件落地
-
- 0️⃣ 前言(Preface)
- 1️⃣ 摘要(Abstract)
- 2️⃣ 背景与需求(Why)
-
- 2.1 为什么要做这类采集与清洗
- 2.2 目标站点与目标字段
- 3️⃣ 合规与注意事项(必写)
-
- 3.1 robots.txt 基本说明
- 3.2 频率控制,不做攻击式并发
- 3.3 不采集敏感信息,不绕过付费或登录限制
- 4️⃣ 技术选型与整体流程(What/How)
-
- 4.1 本文属于哪种采集类型
- 4.2 整体流程
- 4.3 为什么选择 requests、bs4、lxml
- 5️⃣ 环境准备与依赖安装(可复现)
-
- 5.1 Python 版本
- 5.2 依赖安装
- 5.3 推荐项目结构
- 6️⃣ 核心实现:请求层(Fetcher)
-
- 6.1 config.py
- 6.2 fetcher.py
- 7️⃣ 核心实现:解析层(Parser)
-
- 7.1 parser.py
- 7.2 列表页如何拿详情链接
- 7.3 详情页如何抽字段
- 7.4 缺失字段怎么办
- 8️⃣ 数据存储与导出(Storage)
-
- 8.1 字段映射表
- 8.2 去重策略
- 9️⃣ 核心实现:清洗层(Normalizer)
-
- 9.1 常见脏字符类型
-
- 1. 零宽字符
- 2. 全角字符
- 3. 异常空白
- 4. 繁简混用
- 9.2 normalizer.py 完整实现
- 9.3 关于 NFKC 与全半角转换
- 9.4 关于中文标点规范化
- 9.5 transform_rules_hit 的价值
- 10️⃣ Storage 完整实现
-
- 10.1 storage.py
- 11️⃣ 主入口:串起采集、解析、清洗、存储
-
- 11.1 main.py
- 12️⃣ 本地演示:不依赖真实网站也能跑通
-
- 12.1 demo_local.py
- 13️⃣ 运行方式与结果展示(必写)
-
- 13.1 如何启动
- 13.2 输出在哪里
- 13.3 示例结果展示
- 🔟 常见问题与排错(强烈建议写)
-
- 10.1 403 怎么办
- 10.2 429 怎么办
- 10.3 HTML 抓到空壳怎么办
- 10.4 解析报错怎么办
- 10.5 编码或乱码如何处理
- 10.6 清洗后文本变奇怪怎么办
- 1️⃣1️⃣ 进阶优化(可选但加分)
-
- 11.1 并发优化
- 11.2 asyncio 版本思路
- 11.3 断点续跑
- 11.4 日志与监控
- 11.5 定时任务
- 11.6 扩展为 Scrapy 中间件
- 11.7 清洗规则配置化
- 11.8 加入单元测试
- 1️⃣2️⃣ 总结与延伸阅读
- 🌟 文末
-
- ✅ 专栏持续更新中|建议收藏 + 订阅
- ✅ 互动征集
- ✅ 免责声明
- 27.2 不要迷信一个超长正则
- 🌟 文末
-
- ✅ 专栏持续更新中|建议收藏 + 订阅
- ✅ 互动征集
- ✅ 免责声明
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。 运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO 欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战,主理专栏👉 《Python爬虫实战》:从采集策略到反爬对抗,从数据清洗到分布式调度,持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”,让数据价值真正做到——抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间:如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。 💕订阅后更新会优先推送,按目录学习更高效💯~
0️⃣ 前言(Preface)
这篇文章要做的事情很具体:用 Python 采集公开页面中的统一社会信用代码、邮编、车牌号等规整字段,通过一个“配置化正则规则中心”对字段格式进行合法性审计,最后把合规数据、异常数据和审计结果分别存入 SQLite,形成一套可复现、可扩展、可动态下发规则的数据质量拦截方案。
读完这篇文章,你大概能获得三件东西:
第一,你会知道如何把爬虫采集、字段解析、正则审计、数据入库这几个步骤拆成清晰的工程模块。
第二,你会得到一套真实可运行的 Python 示例项目,包含请求层、解析层、规则中心、审计器、存储层和命令行入口。
第三,你会理解“格式合法”与“业务真实”之间的边界:正则表达式能拦截大量脏数据,但它不是万能的,更不能替代后续的业务校验、权威库校验和人工复核。
1️⃣ 摘要(Abstract)
本文基于 Python、requests、BeautifulSoup、PyYAML 和 SQLite,构建一个面向统一社会信用代码、邮编、车牌号等公开规整数据的数据格式合法性审计系统,最终产出结构化审计表、合规数据表和异常隔离表。
读完之后,你可以掌握:
这篇文章不是为了追求“爬得多快”,而是为了把一个常见但经常被忽略的问题讲透:数据进入数据库之前,至少应该先过一遍格式合法性审计。
2️⃣ 背景与需求(Why)
做数据采集久了,大家会慢慢遇到一个不太显眼但很烦的问题:页面上的数据看起来很规整,字段也都像那么回事,可真正入库以后才发现,有些统一社会信用代码少了一位,有些邮编混进了中文字符,有些车牌号多了空格或符号,还有些字段根本是测试数据。
如果这些数据只是临时分析,问题可能还不严重。可一旦它们进入正式数据仓库、风控系统、客户画像系统、企业信息聚合系统,后续清洗成本就会越来越高。更麻烦的是,很多异常字段不会立刻报错,而是悄悄躺在数据库里,等到报表统计、接口联调、模型训练或下游同步时才暴露出来。
所以,这篇文章要解决的核心问题不是“怎么写一个能跑的爬虫”,而是:
如何在公开规整数据采集之后、正式入库之前,建立一层可配置、可动态更新、可审计追溯的数据格式合法性拦截系统。
2.1 为什么要爬这些数据
这类数据常见于企业信息展示页、物流网点公开页、车辆服务公开样例页、地址信息页面、开放数据目录页面等。它们往往具有以下特点:
例如:
统一社会信用代码一般是 18 位,由数字和大写字母组成,通常不包含 I、O、Z、S、V 等易混淆字符。
邮编一般是 6 位数字,且第一位不为 0。
车牌号根据不同规则会有普通燃油车、新能源车、警用车、使领馆车等多种格式。本文为了工程演示,会采用一个适合常规场景的简化正则,不追求覆盖所有特殊车牌。
2.2 目标字段清单
本文设计的数据字段如下。
| record_id | 原始记录编号 | R0001 |
| field_name | 字段名称 | unified_social_credit_code |
| value | 字段值 | 91350100M000100Y43 |
| regex_pattern | 命中的正则规则 | 1{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$ |
| is_compliant | 是否合规 | 1 |
| source_url | 来源页面 | https://example.com/public/list |
| reason | 审计说明 | matched |
| created_at | 审计时间 | 2026-07-06 10:30:00 |
为了便于演示,最终会落到 SQLite 中的三张表:
2.3 本文采用的实战目标
为了保证代码可以安全复现,本文不会绑定某一个真实商业平台,也不会演示绕过登录、绕过限制、批量刷取接口等行为。项目支持两种数据来源:
第一种是本地示例 HTML 文件,用于完整跑通采集、解析、审计、入库流程。
第二种是配置 URL 的公开静态页面,读者可以在遵守目标站点规则的前提下替换为自己的公开数据页面。
我平时写这类小项目时,比较偏爱先做“本地可跑通版本”。原因很简单:爬虫项目出问题时,网络、站点结构、反爬策略、编码、解析选择器、字段规则都可能是变量。如果一上来就连真实站点,排错会很乱。先用本地 HTML 固定输入,确认流程和数据模型没问题,再替换数据源,效率会高很多。
3️⃣ 合规与注意事项
爬虫技术本身是中性的,但使用方式必须谨慎。尤其是涉及企业标识、地址、邮编、车辆号牌等字段时,更应该明确边界。本文的目标是技术学习和数据质量审计,不涉及敏感信息采集,不演示绕过登录、绕过付费、绕过访问限制的做法。
3.1 关于 robots.txt
robots.txt 是网站用来声明爬虫访问规则的文本文件。它通常位于站点根目录,例如:
https://example.com/robots.txt
在正式采集前,建议先查看目标站点的 robots.txt,确认相关路径是否允许抓取。虽然 robots.txt 不是访问控制系统,但它体现了站点对自动化访问的基本态度。作为技术人员,至少应该尊重这些规则。
如果目标路径被明确禁止抓取,就不要继续抓取该路径。很多时候,换成站点提供的开放数据接口、公开下载文件、官方数据集或手动授权方式,会比硬爬页面更稳定,也更省心。
3.2 控制访问频率
本文示例代码会在请求层加入 timeout、重试和退避机制,但这不代表可以无限并发。一个负责任的爬虫应该做到:
对于公开页面的学习型采集,通常不需要高并发。很多时候,每秒 1 次甚至每几秒 1 次都足够了。真正的工程价值不在于“刷得快”,而在于“流程稳、字段准、入库干净、异常可追溯”。
3.3 不采集敏感信息
本文只讨论公开规整字段的格式合法性审计,不采集个人隐私信息,不抓取需要登录后才能访问的数据,不绕过权限限制,也不演示验证码破解、接口签名破解、付费内容绕过等行为。
如果业务确实需要采集某些数据,应优先考虑以下方式:
3.4 正则审计不是事实校验
这点很重要。正则表达式只能判断一个字段“长得像不像合法格式”,不能证明它一定真实存在。
例如:
91350100M000100Y43
这个字符串可能符合统一社会信用代码的基础格式,但它是否真实存在,还需要权威企业信息库、工商数据源或业务系统进一步验证。
同样,邮编 100000 符合 6 位数字格式,但它是否对应某个具体地址,也不是正则能解决的问题。
所以本文系统的定位是:
在数据入库前做第一道格式质量门禁,拦截明显不合规数据,降低后续清洗成本。
4️⃣ 技术选型与整体流程(What/How)
4.1 静态、动态、API 三种采集方式
常见页面采集可以粗略分为三类。
第一类是静态页面。页面 HTML 中已经包含目标字段,直接用 requests 获取页面,再用 BeautifulSoup、lxml 或 XPath 解析即可。
第二类是动态页面。浏览器打开页面后,数据由 JavaScript 再次请求接口渲染出来。requests 抓到的 HTML 可能只是一个空壳。这种情况可以分析接口,也可以用 Playwright 这类浏览器自动化工具渲染后再解析。
第三类是 API。数据直接以 JSON 形式返回,解析成本低,结构稳定性通常也更好。如果站点提供公开 API,并且允许调用,优先考虑 API。
本文采用的是:
静态 HTML 页面采集 + BeautifulSoup 解析 + YAML 正则规则中心 + SQLite 入库。
为什么不一上来用 Scrapy 或 Playwright?
因为本文重点不是复杂反爬,也不是大规模调度,而是“采集之后如何做格式审计和脏数据拦截”。requests + BeautifulSoup 足够清楚、依赖少、可读性好。等后续需要并发、队列、断点续跑、分布式调度时,再迁移到 Scrapy 或异步框架也不迟。
4.2 整体流程
系统流程可以概括为:
#mermaid-svg-1ccuUBF4pHie15Bn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1ccuUBF4pHie15Bn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1ccuUBF4pHie15Bn .error-icon{fill:#552222;}#mermaid-svg-1ccuUBF4pHie15Bn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1ccuUBF4pHie15Bn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1ccuUBF4pHie15Bn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1ccuUBF4pHie15Bn .marker.cross{stroke:#333333;}#mermaid-svg-1ccuUBF4pHie15Bn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1ccuUBF4pHie15Bn p{margin:0;}#mermaid-svg-1ccuUBF4pHie15Bn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-1ccuUBF4pHie15Bn .cluster-label text{fill:#333;}#mermaid-svg-1ccuUBF4pHie15Bn .cluster-label span{color:#333;}#mermaid-svg-1ccuUBF4pHie15Bn .cluster-label span p{background-color:transparent;}#mermaid-svg-1ccuUBF4pHie15Bn .label text,#mermaid-svg-1ccuUBF4pHie15Bn span{fill:#333;color:#333;}#mermaid-svg-1ccuUBF4pHie15Bn .node rect,#mermaid-svg-1ccuUBF4pHie15Bn .node circle,#mermaid-svg-1ccuUBF4pHie15Bn .node ellipse,#mermaid-svg-1ccuUBF4pHie15Bn .node polygon,#mermaid-svg-1ccuUBF4pHie15Bn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1ccuUBF4pHie15Bn .rough-node .label text,#mermaid-svg-1ccuUBF4pHie15Bn .node .label text,#mermaid-svg-1ccuUBF4pHie15Bn .image-shape .label,#mermaid-svg-1ccuUBF4pHie15Bn .icon-shape .label{text-anchor:middle;}#mermaid-svg-1ccuUBF4pHie15Bn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1ccuUBF4pHie15Bn .rough-node .label,#mermaid-svg-1ccuUBF4pHie15Bn .node .label,#mermaid-svg-1ccuUBF4pHie15Bn .image-shape .label,#mermaid-svg-1ccuUBF4pHie15Bn .icon-shape .label{text-align:center;}#mermaid-svg-1ccuUBF4pHie15Bn .node.clickable{cursor:pointer;}#mermaid-svg-1ccuUBF4pHie15Bn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1ccuUBF4pHie15Bn .arrowheadPath{fill:#333333;}#mermaid-svg-1ccuUBF4pHie15Bn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1ccuUBF4pHie15Bn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1ccuUBF4pHie15Bn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1ccuUBF4pHie15Bn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1ccuUBF4pHie15Bn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1ccuUBF4pHie15Bn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1ccuUBF4pHie15Bn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1ccuUBF4pHie15Bn .cluster text{fill:#333;}#mermaid-svg-1ccuUBF4pHie15Bn .cluster span{color:#333;}#mermaid-svg-1ccuUBF4pHie15Bn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1ccuUBF4pHie15Bn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1ccuUBF4pHie15Bn rect.text{fill:none;stroke-width:0;}#mermaid-svg-1ccuUBF4pHie15Bn .icon-shape,#mermaid-svg-1ccuUBF4pHie15Bn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1ccuUBF4pHie15Bn .icon-shape p,#mermaid-svg-1ccuUBF4pHie15Bn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1ccuUBF4pHie15Bn .icon-shape .label rect,#mermaid-svg-1ccuUBF4pHie15Bn .image-shape .label


