前两天同事丢给我一个Excel文件,说客户表里重复特别多,让我帮忙弄干净。我打开一看,3万多行,一开始觉得这有什么难的,鼠标点几下“删除重复值”不就完事了?等我真正开始处理才发现,真正麻烦的从来不是那些完全一模一样的行,而是这些:“某某科技有限公司”和“某某科技 有限公司”、“张三客服部”和“张三 客服部”、甚至同一个客户被不同销售录成了“北京华信”和“华信(北京)”。如果你也遇到过类似的情况,这篇就用实际处理流程来聊聊,怎么把Excel文件里完整的、近似的重复文本行一次性找出来,并且根据数据量选择最合适的工具。
1. 问题场景与方案选型
1.1 为什么“完整重复”和“近似重复”都要处理
很多人一说去重,脑子里第一反应就是“删除重复值”那个按钮。这个按钮对完全一致的行非常管用,但实际数据环境里,完全一致的行只是冰山一角。业务系统导出的Excel,经过多个人手工录入、多张表合并、多个部门用不同模板整理之后,真正让人头疼的是“看起来像同一件事,但文本并不完全一样”的近似重复。
举个例子。A表里写的是“北京市朝阳区XX大厦1201室”,B表里同一地址可能是“北京市朝阳区XX大厦1201 室”,中间多了一个空格;A表写“李四”,B表写“李四 ”;A表写“华信科技有限公司”,B表写“华信科技”。这些在人的眼里一眼就能认出是同一个实体,可Excel的函数和按钮只会按字符去比,差一个空格都会认为是两行。
所以这个任务的完整定义应该是:第一,找出完全重复的行,也就是所有被选中列的内容在字符级别完全一致;第二,找出近似重复的行,可能因为空格、标点、大小写、全角半角、停用词出现差异,但语义上高度相似。处理完以后,你才能放心地去做统计、发通知、导入数据库、制作报表,否则重复数据会直接影响数量汇总准确性和后续业务判断。
1.2 方案横向对比:内置功能、VBA、Python、SQL怎么选
处理重复文本行没有一招通用的方案,关键在于数据量和运行环境。我把常用的四种方式放在一起做了一个对比,也是我自己平时选型的依据。
| 方案 | 适合数据量 | 能否处理近似重复 | 上手难度 | 典型场景 |
|---|---|---|---|---|
| Excel删除重复值 | 几千行以内 | 不能 | 极低 | 一次性快速去重 |
| Excel函数+条件格式 | 几千行以内 | 只能做简单通配符 | 低 | 标记完全重复、局部重复 |
| VBA宏 | 数千到一万行 | 能,自己写相似度算法 | 中 | 不想装Python的办公环境 |
| Python脚本 | 一万行以上 | 能,算法丰富 | 中高 | 大规模数据清洗、例行任务 |
我个人的选择逻辑很直接:如果只是临时把完全一样的行去掉,用Excel删除重复值就够了;如果带有“近似”需求,但数据量在几千行以内,用VBA写一个相似度匹配工具就行;如果数据上了万行,或者以后每个月都要跑一次,那我一定会用Python。SQL也能做完全重复,但做近似重复匹配在标准SQL里相当费劲,真要做的话也得靠自定义函数,实际场景里不如前面几种方便。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先用Excel原生功能处理“完全重复”
2.1 删除重复值:三分钟上手
在进入近似重复之前,先把完全重复处理掉是最划算的。Excel自带的“删除重复值”在“数据”选项卡下,点击后可以勾选要判断的列。这里有一个非常容易踩的坑:如果你只勾选客户名称列,Excel会把客户名称相同但地址不同的行也删掉;如果你需要整行完全一致才算重复,那就把所有列都勾上。
操作方法很简单:选中数据区域内的任意单元格,点“数据 - 删除重复值”,在弹出的对话框里勾选参与判断的列,点确定即可。这样操作完以后,重复的行会被删掉,只保留第一行。但在点击之前,我强烈建议先复制一份原始数据到另一个工作表,因为删除重复值不等于“标记重复”,它是真的删行,一旦误选列范围,恢复起来非常麻烦。
另外还有一种情况:你只想找出重复行,并不想真正删除它们。那“删除重复值”就不合适了,应该用下面这种标记方法。
2.2 用函数和条件格式做标记:COUNTIF是核心
标记完全重复行最常用的函数是COUNTIF。在数据表右侧新增一列,输入公式:
excel复制=IF(COUNTIF(A$2:A$10000,A2)>1,"重复","")
COUNTIF统计的是当前单元格的文本在整个区域里出现了多少次,如果大于1就说明不只是当前这一行,后面还有一样的。这个公式的好处是“只看不删”,结果安全。你还可以配合条件格式,将“重复”文本所在的行标成黄色,方便肉眼人工检查。
如果只是判断单列,COUNTIF完全够用。但如果要判断多列联合重复,比如“客户名称+联系人+电话”三列都一样才算重复,可以加一个辅助列,先把三列拼起来:
excel复制=A2&"|"&B2&"|"&C2
然后对辅助列做COUNTIF。这里的“|”是分隔符号,随便选一个不太可能出现在数据里的字符就行,避免“张三”和“123”拼接后跟“张三1”与“23”拼接撞车。
条件格式的思路也一样:选中区域,点击“开始 - 条件格式 - 新建规则 - 使用公式确定要设置格式的单元格”,输入类似上面的公式,然后设置一个填充色。这样不用手工加辅助列也能直接看到哪些行重复,但公式里千万别忘了绝对引用的$符号,否则区域会跟着单元格移动,结果就全乱了。
2.3 原生功能的边界在哪里
用Excel原生功能处理完全重复,核心问题就是它只能做“字符级别的完全一致判断”。哪怕只是多了一个空格,结果都会漏掉。很多人查来查去查不到重复,其实就是因为文本里有看不见的空格、换行符,或者全角半角不统一。
举个我很常见的例子:从网页复制下来的Excel,单元格末尾经常带上一个不可见换行符,你在界面上看都是“北京华信”,实际上一个是北京华信,另一个是北京华信\n,在COUNTIF里就是两个完全不同的字符串。遇到这种情况,需要先用CLEAN函数去掉不可见字符、用TRIM函数去掉首尾空格,再做判断。
原生功能还有个边界:它无法回答“这两行是不是同一家公司的不同写法”。当你有“北京市XX科技有限公司”和“XX科技(北京)有限公司”这种需要靠语义去判断的近似重复时,Excel按钮根本无能为力。这时候就得引入相似度算法,也就是下面这一节要讲的内容。
3. 近似重复的核心:相似度计算与数据预处理
3.1 相似度是怎么算出来的
处理近似重复,核心不是写循环,而是先想清楚怎么判断“相似”。比较两个字符串有多像,业界最常用的是编辑距离(Levenshtein Distance)。它表示把一个字符串变成另一个字符串,最少需要做多少次删除、插入、替换操作。
比如“北京朝阳”和“北京 朝阳”之间的距离是1,因为只需要插入一个空格;“华信科技”和“华信科技有限公司”之间的距离是2,因为需要插入“有限”两个字。得到编辑距离以后,再结合两个字符串的长度,计算一个相似度分数:
text复制相似度 = 1 - 编辑距离 / 两个字符串的最大长度
按这个公式,“北京朝阳”和“北京 朝阳”的相似度是1 - 1/4 = 0.75,而“华信科技”和“华信科技有限公司”的相似度是1 - 2/9 ≈ 0.78。在实际项目中,我会先定义一个阈值,比如0.85,相似度大于等于阈值的,就判定为疑似重复。
除了编辑距离,还有Jaccard相似度、n-gram、SimHash等方法。Excel和Python里最容易落地的是编辑距离和基于它的变种,因为实现逻辑直观,中英文都能用。英文环境里还可以按空格分词后做词级别比较,中文文本我更建议先做字符级比较,因为中文分词本身就是一个新的复杂问题。
3.2 数据预处理:决定成败的一步
别急着跑相似度算法,数据预处理不做,相似度再高也是空谈。我踩过不少次坑之后,总结了一套默认清洗规则,每次处理前先无条件执行:
- 去掉单元格首尾空格,把中间的连续空格替换成单空格。
- 去掉换行符和不可见字符,用CLEAN处理。
- 把英文字母统一成小写或大写,避免“ABC”和“abc”被判成不同。
- 把全角字符转成半角字符,主要处理数字、字母、标点。
- 去掉文本里常见的标点符号,比如逗号、句号、括号。
- 对同一个公司里的冗余词做归一化,比如“有限责任公司”统一成“公司”或直接删掉。
在Excel里,这几步可以用公式串起来。比如把A2单元格做简单清洗:
excel复制=TRIM(SUBSTITUTE(SUBSTITUTE(A2," ",""),CHAR(10),""))
这个公式把空格和换行都去掉,适合只关注核心文字的场景。如果还要统一“有限公司”这个后缀,可以在外面再套一层SUBSTITUTE。
在Python里清洗更简单,用pandas的str方法几行就搞定:
python复制df['清洗后'] = (
df['名称']
.astype(str)
.str.strip()
.str.replace(' ', '')
.str.replace('\n', '')
.str.replace('有限公司', '')
.str.lower()
)
为什么要花这么大力气做清洗?因为“华信科技 ”和“华信科技”如果直接送去算相似度,编辑距离是1,相似度会降到0.9,阈值一高就会漏掉;清洗完之后两者完全相同,相似度直接是1,根本不用纠结。
3.3 相似度阈值怎么定:短文本和长文本要分开
阈值的选择直接影响结果。定太高会漏报,定太低会误报。根据我的实际经验,阈值要结合文本长度来调整。
对于长度只有2到5个字符的短文本,比如“华信”和“华信 ”这样,清洗后的差异已经非常小,阈值要定高一点,建议0.9以上。因为短文本只要改一个字符,相似度就掉很多,再用0.8的阈值会漏掉真正的重复。
对于较长文本,比如地址、公司注册名称,因为本身字符多,个别差异带来的影响被稀释了,阈值可以放宽到0.8到0.85。比如“北京市朝阳区XX大厦1201室”和“北京市朝阳区XX大厦1201 室”,清洗空格以后两者完全一致,但如果没清洗,相似度约0.96,还是能查出来。
我通常的做法是只设一个默认值0.85,但输出所有候选结果时都带上相似度分数,人工复核时可以对低于0.9的再重点看一下。这样既保证召回率,又不会因为误报太多浪费精力。
4. 用VBA在Excel里做模糊查重
4.1 为什么还在Excel内部搞
Python虽然强大,但很多人所在的公司电脑没安装Python环境,日常只允许用Office。这时候VBA是处理近似重复的最优解,它做的是Excel内置功能做不到的事情,又不需要额外装软件。你只要打开Excel,按Alt+F11进入VBA编辑器,就能把代码跑起来。
但也要提前说一句,VBA适合的是“几千行级别”的数据量。因为它要做两两比较,数据量从1000行增长到5000行,比较次数会从50万次涨到1250万次,即便用数组操作也需要一点时间。如果你的数据超过一万行,我更推荐直接跳到下一节用Python。
4.2 核心代码:Levenshtein函数与主程序
先写一个计算编辑距离的自定义函数,这是判定相似度的基础。放在VBA的模块里:
vba复制Function LevenshteinDistance(ByVal s As String, ByVal t As String) As Long
Dim i As Long, j As Long
Dim m As Long, n As Long
Dim cost As Long
Dim d() As Long
m = Len(s)
n = Len(t)
ReDim d(0 To m, 0 To n)
For i = 0 To m
d(i, 0) = i
Next i
For j = 0 To n
d(0, j) = j
Next j
For i = 1 To m
For j = 1 To n
If Mid(s, i, 1) = Mid(t, j, 1) Then
cost = 0
Else
cost = 1
End If
d(i, j) = Application.WorksheetFunction.Min( _
d(i - 1, j) + 1, _
d(i, j - 1) + 1, _
d(i - 1, j - 1) + cost)
Next j
Next i
LevenshteinDistance = d(m, n)
End Function
然后写一个主程序,对选中的区域逐行比较,相似度超过阈值就标黄。为了方便阅读和检查,我把相似度结果输出到表格右侧新增列,而不是只改颜色。
vba复制Sub FindApproxDuplicates()
Dim rng As Range
Dim i As Long, j As Long
Dim s1 As String, s2 As String
Dim sim As Double
Dim threshold As Double
Dim maxLen As Long
Dim lastRow As Long
On Error Resume Next
Set rng = Application.InputBox("请选择要比较的单元格区域", "选择区域", Type:=8)
On Error GoTo 0
If rng Is Nothing Then Exit Sub
threshold = 0.85
lastRow = rng.Rows.Count
For i = 1 To lastRow
For j = i + 1 To lastRow
s1 = Trim(rng.Cells(i, 1).Value)
s2 = Trim(rng.Cells(j, 1).Value)
If Len(s1) > 0 And Len(s2) > 0 Then
maxLen = Application.WorksheetFunction.Max(Len(s1), Len(s2))
If maxLen > 0 Then
sim = 1 - LevenshteinDistance(s1, s2) / maxLen
If sim >= threshold Then
rng.Cells(i, 1).Interior.Color = RGB(255, 255, 0)
rng.Cells(j, 1).Interior.Color = RGB(255, 255, 0)
End If
End If
End If
Next j
If i Mod 100 = 0 Then Application.StatusBar = "正在比较第 " & i & " 行..."
Next i
Application.StatusBar = False
MsgBox "近似重复标记完成"
End Sub
代码的逻辑很直接:第一行跟后面所有行比,第二行跟后面所有行比。这样每一对行只会被比较一次,不会出现重复计算。注意我只取了选区的第一列作为比较对象,如果要多列联合判断,可以先在表格里用公式做辅助列,然后再运行这段代码。
4.3 VBA性能优化与使用细节
VBA慢在大循环里的单元格访问。上面代码已经尽量避免读取整个区域,但rng.Cells(i, 1).Value这种操作依然慢。一个更彻底的优化是把数据一次性读进数组,比较完再写回数组,最后统一输出到工作表。原理不复杂:数组在内存里操作,速度远比一个一个访问单元格快。
我还有一个实际优化技巧:先限制“只有长度相近的文本才比较”。两个字符串长度差超过3个字符,一般不会是同一句话,直接跳过。这样能省掉大量无效比较。代码里可以这样加:
vba复制If Abs(Len(s1) - Len(s2)) > 3 Then GoTo NextJ
另外两个重要细节:第一,运行前记得保存备份,VBA一旦误标颜色,撤销也不是每次都好用;第二,建议在代码开头关闭屏幕更新:
vba复制Application.ScreenUpdating = False
跑完再恢复成True,否则你会看到Excel在屏幕上疯狂刷新,速度至少慢一半。
5. 用Python批量处理大规模近似重复
5.1 为什么数据量一大就得换Python
VBA在两三千行以内还能接受,到了上万行以后,两两比较的数量级是千万甚至亿级的,VBA跑起来经常会卡到让你怀疑人生。Python更适合这个场景,因为它处理字符串的库非常成熟,而且内存操作效率比Excel高很多。
另一个常见需求是:Excel数据最终要导入数据库,或者定期接收新数据做增量检查。在这种重复性任务里,用Python写一个脚本比每次打开Excel手动操作靠谱得多,参数、阈值、清洗规则都写死在代码里,结果可复现。所谓“python查找excel中字符串”,本质上也是同一类需求,只是从“查找”变成了“查找并比较相似度”。
5.2 用pandas+difflib快速实现
Python里有很多相似度库,比如difflib、fuzzywuzzy、RapidFuzz。如果不想安装太多第三方库,直接用Python自带的difflib就够了。下面这套代码是我最常用的基础版本,适合几千到几万行的数据。
python复制import pandas as pd
from difflib import SequenceMatcher
# 读取Excel,所有列先转成字符串,避免因为数字格式导致比较不准
df = pd.read_excel('客户表.xlsx', dtype=str).fillna('')
# 数据预处理
df['比较键'] = (
df['客户名称']
.str.strip()
.str.replace(' ', '')
.str.replace('有限公司', '')
.str.lower()
)
threshold = 0.85
results = []
n = len(df)
for i in range(n):
for j in range(i + 1, n):
a, b = df.loc[i, '比较键'], df.loc[j, '比较键']
# 长度差超过5,直接跳过
if abs(len(a) - len(b)) > 5:
continue
sim = SequenceMatcher(None, a, b).ratio()
if sim >= threshold:
results.append((a, b, round(sim, 4), i, j))
result_df = pd.DataFrame(results, columns=['文本A', '文本B', '相似度', '行号A', '行号B'])
result_df.to_excel('疑似重复行.xlsx', index=False)
SequenceMatcher.ratio()返回的是0到1之间的相似度,比我们手动算的编辑距离相似度稍微宽松一些,因为它是按匹配块来计算的,对连续相同的部分更友好。如果你更看重精确控制,也可以用编辑距离来算,核心逻辑是一样的。
这段代码跑出来的结果会生成一个新的Excel文件,每一行都是一对“疑似重复”,并且带上了行号,方便回去定位原始数据。如果数据量很大,我建议先在输出里加一个“重复组编号”,手动给每一组重复行分配ID,后续再用pandas按组汇总。
5.3 处理结果的输出与人工复核
自动识别出来的重复结果,千万不能直接删,一定要先做人工复核。我的习惯是把相似度在0.9以上的标成红色重点看,0.85到0.9之间的标成黄色抽检,低于0.85先不处理。这样做的原因是:文本相似度本质上是工具给的参考,真正是不是同一家客户、同一个地址,还是需要人眼确认。
另外一个实用技巧:复核时可以按“清洗后文本”排序,相似的行会自然靠在一起,肉眼检查起来效率高很多。如果你想把结果合并回原表,可以用pandas的merge,把“疑似重复行.xlsx”里的行号对应回原始数据,方便直接筛选出所有需要处理的记录。
如果数据量继续往上走,比如几十万行,两两比较也会变得不可接受。这时候我会再加一层粗筛:先按“首字符”和“长度区间”分组,只在组内做两两比较,很多明显不同的行根本不会碰面。如果还需要更快,可以使用RapidFuzz库,它有专门针对大规模数据优化的C语言实现,速度比纯Python的difflib快几十倍。
6. 常见问题与排查技巧实录
6.1 误报太多怎么办
误报指的是两行其实不是同一个东西,却被算法标成了重复。最常见原因是阈值设得太低。比如0.6的阈值下,“北京华信”和“北京华为”相似度都有0.5,很容易被误判。短文本本身包含的信息量少,稍微像一点就会给出高相似度,所以阈值要对应调高。
还有一种情况是数据清洗不够彻底。如果公司名里有“有限公司”和“有限责任公司”,清洗时没有统一,“华信有限公司”和“华信有限责任公司”可能相似度只有0.8,但如果把“有限公司”和“有限责任公司”都删掉,两者就完全一致了。所以出现误报时,先回头看看清洗规则是不是做得太粗,再决定要不要动阈值。
6.2 漏报太多怎么办
漏报往往比误报更危险,因为漏掉的数据会导致后面的统计口径不一致。漏报的常见原因有两个:一是清洗得不彻底,比如全角半角没有统一,二是真实文本确实差异很大,比如“北京华信科技有限公司”和“华信科技(北京)股份有限公司”,这种情况用字符级相似度很难查出来。
我的建议是把预处理规则做得更细。比如把“(北京)”和“北京”统一成空字符串,把“股份”“集团”这类词做成一个忽略清单。如果还想进一步提高召回率,可以尝试对中文文本按2-gram切分后再比较,这会捕捉到更多局部相似的信息,但代价是误报率也会上升,需要人工复核的时间变多。
6.3 运行太慢怎么办
慢的根源是O(n²)的两两比较。数据从5000行涨到1万行,比较次数是原来的4倍。除了前面提到的“长度差跳过”之外,我常用的优化手段是数据分组粗筛。比如把文本首字符、长度、关键词频率做成索引,先筛选出“有潜力成为重复”的小批次,再做精确比较。
下面的表格是我在实际项目中用到的排查思路速查表。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 误报太多 | 阈值过低 / 清洗不足 | 调高阈值,补充归一化规则 |
| 漏报太多 | 清洗规则不全面 | 增加全角半角、标点、后缀处理 |
| 运行太慢 | 两两比较次数爆炸 | 按长度、首字符粗筛,限制比较范围 |
| 结果错乱 | 表头混入数据 | 从第2行开始读取,固定表头区域 |
| 读取失败 | 文件格式不兼容 | 检查xls/xlsx,另存为新格式再处理 |
6.4 Excel文件读取与格式兼容问题
处理Excel时还有一个很常见的问题:明明文件能打开,但用Python或外部工具读取时却报错,比如“外部表不是预期的格式”。这种情况多半是因为文件扩展名和实际格式不一致,或者文件是从网页、PDF转换工具导出的“伪Excel”。解决方法是先打开Excel,把文件另存为“.xlsx”格式,再重新读取。
另外,从数据库导出的Excel经常会有很多格式上的坑,比如文本型数字、日期格式错乱、单元格里有大量空白。这些字段如果不先转成字符串再比较,很容易在相似度计算时出差错。所以我读数据时通常会统一加dtype=str,把所有列都当成文本处理,避免Excel自动把“001”变成“1”这种经典问题。
最后再分享一个我自己的习惯。无论数据规模多大,我在跑任何重复识别前,都会先复制一份原始表,把这次的所有操作都做成可追溯的列,比如“清洗后的文本”“相似度”“重复组号”“备注”。因为这种清理工作一旦做错,影响的是后续所有的统计和分析。宁可多标一些疑似项让人工再扫一眼,也不要让真正的重复数据漏过去。这套方法我用了很多年,从几百行的Excel到几十万行的CSV都验证过,核心思路没有变过,变的只是工具和阈值。
