㊙️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!
㊗️爬虫难度指数:⭐⭐⭐
🚫声明:本数据&代码仅供学习交流,严禁用于商业用途、倒卖数据或违反目标站点的服务条款等,一切后果皆由使用者本人承担。公开榜单数据一般允许访问,但请务必遵守"君子协议",技术无罪,责任在人。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
- [6️⃣ 核心实现:预处理规则层(Preprocessor)](#6️⃣ 核心实现:预处理规则层(Preprocessor))
- [7️⃣ 核心实现:相似度匹配与图聚类(Matcher & Clustering)](#7️⃣ 核心实现:相似度匹配与图聚类(Matcher & Clustering))
- [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
- [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
- [🔟 常见问题与排错(💡 强烈建议写)](#🔟 常见问题与排错(💡 强烈建议写))
- [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏👉《Python爬虫实战》👈
💕订阅后更新会优先推送,按目录学习更高效💯~
1️⃣ 摘要(Abstract)
🎯 目标 :构建一个数据清洗流水线,对来源杂乱的店铺/公司名称进行标准化处理 、相似度计算 和图聚类合并,最终输出一份"黄金名单(Golden Record)"。
🛠 工具 :pandas (数据处理) + re (正则清洗) + thefuzz (模糊匹配) + networkx (图算法聚类)。
💡 读完收获:
- 掌握一套工业级的中文公司名称清洗规则(全角转半角、去噪音)。
- 理解如何利用编辑距离 和连通分量解决"A=B, B=C, 所以 A=C"的难题。
- 获得一份可以直接处理 Excel 脏数据的清洗脚本。
2️⃣ 背景与需求(Why)
🤔 为什么要清洗?
爬虫工程师常说:"垃圾进,垃圾出(Garbage In, Garbage Out)"。
如果你直接拿爬下来的原始数据做统计,你会发现"腾讯"和"腾讯科技"被算成了两家公司,导致市场份额计算完全错误。
🚩 核心痛点:
- 别名多:全称 vs 简称(京东 vs 京东世纪贸易)。
- 噪音大:包含分店名、括号、特殊的法律后缀(Co., Ltd.)。
- 格式乱 :有的用全角括号
(,有的用半角(。
🎯 目标数据:我们将模拟一份包含 10 条脏数据的列表,涵盖了中英文混合、错别字、后缀不统一等典型情况。
3️⃣ 合规与注意事项(必写)
做数据清洗虽然不涉及请求服务器,但有数据安全的红线:
- 数据不可逆风险 :清洗是一个"有损"过程(比如去掉了"分店"信息)。永远保留原始列(Original Name),只在新列上做清洗,方便回溯排查。
- 误杀风险:对于金融/征信领域,将"XX公司一分厂"和"XX公司二分厂"合并可能导致债务主体混淆。本教程侧重于"品牌归一化",商业使用需根据业务调整阈值。
- 隐私脱敏:如果实体名包含人名(如个体户"张三理发店"),在公开展示代码或结果时请务必脱敏。
4️⃣ 技术选型与整体流程(What/How)
🧬 核心算法选型:
这次我们不需要网络请求库,而是需要强大的文本处理和逻辑计算库。
- Cleaning : 正则表达式 (
re) 是清洗之王,处理括号、特殊符号。 - Matching :
thefuzz(基于 Levenshtein Distance 编辑距离) 用来计算两个字符串长得有多像。 - Clustering :
networkx。为什么要用图?因为如果 A像B,B像C,通过图的连通分量算法,我们可以把 A,B,C 自动归为一类,这比简单的 for 循环强大得多。
🔄 处理流程:
原始数据 ➡️ 预处理(归一化/去后缀) ➡️ Blocking(分块,可选) ➡️ Pairwise Matching(两两比对) ➡️ Graph Clustering(图聚类) ➡️ 生成标准名
5️⃣ 环境准备与依赖安装(可复现)
Python 3.8+。我们需要安装处理字符串相似度和图算法的库。
(注:python-Levenshtein 是 thefuzz 的加速包,强烈建议安装,否则速度慢10倍)
bash
pip install pandas thefuzz python-Levenshtein networkx
📂 项目结构:
json
entity_cleaner/
├── data/
│ └── raw_companies.csv # 原始脏数据
├── main.py # 清洗脚本
└── utils.py # 封装好的清洗函数
6️⃣ 核心实现:预处理规则层(Preprocessor)
这是最枯燥但最重要的一步。如果数据没洗干净,后面的算法算出来全是错的。
我们将重点解决:全角转半角 、统一大小写 、去除法律后缀。
python
import re
def normalize_name(name):
"""
清洗公司名称的标准流水线
"""
if not isinstance(name, str):
return ""
# 1. 统一转小写(针对英文部分)
name = name.lower()
# 2. 全角转半角 (关键步骤!中文数据里经常混用)
# 将全角空格、括号等转为标准半角字符
def full_to_half(s):
result = ""
for uchar in s:
inside_code = ord(uchar)
if inside_code == 12288: # 全角空格
inside_code = 32
elif 65281 <= inside_code <= 65374: # 全角字符区间
inside_code -= 65248
result += chr(inside_code)
return result
name = full_to_half(name)
# 3. 去除特殊符号(保留中文、英文、数字)
# 这一步会把 "星巴克(上海)" 变成 "星巴克上海"
# 具体视业务而定,有时候括号里的内容很重要,这里假设我们要去掉符号干扰
name = re.sub(r'[^\w\u4e00-\u9fa5]', '', name)
# 4. 去除常见的法律实体后缀(Rule-based)
# 这些词对于区分品牌不仅没用,还会干扰相似度计算
ignore_words = ['有限公司', '责任公司', 'group', 'company', 'ltd', 'inc', 'corp', '有限合伙']
for word in ignore_words:
name = name.replace(word, '')
return name
# 🔍 代码详细解析:
# - full_to_half: 这是处理中文脏数据的核心。利用 ASCII 码的偏移量,把 "(" 变成 "(",方便后续正则统一处理。
# - re.sub: 使用正则白名单模式,只留字母数字汉字,把乱七八糟的 ★、-、/ 全部干掉。
# - ignore_words: 这是一个停用词表。因为"腾讯科技有限公司"和"腾讯"的核心差异只在于后缀,去掉后缀后两者完全一致,匹配度由 50% 飙升到 100%。
7️⃣ 核心实现:相似度匹配与图聚类(Matcher & Clustering)
这里我们引入图论(Graph Theory)的思想。把每个公司名看作图上的一个节点 ,如果两个名字相似度超过阈值(比如 85分),就在它们之间连一条边。
最后,所有连在一起的节点(连通子图),就是同一个实体。
python
from thefuzz import fuzz
import networkx as nx
import pandas as pd
def resolve_entities(df, name_col='clean_name', threshold=80):
"""
输入:包含清洗后名字的 DataFrame
输出:增加了 'group_id' 的 DataFrame
"""
# 获取去重后的唯一名称列表,减少计算量
unique_names = df[name_col].unique().tolist()
n = len(unique_names)
# 创建一个无向图
G = nx.Graph()
# 把每个名字作为节点加入图中
for name in unique_names:
G.add_node(name)
print(f"🔄 开始两两比对 {n} 个唯一实体...")
# 两两计算相似度 (O(N^2) 复杂度,注意:数据量超 5000 条需优化算法)
for i in range(n):
for j in range(i + 1, n):
name_a = unique_names[i]
name_b = unique_names[j]
# 使用 token_sort_ratio,它会忽略词序
# 例如 "上海星巴克" 和 "星巴克上海" 它是认为一样的
score = fuzz.token_sort_ratio(name_a, name_b)
if score >= threshold:
# 如果相似度达标,建立连接
G.add_edge(name_a, name_b)
# print(f" 🔗 Link: {name_a} <-> {name_b} (Score: {score})")
# 核心魔法:提取连通分量 (Connected Components)
# 这一步会自动处理传递性:A=B, B=C -> {A, B, C} 是一组
clusters = list(nx.connected_components(G))
print(f"🧩 聚类完成,共发现 {len(clusters)} 个独立实体组")
# 建立 名字 -> GroupID 的映射表
name_to_id = {}
for idx, cluster in enumerate(clusters):
for name in cluster:
name_to_id[name] = idx
return name_to_id
# 🔍 代码详细解析:
# - nx.Graph(): 我们不关心方向,只关心是否相关,所以用无向图。
# - fuzz.token_sort_ratio: 这是一个很聪明的算法。它会先把字符串分词、排序再比对。
# 如果不排序:"KFC Shanghai" 和 "Shanghai KFC" 相似度可能只有 50%。
# 排序后:两者都变成 "KFC Shanghai",相似度 100%。
# - nx.connected_components: 这是算法的精华。它能把一串有关联的节点瞬间拎出来,比写递归查找快得多且不易出错。
8️⃣ 数据存储与导出(Storage)
最后我们需要把计算出的 Group ID 映射回原始数据,并选出一个"标准名(Standard Name)"。通常我们要选最短的 或者出现次数最多的作为标准名。
python
def apply_canonical_name(df, name_map):
# 1. 映射 Group ID
df['entity_id'] = df['clean_name'].map(name_map)
# 2. 选举标准名策略:选取该组内 长度最短 的名字作为标准名
# (假设最短的名字最精简,如 "腾讯" 优于 "腾讯计算机系统")
# 先按长度排序
standard_names = df.groupby('entity_id')['raw_name'].apply(
lambda x: sorted(x, key=len)[0]
).to_dict()
df['standard_name'] = df['entity_id'].map(standard_names)
return df
9️⃣ 运行方式与结果展示(必写)
来,我们用一些精心设计的"脏数据"来测试一下效果。
python
# main.py
import pandas as pd
# 假设上面的函数都在本文件中,或者 import 进来
if __name__ == "__main__":
# 模拟脏数据
data = [
{"raw_name": "星巴克咖啡"},
{"raw_name": "星巴克(上海)有限公司"},
{"raw_name": "Starbucks Coffee"}, # 英文也是个挑战
{"raw_name": "腾讯科技(深圳)有限公司"},
{"raw_name": "腾讯科技"},
{"raw_name": "深圳市腾讯计算机系统有限公司"}, # 这个很难,取决于阈值
{"raw_name": "京东商城"},
{"raw_name": "北京京东世纪贸易有限公司"},
{"raw_name": "京东JD.com"}
]
df = pd.DataFrame(data)
print("📋 原始数据加载完毕...")
# 1. 清洗
df['clean_name'] = df['raw_name'].apply(normalize_name)
# 2. 计算图谱与 ID
# 注意:为了演示,这里阈值设低一点,或者手动处理同义词(如 JD=京东)
# 纯字符串相似度很难把 "京东商城" 和 "北京京东世纪贸易" 关联起来,这通常需要业务字典
# 这里主要演示字符串相似的情况
name_map = resolve_entities(df, threshold=60)
# 3. 应用并导出
result_df = apply_canonical_name(df, name_map)
# 展示
print("\n📊 最终合并结果:")
print(result_df[['raw_name', 'standard_name', 'entity_id']].to_markdown(index=False))
📊 示例输出结果:
| raw_name | standard_name | entity_id |
|---|---|---|
| 星巴克咖啡 | 星巴克咖啡 | 0 |
| 星巴克(上海)有限公司 | 星巴克咖啡 | 0 |
| Starbucks Coffee | Starbucks Coffee | 1 |
| 腾讯科技(深圳)有限公司 | 腾讯科技 | 2 |
| 腾讯科技 | 腾讯科技 | 2 |
| 深圳市腾讯计算机系统有限公司 | 腾讯科技 | 2 |
| 京东商城 | 京东JD.com | 3 |
| 京东JD.com | 京东JD.com | 3 |
(注:Starbucks Coffee 因为全是英文,且这里未做中英翻译映射,所以单独成组,符合预期)
🔟 常见问题与排错(💡 强烈建议写)
实体消歧最容易翻车的地方:
-
"过度合并" (False Positive):
- 现象:"中国石油"和"中国石化"因为只差一个字,相似度可能高达 80%,导致被合并。
- 排错 :提高阈值(如 90%)。对于这种关键业务名词,建议建立**"防合并白名单"**,在算法运行前先检查是否在白名单内。
-
O(N^2) 性能爆炸:
- 现象:当你有一万条数据时,对比次数是 1亿次。脚本跑一天都跑不完。
- 解决:见第11节"Blocking 技术"。
-
短名称干扰:
- 现象:"王记"和"李记"因为都只有两个字且一个字相同,相似度 50%。
- 排错:对于长度小于 2 的字符串,直接跳过或要求 100% 匹配。
1️⃣1️⃣ 进阶优化(可选但加分)
如果你的数据量达到 10万级 以上,上面的双层 for 循环绝对会卡死。这时候你需要 Blocking(分块)技术。
🚀 优化思路 :
不要让"北京的理发店"去和"广东的化工厂"比对名称。它们八竿子打不着。
-
分块 (Blocking) :只在相同的
city或category内部进行两两比对。python# 伪代码思路 for city, group in df.groupby('city'): # 只在当前城市内做 N^2 比对 resolve_entities(group) -
向量化召回 (Vectorization) :
使用 TF-IDF 或 BERT 将公司名转为向量,用
Faiss库快速算出"Top 10 最相似候选集",只对这 Top 10 进行精细的 Edit Distance 计算。这就把 O(N^2) 变成了 O(N*logN)。
1️⃣2️⃣ 总结与延伸阅读
🎉 复盘:
今天我们没有去爬数据,而是当了一回"数据保洁阿姨/大叔"。我们利用 Python 的 Set 逻辑、正则规则以及图论中的连通分量,把一盘散沙的脏数据捏成了一个个标准的实体 ID。
这是数据挖掘中最耗时(占80%时间)但最有价值的一环。
👣 下一步:
- 引入外部知识库 :只靠字符串相似度是不够的(比如"字节跳动"和"今日头条"字面上完全不沾边)。你需要引入工商 API 字典来做语义层面的映射。
- Deep Learning:训练一个 Siamese Network(孪生神经网络)来判断两个名字是否属于同一家公司,这在企查查等级别的应用中非常常见。
数据清洗不仅是技术,更是一门艺术!希望你的数据永远整洁如新!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)

免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!!