上个月帮朋友处理一张两千多行的客户名单,任务是找出Excel里的重复文本行。我原本以为这活儿交给Excel自带的“删除重复项”几下就能搞定,结果点完以后愣住了——完全一致的重复倒是清掉了,但那些真正让人头疼的“近似重复”,比如“张三(李四代签)”和“张三 李四代签”,一条都没查出来。后来我花了两个晚上,把数据清洗、公式、VBA、Python方案全过了一遍,才算把这一类问题彻底理清楚。
这篇东西就是来聊聊:在Excel里找完整重复和近似重复的文本行,到底有哪些靠谱的路子,每种路子适合什么场景,以及那些文档里不会写、但实操里一定会遇到的坑。不管你是只会几个基础函数、打算用VBA硬刚,还是想用Python批量处理,下面都有能直接抄作业的代码和步骤。
1. 一行重复的背后:我需要处理的三种情况
很多人一听到“重复文本行”,第一反应就是“两行内容一模一样”。但真实数据里,尤其是从各个系统导出来、多人协作填写、或者手工汇总出来的表格,所谓的“重复”远远更复杂。我把自己实际遇到过的重复情况归纳成了三类。
1.1 完全重复:最简单的那种
完全重复就是两行文本一模一样,字符、顺序、空格、标点都相同。这种最简单,Excel自带的删除重复项、条件格式,或者一个COUNTIF公式,几秒钟就能找出来。
比如下面这两行:
code复制A001 张三 13800138000
A001 张三 13800138000
这就是完全重复,处理起来没有任何悬念。可问题是,真实工作里完全重复往往只占一小部分,更常见的是下面这种。
1.2 隐形差异:看上去一样,实际上不一样
很多时候,两行文本肉眼看上去完全一样,但Excel里面就是找不到重复。原因在于“看上去一样”和“实际上相同”之间,隔了一堆看不见的差异:
- 全角半角不同:
ABC和ABC肉眼看起来差别不大,但对Excel来说就是不同字符串。 - 空格不同:中文和英文之间有没有空格,行尾有没有空格,中间是全角空格还是半角空格,都会导致判断不一致。
- 标点符号不同:中文逗号“,”和英文逗号“,”,中文括号“()”和英文括号“()”,很难一眼看出区别。
- 大小写不同:英文字母A和a,对于Excel的精确匹配来说不一样。
- 隐藏字符:从网页或PDF复制过来的文本,经常会夹带换行符、制表符、甚至看不见的Unicode字符。
这也就是为什么“看着就是重复”却怎么都查不出来的真正原因。处理这类重复,第一步不是去重,而是把文本“洗干净”,让同样的内容变成同样的字符串。
1.3 近义重复:需要算法出马的“模糊重复”
第三种是真正的“近似重复”或“语义重复”。两行文本内容不完全一样,但指向的其实是同一个东西、同一个人、同一件事。
典型的例子:
code复制张三 13800138000 北京朝阳
张三 1380013800 北京朝阳区
再比如:
code复制深圳市南山区科技园南路
深圳南山区科技园南街
这种重复没有固定的规则,可能少了一个字、多了一个词、顺序颠倒、或者用了同义词。准确处理这种重复,需要在文本清洗之后,再用相似度算法(比如编辑距离、相似度比值)来判断“两行到底有多像”。这也是这篇博文里最想展开的内容。
1.4 先判断你的需求属于哪一种
动手之前先花几分钟盘点一下,你的数据里重复主要是哪一类:
- 如果是系统导出的数据,重复大多是“完全一致”——直接上删除重复项。
- 如果是多人填写的表格,重复大多是“隐形差异”——需要清洗后再去重。
- 如果是长期积累、人工维护的名单,重复大多是“近似重复”——清洗之后还必须上相似度算法。
判断错了方向,后面工具选得再好也白搭。我见过不少人拿着模糊匹配的算法去处理完全重复的数据,结果又慢又容易误杀;也有不少人用精确去重去处理近似重复,结果等于白干。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 精确重复:Excel自带功能与公式方案的边界
先把最简单的说透。如果你确定数据里都是完全重复,或者已经做好了清洗,那么用Excel自带功能最多半小时就能搞定。
2.1 30秒搞定完全重复:条件格式和删除重复项
第一种方式是条件格式,适合“我只想知道哪些行重复了,但我不想删”。
操作步骤:
- 选中需要检查的文本所在区域,比如A1:A2000。
- 点击“开始”选项卡里的“条件格式”。
- 选择“突出显示单元格规则” -> “重复值”。
- 点击确定,重复的单元格会被标成默认的浅红色。
这个方法肉眼看起来非常直观,但注意它只能帮你标出来,后面的操作还得你自己决定。而且条件格式默认基于“单元格内容完全相同”来判断,不管格式差异。
第二种是“删除重复项”,适合“我确定要保留唯一值”。
操作步骤:
- 选中包含文本的数据区域。
- 点击“数据”选项卡里的“删除重复项”。
- 在弹出的对话框里选择你判断重复所依据的列。
- 点击确定,Excel会弹出提示告诉你删除了多少行、保留了多少行。
很多人忽略的一点是:删除重复项默认保留第一个出现的值,后面的重复行都会被删掉。如果你希望保留最后一条记录,用这个功能就不合适了,得先把数据倒序排一下。
2.2 COUNTIF辅助列:能定位重复,但有几个坑
如果你不想破坏原始数据,又想多一点控制权,COUNTIF辅助列是个经典做法。
在B2单元格输入下面这个公式:
code复制=COUNTIF(A:A, A2)
然后下拉填充。这一列显示的是“当前行文本在整列中出现了几次”。任何大于1的值,都意味着当前行文本有重复。
更进一步,在C2输入:
code复制=IF(COUNTIF(A$2:A2, A2)=1, "首次出现", "重复出现")
这样可以把“第一次出现”和“后面的重复”区分开,方便你决定保留谁、删除谁。
但COUNTIF有两个明显的坑,必须提醒一下:
- COUNTIF对超过255个字符的文本会失效,返回#VALUE!错误。如果单元格里是长文本,用COUNTIF会漏掉重复。
- COUNTIF默认区分全角半角、区分大小写、区分首尾空格,所以在清洗之前用它,很多“肉眼重复”不会被识别成重复。
2.3 边界在哪:为什么“看起来重复”它管不了
Excel自带的这些功能,本质上全都是在做“逐字符精确比较”。也就是说,只有当两个字符串的所有字符完全一样时,才认为是重复。
问题来了:如果两条文本里有一个空格不同、一个标点不同、或者一个错字,Excel自带的精确去重就完全失效了。这种情况下你需要的不是“精确匹配工具”,而是“先清洗、再比较”的一套流程。
换句话说,Excel自带的这些功能只是“查重工具箱”里的第一层。它们解决不了“近似”,也解决不了“隐形差异”。把这些边界搞清楚,你才知道什么时候该换工具,什么时候该写代码。
3. 近似重复的“脏数据”根因:不是Excel不给力,是数据太乱
我之前一度以为“近似重复”是Excel的功能短板,直到有一次我逐条对比了几百条“疑似重复”的数据才发现,问题根本不在工具,而在数据本身。脏数据是怎么产生的?总结下来无外乎下面这几种来源。
3.1 手工录入导致的常见噪声
人不是机器,手工输入的时候一定会引入各种不一致:
- 有人习惯打“北京朝阳区”,有人习惯打“北京朝阳”;
- 有人写“深圳市南山区”,有人写“深圳南山”;
- 有人用全角括号“(张经理)”,有人用半角括号“(张经理)”;
- 有人名字后面带个“先生”,有人不带。
这些差异在人工看的时候完全不影响理解,但对计算机来说是实打实的“不同字符串”。这类噪声无解,只能靠清洗规则去统一。
3.2 复制粘贴带进来的隐藏字符
另一个特别常见但又特别隐蔽的来源,是从网页、PDF、Word、聊天记录里复制粘贴。
举个例子,从Excel的单元格里复制一段文本,再到Word里粘贴,看起来一切正常。但如果你从网页复制一段文字进Excel,经常会在每个换行处带进一个不可见的回车符,甚至是一堆Unicode控制字符。这些字符你用肉眼根本看不到,但COUNTIF、VLOOKUP、条件格式全都把它当成不同的内容。
我以前处理过一批从企业微信聊天记录里导出的名单,里面几乎每一行末尾都带着一个看不见的换行符。当时用Excel的精确去重,发现几乎没有任何重复,但把换行符清理掉之后,一下子冒出来几十条重复。
3.3 表格结构混乱:合并单元格、换行符、数字格式
还有一个容易踩的坑,是表格本身的结构问题。
- 合并单元格:一个单元格里同时包含“姓名+电话+地址”,这种情况在导出数据里特别常见,重复判断就比单列字段复杂得多。
- 单元格内部换行:Alt+Enter在单元格里造成的换行,Visual看起来是换行了,但实际上整个单元格还是一个字符串。比较的时候,换行符的存在会让两个“肉眼相同”的单元格被判定为不同。
- 数字格式:电话号码、身份证号如果被Excel自动转成了科学计数法,或者数字后面被自动加了“.0”,文本看起来就完全不一样了。比如 13800138000 被展示成 1.38001E+10,就会让重复判断崩溃。
3.4 业务层面的重复:同一对象多条不同记录
还有一些重复,不是“字符串层面的重复”,而是“业务意义上的重复”。
比如一个人换了手机号,旧记录和新记录都存在;一家公司改了名称,老名称和新名称在名单里都有。这种情况下,两条文本完全不一样,但这个对象实际上是同一个人。
这类重复靠字符串比较解决不了,必须结合业务规则、时间字段、关联字段来综合判断。如果你要处理的是这种“业务重复”,我的建议是:先做好文本清洗和精确去重,再去人工核对候选集。算法可以帮你缩小范围,但最终判断还得靠人。
4. 先归一化再去重:清洗数据的完整实操
理解了脏数据怎么来的,再来看怎么洗。我把这套流程叫“归一化”:把各种写法统一的文本,处理成同一种标准形式,然后再去做比较。这是处理近似重复之前,必须走的一步。
4.1 归一化到底在做什么
归一化的核心目标只有一个:让“相同含义的文本”变成“完全相同的字符串”。具体包含这几件事:
- 去除首尾空格和多余的空格。
- 把全角字符转成半角字符。
- 统一大小写。
- 去掉隐藏的换行符、制表符、不可见字符。
- 把中文标点统一成英文标点,或反过来全统一成一种。
- 如果有特定业务规则,再统一简称、去除量词、等地名后缀等。
做完这一步,你会发现很多原本“看起来重复但去不掉”的重复,全都浮出水面了。
4.2 用VBA写一个能扛住大部分场景的清洗函数
如果你经常处理Excel数据,掌握一点VBA会让效率提高几个量级。下面这段VBA代码,把上面说的归一化规则全都封装成了一个自定义函数,可以直接在Excel里当公式用。
打开VBA编辑器(Alt+F11),插入一个模块,粘贴以下代码:
vba复制Function CleanText(ByVal inputStr As String) As String
Dim i As Integer
Dim outStr As String
Dim ch As String
' 1. 去首尾空格
inputStr = Trim(inputStr)
' 2. 全角转半角
For i = 1 To Len(inputStr)
ch = Mid(inputStr, i, 1)
' 全角空格 U+3000 -> 半角空格
If ch = ChrW(12288) Then
ch = " "
ElseIf AscW(ch) >= 65281 And AscW(ch) <= 65374 Then
' 全角字符统一平移33
ch = ChrW(AscW(ch) - 65248)
End If
outStr = outStr & ch
Next i
' 3. 去除换行符和制表符
outStr = Replace(outStr, Chr(10), "")
outStr = Replace(outStr, Chr(13), "")
outStr = Replace(outStr, Chr(9), " ")
' 4. 多个空格压缩为一个,去掉首尾空格
Do While InStr(outStr, " ") > 0
outStr = Replace(outStr, " ", " ")
Loop
outStr = Trim(outStr)
' 5. 统一英文大小写
outStr = StrConv(outStr, vbLowerCase)
' 6. 中文标点改英文(按需加,这里以逗号、括号、句号为例)
outStr = Replace(outStr, ",", ",")
outStr = Replace(outStr, "(", "(")
outStr = Replace(outStr, ")", ")")
outStr = Replace(outStr, "。", ".")
CleanText = outStr
End Function
使用方式:在你数据的旁边新建一列,输入 =CleanText(A2),往下拉。这一列就是“清洗后的标准文本”。
几个提醒:
AscW(ch) >= 65281 And AscW(ch) <= 65374是全角转半角的经典判断区间,能覆盖所有全角可打印字符。StrConv(outStr, vbLowerCase)只处理英文,对中文没有影响。- 中文标点替换那一步你可以根据自己的数据情况增删。比如有“天津市”和“天津”这类简称统一需求,也可以写几个Replace语句去处理。
4.3 公式党的替代方案:辅助列清洗
如果你不想用VBA,也可以用Excel函数直接做归一化。虽然长一点,但胜在不用开启宏、不用保存为xlsm格式。
假设原始文本在A2,在B2输入下面这个“一条龙”公式:
code复制=SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(TRIM(A2),",",","),"(","("),")",")")," ","")
这只是最简单的演示。实际情况中,要处理全角转半角,公式法会非常冗长。我的建议是:如果只是偶发处理一次,用公式抽空也行;如果这个需求你每个月都会遇到,尽早学一点VBA更划算。
4.4 清洗后如何做精确去重
清洗完之后,接下来的去重就简单了。把“清洗后的文本”这一列复制成值,然后对这一列做删除重复项,或者用COUNTIF判断哪些行重复。
这里有一个我必须强调的操作顺序:一定要先复制成值,再删掉原来那列。不然你把清洗列的公式删掉之后,重复判断的结果也就跟着没了。
我就是在这个环节踩过坑,清洗列直接做了删除重复项,删完才发现公式引用的原列也被删了,等于白干。后来学乖了,永远是“先复制粘贴为值,再删除中间列”。
5. 相似度判断:让“近似重复”无所遁形
清洗完成之后,有一部分重复已经能查出来了。剩下的才是真正的“近似重复”:清洗完了依然不完全一样,但明显指向同一个东西。这时候就需要上相似度算法了。
5.1 相似度算法:一个简单可靠的选择
对于短文本来说,最常用也最好理解的算法是“编辑距离”,也叫Levenshtein距离。它的含义是:把一个字符串变成另一个字符串,最少需要多少次“插入、删除、替换”操作。
比如“北京朝阳”到“北京朝阳区”,只需末尾插入一个“区”字,编辑距离就是1。直观上,这两条文本非常相似。
用编辑距离算一个相似度比值:
code复制similarity = 1 - 编辑距离 / max(两个文本的长度)
比如“北京朝阳”长度为4,“北京朝阳区”长度为5,编辑距离为1,相似度就是 1 - 1/5 = 0.8,也就是80%相似。
实战中怎么用这个值?设定一个阈值。我的经验是:
- 相似度 0.95 以上,基本都是同一条记录。
- 0.85 ~ 0.95,极有可能是同一条记录,建议人工核对。
- 0.70 ~ 0.85,有可能是,交给人工抽检。
- 0.70 以下,通常可以视为不同。
但阈值不能一刀切,要根据你的数据情况调整。短文本(比如5个字以内)和长文本(比如50个字以上)的相似度分布差异很大。我处理客户姓名的时候,阈值定得比较高;处理地址的时候,阈值就适当放低了一些。
5.2 VBA实现编辑距离判断
下面这段VBA代码实现了一个编辑距离函数,你可以用它来算两行的相似度。
vba复制Function Levenshtein(s1 As String, s2 As String) As Integer
Dim i As Integer
Dim j As Integer
Dim cost As Integer
Dim n As Integer
Dim m As Integer
Dim d() As Integer
n = Len(s1)
m = Len(s2)
If n = 0 Then
Levenshtein = m
Exit Function
End If
If m = 0 Then
Levenshtein = n
Exit Function
End If
ReDim d(0 To n, 0 To m)
For i = 0 To n
d(i, 0) = i
Next i
For j = 0 To m
d(0, j) = j
Next j
For i = 1 To n
For j = 1 To m
If Mid(s1, i, 1) = Mid(s2, 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
Levenshtein = d(n, m)
End Function
Function Similarity(s1 As String, s2 As String) As Double
Dim dist As Integer
Dim maxLen As Integer
dist = Levenshtein(s1, s2)
maxLen = Application.WorksheetFunction.Max(Len(s1), Len(s2))
If maxLen = 0 Then
Similarity = 1
Else
Similarity = 1 - dist / maxLen
End If
End Function
在C2输入 =Similarity(B2, B3),就能看到第2行和第3行的相似度。
但VBA算编辑距离有一个致命缺点:慢。2000行的数据两两比较,要算约200万个组合,每组合都走一次O(n*m)的循环,Excel会卡到怀疑人生。所以这种双层循环的用法,只适合几百行以内的小批量数据。
5.3 Python方案:批量识别更高效
如果你需要处理几千上万行数据,Python是更好的选择。下面这个脚本用pandas读取Excel,利用difflib库计算相似度,把相似度超过阈值的行对都列出来。
python复制import pandas as pd
from itertools import combinations
from difflib import SequenceMatcher
# 读取Excel的指定列
df = pd.read_excel("客户名单.xlsx", sheet_name="Sheet1", dtype=str)
# 如果之前做了清洗,这里直接用清洗后的列
texts = df["清洗后文本"].fillna("").tolist()
# 相似度阈值
threshold = 0.85
results = []
# 两两比较
for i, j in combinations(range(len(texts)), 2):
# 长度差太远的直接跳过,可以省很多时间
len_i = len(texts[i])
len_j = len(texts[j])
if abs(len_i - len_j) > max(len_i, len_j) * 0.5:
continue
sim = SequenceMatcher(None, texts[i], texts[j]).ratio()
if sim >= threshold:
results.append((i + 2, j + 2, texts[i], texts[j], round(sim, 4))) # +2 是因为表头一行
# 输出到控制台,也可以写入新Excel
if results:
pd.DataFrame(results, columns=["行号1", "行号2", "文本1", "文本2", "相似度"]).to_excel("疑似重复.xlsx", index=False)
print(f"发现 {len(results)} 对疑似重复")
else:
print("未发现疑似重复")
这里我故意加了一个长度过滤:如果两条文本的长度差已经超过较长文本的一半,就没必要算相似度了。这能大幅减少计算量。
用diffib的SequenceMatcher,底层比纯编辑距离要精细一些,对小改动更敏感,处理中文也比我自己写的编辑距离函数更稳。
5.4 处理大批量数据时的优化思路
如果你的数据上了几万行,两两比较会爆炸,几万行意味着上亿次比较,任何脚本都跑不动。这时有几个思路可以绕过去:
- 分组分桶:先按首字母、关键词或长度分组,只在组内两两比较。比如地址类的数据,先按“省+市”分组,再比较同组内部的文本。
- 用索引去重:先用精确去重把一模一样的清掉,只对剩下的唯一值做两两比较。
- 用专业的工具:比如
pandas-dedupe、recordlinkage这类Python库,里面封装了更专业的相似度匹配算法,支持分块、索引和机器学习辅助。普通需求不必用到这个级别,但真到大批量脏数据,它们比你自己写循环靠谱得多。
6. 方案选型与避坑:不同规模数据怎么落地
讲完所有技术手段,最后聊聊怎么落地,以及在实操中容易踩的坑。
6.1 三种方案的适用场景对比
| 数据量 | 需求类型 | 推荐方案 | 理由 |
|---|---|---|---|
| 几百行 | 完全重复 | Excel自带删除重复项 | 最快,不用写代码 |
| 几百行 | 隐形差异重复 | 先清洗,再去重 | 清洗后大部分重复会自动暴露 |
| 几百至一千行 | 近似重复 | VBA编辑距离 | 不用安装额外软件,Excel内直接跑 |
| 几千至几万行 | 近似重复 | Python脚本 | 性能和可控性都远强于VBA |
| 几万行以上 | 近似重复 | Python分桶/专用匹配库 | 两两比较已经不可行 |
这个表格给的是我个人的经验值,实际情况仍要按数据形态灵活调整。判断标准就一条:先算一下最坏情况下需要比较多少次,如果超过百万次,建议不要用VBA双层循环。
6.2 操作前必须做的三件事
无论你选了哪个方案,动手之前先做三件事,能省掉后面80%的麻烦:
第一,备份原始文件。这话听起来像废话,但我知道很多人都不做。我见过拿着删除重复项直接“删坏”一份核心名单的,最后只能靠历史版本来还原。处理数据之前,先Ctrl+C复制一份副本。
第二,确认表头和数据范围。批量脚本里一旦把表头当成数据算进去,不仅结果全错,还可能误删。Python脚本里我用 +2 来调整行号,就是因为第1行是表头,第2行才是第一条数据。
第三,先小范围测试。先用50行数据跑一遍流程,确认结果符合预期,再对全量数据操作。这个习惯能帮你挡掉很多低级错误。
6.3 我踩过的几个坑
这里挑几个比较有代表性的坑来说。
第一个坑是COUNTIF的255字符限制。当时我处理一批留言文本,每条都好几百字,用COUNTIF直接就#VALUE!了。后来换了辅助列用VBA清洗,再配合Python做相似度,才算解决。
第二个坑是“先删后洗”。我一开始图省事,直接对原始列做删除重复项,结果“张三 ”和“张三”这种带空格差异的重复没被处理,重复数据该在的还在。后来我把顺序调整成“先清洗->再判断->再删除”,这个问题才消失。
第三个坑是VBA双层循环跑死。那一次数据量大概1800行,虽然不算特别大,但两两比较组合数已经接近160万,VBA跑了十几分钟还没结束,最后Excel直接无响应。后来我做了两件事:先精确去重把唯一值降到800行左右,再把比较逻辑改成Python里的分桶比较,几分钟就出来了。
第四个坑是相似度阈值拍脑袋定。一开始我直接定了0.8,结果发现一批长度很短的地址文本,因为字符少、改动一两个字,相似度就掉到0.5以下,漏掉了很多重复。后来我针对不同字段单独调阈值,姓名类用0.9,地址类用0.75,效果好很多。
6.4 扩展思路:重复行之外还能做什么
掌握了清洗和相似度比较,你能做的不只是找重复。这套技能可以顺带解决很多类似问题:
- 数据合并:把同一客户的多次记录、多个联系方式合并成一条。
- 标准化:把“北京市”和“北京”统一成一种写法,为后续数据分析打好基础。
- 异常检测:当一条记录的相似度与所有其他记录都很低时,往往是拼写错误或格式异常,值得重点检查。
我自己现在处理表格,已经习惯了这套“先清洗、再精确去重、再按需做相似度判断”的流程。比起一上来就选一个工具硬扛,这个流程让我少走很多弯路。
最后再分享一个细节:无论你用VBA还是Python,处理文本之前把所有单元格先统一设置成文本格式,再导入数据。Excel自动把长数字转成科学计数法的问题,不知道坑了多少人。如果你要处理的是电话号码、身份证号、订单号这类长数字文本,这一步尤其重要。先把这关过了,后面的所有操作都会顺畅很多。
