两个文本文件找不同:一个Python脚本输出所有差异行(实测版)

发布于 2026-10-06 · 本文所有数据均为脚本真实运行输出

改完一份产品说明、合同、报告,想知道到底改了哪几行?对方回过来的 Word 只说"小改了几处"?把两版各存成 txt,这个脚本 10 秒列出所有差异行,不用装 Beyond Compare。

脚本(实测通过,可直接复制)

# -*- coding: utf-8 -*-
"""比较两个文本文件,输出所有不同行(带行号)。

用法: python3 text_diff.py before.txt after.txt [输出文件] [--keep-ws]
默认忽略行首尾空白和空行(适合对比 Word 导出/复制的文本);
加 --keep-ws 则逐字符严格比较(适合代码、合同条款等缩进有意义的文本)。
"""
import sys, difflib

def read_lines(path):
    data = open(path, 'rb').read()
    for enc in ('utf-8-sig', 'gbk'):
        try:
            return data.decode(enc).splitlines()
        except UnicodeDecodeError:
            continue
    return data.decode('utf-8', errors='replace').splitlines()

def norm(lines, keep_ws, ignore_blank):
    out = []
    for l in lines:
        l = l.rstrip('\r\n')
        if not keep_ws:
            l = l.strip()
        if ignore_blank and not l:
            continue
        out.append(l)
    return out

def main(f1, f2, out='diff_result.txt', keep_ws=False):
    a = norm(read_lines(f1), keep_ws, not keep_ws)
    b = norm(read_lines(f2), keep_ws, not keep_ws)
    d = difflib.unified_diff(a, b, fromfile=f1, tofile=f2, lineterm='', n=1)
    out_lines = list(d)
    with open(out, 'w', encoding='utf-8') as f:
        f.write('\n'.join(out_lines) + '\n')
    changed = sum(1 for l in out_lines if l[:1] in '+-' and not l.startswith(('+++','---')))
    mode = '严格' if keep_ws else '忽略空白'
    print(f"[{mode}] 差异 {changed} 行,已写入 {out}")
    for l in out_lines:
        print(l)
    return changed

if __name__ == '__main__':
    args = [a for a in sys.argv[1:] if not a.startswith('--')]
    keep_ws = '--keep-ws' in sys.argv
    f1, f2 = args[0], args[1]
    out = args[2] if len(args) > 2 else 'diff_result.txt'
    main(f1, f2, out, keep_ws)

实测 1:改过的产品说明

构造 before / after 两版文本(改版本号、换电话、删地址、改价格、加新品、加条款):

$ python3 text_diff.py before.txt after.txt
[忽略空白] 差异 9 行,已写入 diff_result.txt
--- before.txt
+++ after.txt
@@ -1,9 +1,10 @@
-产品说明v1
+产品说明v2(已更新)
 联系人: 张三
-电话: 13800000000
-地址: 北京市朝阳区某路1号
+电话: 13900000000
 价格表
-A 10
+A 12
 B 20
+C 33
 合同条款
 双方协商
+仲裁

每处改动一行 -/+,未变的行原样带出上下文。diff_result.txt 直接发群里或存档。

实测 2:乱序文件 —— 不是脚本笨,是真不一样

把 after 的行整个倒序存成 after_shuffled.txt 再跑:

$ python3 text_diff.py before.txt after_shuffled.txt diff2.txt
[忽略空白] 差异 17 行

内容"其实一样"但行序不同 → 17 行差异。difflib 是按行序对比的,要"内容集合级"对比得先 sort,两种需求别搞混。

实测 3:同一文件重跑,输出完全一致(幂等)

两次运行 diff_result.txt 逐字节相同,脚本无副作用,可放心反复跑。

实测中踩到的 4 个真坑

  1. 尾随空格制造"假差异":「你好」vs「你好 」逐字符是两行不同。默认模式做了 strip()(实测这类假差异归零);但对比代码/缩进有意义的文本要加 --keep-ws——实测该模式下 2 处空格差异被正确检出。
  2. Windows 换行 CRLF:Windows 来的文件每行结尾是 \r\n,不处理的话整份文件"全部不同"。用 splitlines() + rstrip,实测 CRLF 文件与 LF 文件对比差异 0 行,正确。
  3. GBK 编码文件直接崩:Windows 记事本存的 ANSI 文件是 GBK,用 open(..., encoding='utf-8') 读会抛 UnicodeDecodeError(实测复现)。脚本按 utf-8-sig → gbk 顺序尝试解码,实测 GBK 文件与同内容 UTF-8 文件对比差异 0 行。
  4. 别拿 diff 行数当"相似度":unified_diff 带上下文行,输出行数多≠文件差别大。看"到底几处不同"以脚本统计的 -/+ 行数为准,本页统一以"差异 N 行"口径为准。

更多办公自动化实战: 博客合集(50+ 篇) · 服务清单

下单代做 →