版权与内容来源声明本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容均在附表 A 中标注来源引用官方原文保持原样不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式也不对任何收益结果作承诺。转载请注明出处。一、为什么这条线容易漏脱敏指的是在日志落盘、导出或外发之前把手机号、身份证号、银行卡号这类字段替换成星号或占位符的动作。绝大多数团队做这件事的方式是一样的写一条正则把「看起来像手机号」的数字换掉收工。问题不在于大家不会写正则。正则指的是用一串符号描述「什么样的字符串算命中」的规则文本会写re.sub的人很多。问题在于这条规则从来没有被证伪过——它上线之后只跑正常样本或者只在「格式整齐」的样本上跑漏了也没人知道因为脱敏漏了不会报错。1.1 第一版脚本几乎都长这样下面三行是从主代理的本机实测脚本里原样摘出来的属于已经跑过的写法不是示意importre# 摘自 work/2026-10-22_脱敏实测.py这三条是实跑过的R1re.compile(r1[3-9]\d{9})# 只认连续 11 位R2re.compile(r1[3-9]\d[- ]?\d{4}[- ]?\d{4})# 允许半角空格与短横R3re.compile(r(?:\?86)?1[3-9]\d{9})# 允许 86 前缀1[3-9]的意思是「第一位是 1、第二位是 3 到 9」后面跟九个数字。这条规则在纸面上没错中国内地手机号的号段确实落在这个范围里。但它描述的是号码本身不是号码在日志里的样子。1.2 漏的不是规则是写法分支同一批手机号在真实日志里至少有四种写法。我按这四种写法各构造了 5 条样本一共 15 条逐个喂给上面三条规则。写法各 5 条日志里的样子用哪条规则原样先归一化连续 11 位用户 13812345678 提交订单R15/55/5带半角分隔手机 159 0000 1111 待确认R10/50/5带半角分隔联系 135 1111 2222R25/55/5全角或带前缀手机R14/55/5这张表里有一行特别值得看「全角或带前缀」这一类R1 原样就能抓到 4/5。原因是这 5 条里只有 1 条真的用了全角数字另外 4 条是半角数字配中文标点、括号、86或0086前缀R1 在字符串中间照样能找到那 11 位。所以「原样 4/5」这个数字会给人一个虚假的安心感——看起来只差一点点实际上差的是整整一个写法类别。而「半角分隔」那一行是 0/5怎么调前缀、怎么加86都还是 0/5因为中间的空格和短横不在规则的胃口里。规则写得多花和覆盖了哪些写法是两件不相关的事。判据一召回先看写法覆盖不看正则写得多花。动手写规则之前先把「这套日志里出现过几种写法」列成清单四种写法就是四行样本一行都不能省。1.3 本文的样本与口径必须先把口径说清楚样本是构造样本非真实业务日志仅用于对比规则写法。6 类敏感字段各 5 条手机号连续 11 位、手机号带半角分隔符、手机号全角或带前缀、身份证 18 位、银行卡 16–19 位、邮箱另有 6 条不该被动的正常字段订单号、13 位毫秒时间戳、运单号、12 位批次号、13 位商品条码、15 位流水号。跑测环境是 Python 3.13.14仅用标准库re脚本与日志都在work/目录下任何人可以复跑。所以下面所有分母都是 5所有数字都能回到日志里逐行对上。二、六类字段的写法覆盖实测先把完整的对照表放出来。每一格是「原样匹配 → 先做全角转半角再匹配」分母都是 5。规则手机号·连续 11 位手机号·带分隔符手机号·全角或带前缀身份证·18 位银行卡·16~19 位邮箱R1 连续 11 位5/5 → 5/50/5 → 0/54/5 → 5/55/5 → 5/50/5 → 0/50/5 → 0/5R2 允许半角分隔5/5 → 5/55/5 → 5/54/5 → 5/55/5 → 5/50/5 → 0/50/5 → 0/5R3 带 86 前缀5/5 → 5/50/5 → 0/54/5 → 5/55/5 → 5/50/5 → 0/50/5 → 0/5R4 身份证 18 位0/5 → 0/50/5 → 0/50/5 → 0/55/5 → 5/52/5 → 2/50/5 → 0/5R5 银行卡 16~19 位0/5 → 0/50/5 → 0/50/5 → 0/55/5 → 5/53/5 → 3/50/5 → 0/5R6 邮箱0/5 → 0/50/5 → 0/50/5 → 0/50/5 → 0/50/5 → 0/55/5 → 5/52.1 手机号两个召回台阶分别归功于不同的动作召回指的是「该抓到的里面实际抓到了多少」。把三类手机号写法合起来算 15 条R1 朴素规则原样9/1560.0%先归一化10/1566.7%。R2 允许半角分隔原样14/1593.3%先归一化15/15全部命中。两个台阶要分开看。从 9 涨到 10 是归一化带来的涨的只有全角那 1 条从 10 涨到 15 是写法覆盖带来的靠的是规则里多认了[- ]?这两处分隔符。很多人只做了前者写了个translate或者只做了后者把规则写长所以永远停在 10/15 或 14/15。标题里说的「漏了一半」指的就是 9/15 这个起点15 条样本朴素规则原样只认了 9 条剩下 6 条一条都不会报错、一条都不会告警。2.2 身份证与银行卡位数不是判据这张表里最别扭的两行是 R4 和 R5。R4 是\d{17}[\dXx]在 5 条身份证样本上5/5。这里有一个必须写死的细节样本里有一条的末位是小写x只写大写X会漏掉它必须写[Xx]。漏掉的结果不是报错是那一条身份证原样落进了日志。R5 是\d{16,19}在 5 条银行卡样本上只命中3/56222 0212 3456 7890带空格、6217-0000-1111-2222带短横的两条全部漏掉。同时它把 5 条 18 位身份证全部吃了进来5/5——因为 18 位纯数字正好落在 16 到 19 位这个区间里。反过来的方向也一样R4身份证规则在银行卡那一列命中了 2/5说明这两条规则在互相吃。「多少位数字」只能筛出一个候选集合不能判定字段类型。这是本文后面所有锚点讨论的起点。2.3 邮箱这一条反而最稳邮箱规则[\w.-][\w-]\.[\w.]在 5 条样本上5/5本轮没有参与任何一次误判。原因不神秘它中间有一个。是一个几乎不会出现在订单号、时间戳、运单号里的字符等于规则自带了一个锚点。这个对比很值钱——凡是自带稀有分隔符的字段规则都容易写对凡是「纯数字」的字段规则都容易写歪因为纯数字的世界里没有任何天然边界。三、误判清单什么被当成了手机号误判指的是不该动的字段被动了。6 条正常字段、6 条规则交叉跑下来一共产生11 次误判。正常字段值被哪条规则误判为什么会被吃13 位毫秒时间戳1759999999999R1 / R2 / R313 位里含一段以 1 开头、第二位是 7 的 11 位运单号SF1381234567890R1 / R2 / R3字母后面那 13 位里含同样形状的 11 位15 位流水号159000011112222R1 / R2 / R315 位里含一段 1 开头、第二位是 5 的 11 位18 位订单号202610221234567890R4 / R518 位纯数字正好满足「17 位 1 位」与「16 到 19 位」12 位批次号202610220001无位数不够 16也没有 11 位以 1[3-9] 开头的连续段13 位商品条码6901234567892无位数不够 16首位是 6 不是 13.1 13 位时间戳是重灾区1759999999999这条同时被三条手机号规则命中是这轮实测里最典型的失败。它是 13 位毫秒时间戳——日志里最常见的字段之一几乎每一行都有。为什么 11 位规则能吃 13 位因为正则默认是子串搜索不是整串匹配。1[3-9]\d{9}只要求「存在这么一段」不要求「这段前后没有别的数字」。时间戳的第 1 位是 1、第 2 位是 7刚好满足1[3-9]后面再凑 9 个数字就命中了。规则本身完全没错错在它没有说「前后不许再挨着数字」。3.2 订单号同时命中两条长规则202610221234567890是 8 位日期加 10 位序列共 18 位。它同时满足 R417 位 1 位和 R516 到 19 位也就是说身份证规则和银行卡规则都会把它打成星号。这比手机号误判更麻烦订单号是排障时最主要的检索键。把订单号脱掉线上出问题时按订单号搜日志会直接搜不到人只能按时间盲翻。3.3 误删比漏删更难发现漏删是「该掩的没掩」误删是「不该掩的掩了」。这两件事在监控上的可见度完全不对称漏删日志内容看起来正常没有任何异常信号只有等审计或等出事才知道。误删日志内容变短、字段变成星号但没有人会为「日志里少了一个订单号」告警。判据四误删比漏删更该被盯住。漏删没有任何信号误删却会悄悄让日志失去排障能力而后者通常没人报警。所以判断一条规则好不好不能只看它抓到了多少敏感字段还要看它在正常字段上留了几个洞。本案的 11 次误判分布在 4 个正常字段上其中 3 个是排障必用的定位键时间戳、运单号、订单号。四、归一化与锚点两个必做的动作4.1 归一化必须在匹配之前归一化指的是在比对之前先把不同写法统一成一种形式——这里具体指全角数字转半角数字。# 摘自 work/2026-10-22_脱敏实测.py这两行是实跑过的FWstr.maketrans(,0123456789)texts.translate(FW)# 先归一化再拿 text 去匹配这一步的收益在数字上很小R1 从 9/15 到 10/15R2 从 14/15 到 15/15都只多了 1 条。但它补的是整整一个类别而且这一类是「规则怎么写都补不上」的。有个常见的误解值得点出来有人觉得\d本来就能匹配全角数字所以不用归一化。按 Python 官方文档re模块的说明\d在 Unicodestr模式下确实匹配任何 Unicode 十进制数字包括全角数字。但问题出在规则的另一半——1[3-9]里的1和3-9都是 ASCII 字面量…这一行在第一个字符就断了根本走不到后面的\d。所以把[3-9]换成\d救不了全角只有把文本先归一化才行。顺序也不能反判据二归一化必须放在匹配之前。先匹配、后归一化等于用错误的输入做了一次判断再补救已经晚了。4.2 锚点给长数字规则加前后边界锚点指的是在规则两头加上「前面必须是什么、后面必须是什么」的约束。对纯数字字段来说最有用的一种锚点是否定型数字边界前后不许紧挨着数字或分隔符。⚠️代码待验证# 本文改进写法未在主代理的实测脚本里跑过PHONEre.compile(r(?![\d\-])# 前面不能是数字或短横r(?:(?:\|00)?86[\- ]?)?# 可选国家码86 / 0086r1[3-9]\d(?:[\- ]?\d{4}){2}# 3 位号段 两组 4 位r(?![\d\-])# 后面不能是数字或短横)按这个结构前面那 11 次误判里能被推掉的是三类13 位时间戳、15 位流水号、13 位运单号里的数字部分。机制是(?![\d\-])——时间戳那 11 位后面还跟着99边界断言不成立整条匹配失败正则不会退而求其次去匹配一个短一点的子串。但18 位订单号推不掉。它本身就是 18 位纯数字前后都没有别的数字任何「前后不挨数字」的断言对它都是成立的。要区分它和身份证只能看它前面写的是「订单号」还是「身份证」——这就是下一节。另外提醒一个反向的坑加了(?![\d\-])之后008613812345678这种紧贴国家码的写法会被前面那个0挡住所以国家码部分要显式允许00前缀否则会新造一个漏点。这类「修一个漏点、长出另一个漏点」的情况只有靠用例回归才拦得住。4.3 位数不够必须看字段名前缀判据三长数字规则必须带上下文锚点。把上面的结论压成一句位数只能筛候选字段名才能定类型。同一个 18 位数字串出现在「订单号 202610221234567890」里是业务主键出现在「身份证 110101199003071234」里是敏感个人信息。正则看到的两个串长得一模一样它没有办法自己判断。能判断的信息在字段名前缀上——也就是那个 18 位数字前面若干个字符里出现的「身份证」「证件号」「卡号」「账号」这类词。工程上的做法是关键词窗口不是全行搜索而是要求敏感词出现在匹配位置之前的一个有限窗口内比如前 8 个字符。窗口不能开太大否则一行里同时有「身份证」和「订单号」时又会串味窗口也不能不开否则就等于回到纯位数判断。这里要接受一个事实关键词窗口是业务知识不是正则技巧。它依赖你对自家日志格式的了解换一个系统就得重写。这也是为什么脱敏规则没法直接从网上抄一份通用的。五、上线前的五个自检用例自检用例的价值不在于「证明规则能跑」而在于能证伪。一条用例如果怎么改规则都能过它就没有存在意义。判据五规则要能被证伪。下面五个用例是从上面 11 次误判里反推出来的每个都对准一种具体的失败模式。用例输入片段构造样本期望结果对准的失败模式同行混排用户 13812345678 下单 ts1759999999999手机号被掩时间戳原样子串误吃长数字写法混排联系 159 0000 1111备用两条都被掩分隔符与全角各漏一类末位小写证件号 11010119900307123x被掩[Xx]只写了大写长数字互吃订单号 202610221234567890 卡号 6222021234567890订单号原样卡号被掩两位数规则互相吃单号带字母邮箱 zhangsanexample.com 运单号 SF1381234567890邮箱被掩运单号原样字母前缀没进锚点⚠️代码待验证# 用例断言输出里必须出现 expected 里的每个片段顺序不限CASES[(同行混排,用户 13812345678 下单 ts1759999999999,[138****5678,1759999999999]),(写法混排,联系 159 0000 1111备用,[159****1111,138****5678]),(末位小写,证件号 11010119900307123x,[110101********123x]),(长数字互吃,订单号 202610221234567890 卡号 6222021234567890,[202610221234567890,6222********7890]),(单号带字母,邮箱 zhangsanexample.com 运单号 SF1381234567890,[z***example.com,SF1381234567890]),]写用例时有个细节容易漏期望值要写「原样保留」的那一半。只断言「敏感字段被掩了」规则把整行都打成星号也能通过把「订单号必须原样出现」写进断言误删才会变成红灯。这份资料是什么一份按「先跑通再排查」顺序整理的大模型学习路线图里面有一节专门讲工程侧的数据处理与合规动作该怎么拆步骤。放在资料包里扫码即可获取六、一张可抄的规则表下面这份清单是本文的结论汇总。「实测量」一列写的是主代理脚本在本机跑出来的结果「锚点改进」一列是本文加的属于改进写法没有实跑。字段实测量分母 5 或 15锚点改进要点手机号R2 归一化后 15/15R2 原样 14/15数字边界 显式国家码 分隔符可选身份证5/5但只写大写X会漏末位小写[\dXx] 关键词窗口位数不足以定类型银行卡3/5带空格/短横全漏反向吃身份证 5/5允许分隔符 关键词窗口 校验位邮箱5/5本轮无误判天然锚点保持简单不要过度加约束时间戳被三条手机号规则误吃不靠正则排除走豁免清单订单号 / 运单号被 R4、R5 与 R1 误吃只能靠字段名关键词豁免需业务方给字段清单⚠️代码待验证# 本文改进写法锚点 豁免未在主代理脚本中实跑需按自家日志格式调FWstr.maketrans(,0123456789)# 1) 豁免清单已知的正常长数字匹配前先挂号脱敏时跳过这些区间EXEMPTre.compile(r(?!\d)1[6-9]\d{11}(?!\d))# 13 位毫秒时间戳# 2) 四类字段长规则先跑短规则后跑避免 16~19 位吃掉 18 位身份证ID_CNre.compile(r(?![\dXx])\d{17}[\dXx](?![\dXx]))CARDre.compile(r(?![\d\-])\d{16,19}(?![\d\-]))PHONEre.compile(r(?![\d\-])(?:(?:\|00)?86[\- ]?)?1[3-9]\d(?:[\- ]?\d{4}){2}(?![\d\-]))EMAILre.compile(r[\w.-][\w-]\.[\w.])这份清单里有三处是「必须按你家的日志改」的抄的时候不要整段复制豁免清单里的时间戳位数、国家码的写法、以及银行卡前后允许哪些分隔符。我这轮样本里只有空格和短横两种分隔实际日志里可能还有括号、点号、斜杠。6.1 掩码留多少留可定位的部分掩码指的是用星号等字符替换敏感片段。留几位是有讲究的留得太少客服核对不了留得太多等于没脱敏。字段建议保留掩码后保留的理由手机号前 3 后 4138****5678前 3 位看号段后 4 位供本人核对身份证前 6 后 4110101********1234前 6 位是地区码排障时常按地区筛银行卡后 46222********7890后 4 位是用户报卡时唯一会说的信息邮箱首字母 域名z***example.com域名能定位到企业客户订单号 / 时间戳全部保留原样它们是排障主键脱了就没法查6.2 顺序与幂等执行顺序会直接影响结果\d{16,19}会吃掉 18 位身份证所以身份证规则必须在银行卡规则之前跑或者在银行卡规则里排除已经被身份证规则占用的区间。幂等指的是同一段文本处理两次结果和一次一样。掩码用*替换数字之后第二次跑时那些位置已经不是数字了不会二次命中所以天然幂等。但如果某天有人把掩码改成「保留位数、把数字换成 0」第二次跑就会因为位数没变而再掩一次长度悄悄变短。这种改动必须在用例里回归。七、收尾把脱敏当接口契约7.1 契约要写清三件事《中华人民共和国个人信息保护法》第五十一条第三项要求个人信息处理者「采取相应的加密、去标识化等安全技术措施」。去标识化指的是让信息在不借助额外信息的前提下无法指向特定个人。脱敏脚本就是这条要求最直接的落地点。但落到工程上脱敏不该被当成一个脚本而该被当成一份接口契约。契约至少要写清三件事输入写法清单这套日志里手机号有几种写法、身份证会不会带小写 x、卡号会不会分组。这份清单要由写日志的人确认不是脱敏的人猜。输出掩码格式每个字段保留几位、用什么字符掩。这决定了排障的人还能不能干活。豁免字段清单哪些长数字是正常字段绝不能动。这份清单是误判的唯一解药。7.2 规则要有版本号也要有回归最后一条判据规则文件要带版本号日志落盘处要记下用的是哪一版。理由是误判的形态会变。今天被误吃的是 13 位时间戳明天业务加了一个 19 位的内部单号同样会被\d{16,19}吃掉。如果日志里没有版本信息出问题时你无法判断「这条日志里的订单号为什么变成星号」是新规则造成的还是三个月前就有的老问题。回归的方式很简单把第 5 章那五个用例做成每次改规则都跑的检查再加一条「正常字段不得被掩」的断言。用例不需要多需要的是每次都会跑。这份资料是什么在线可查的大模型知识库里面有 Agent Skills 与工程化落地的专题目录适合用来对照本文这种「一条规则怎么变成可回归的接口」的写法。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表事实出处本文位置全部命中与误判数字9/15、10/15、14/15、15/15、11 次误判、5/5、3/5本机实测work/2026-10-22_脱敏实测.py日志work/2026-10-22_脱敏实测.logPython 3.13.14仅标准库 re第 2、3 章手机号 15 条样本按「连续 / 带分隔符 / 全角或带前缀」三类各 5 条同上脚本的 SAMPLES 字典1.2、2.1R1 原样 9/1560.0%归一化后 10/1566.7%同上日志「手机号召回」段2.1R2 原样 14/1593.3%归一化后 15/15同上日志「手机号召回」段2.1、4.16 条正常字段共 11 次误判批次号与商品条码未被误判同上日志「正常字段误判」段第 3 章身份证规则 5/5末位小写 x 需[\dXx]才命中同上日志「其他关键点」段2.2银行卡规则 3/5且吃掉 5 条 18 位身份证同上日志「其他关键点」段2.2邮箱规则 5/5本轮无误判同上日志命中矩阵2.3\d在 Unicodestr模式下匹配任何 Unicode 十进制数字含全角Python 3 文档 re 模块Python Software Foundationhttps://docs.python.org/3/library/re.html4.1\b为词边界「词」在 Unicode 模式下指 Unicode 字母数字加下划线同上附表 B第五十一条第三项采取相应的加密、去标识化等安全技术措施《中华人民共和国个人信息保护法》全文中央网络安全和信息化委员会办公室 2021-08-20 发布https://www.cac.gov.cn/2021-08/20/c_1631050028355286.htm7.1锚点版正则、豁免清单、关键词窗口为本文改进写法未在本机脚本中实跑本文作者改写对应代码块上方已标注待验证提示4.2、6.1附表 B术语速查表术语一句话解释在本文哪里用到脱敏把敏感字段替换、遮蔽后再落盘或外发的动作第 1 章开头正则用一串符号描述「什么样的字符串算命中」的规则文本1.1召回该抓到的里面实际抓到了多少2.1误判不该动的正常字段被动过了第 3 章归一化比对之前先把不同写法统一成一种形式这里指全角转半角4.1锚点在规则两头加「前后必须是什么」的约束4.2负向断言锚点的一种写法要求「前后不能是什么」如(?!\d)4.2关键词窗口要求敏感词出现在匹配位置前方有限字符数内的约束4.3豁免清单已知的正常长数字匹配前先挂号脱敏时跳过6.1掩码用星号等字符替换敏感片段的做法6.1幂等同一段文本处理两次结果和一次一样6.2去标识化让信息在不借助额外信息的前提下无法指向特定个人7.1写在最后这篇用到的资料写这篇文章时把相关的官方文档和源码又翻了一遍顺手也整理了几份配套的东西大模型学习路线图从零基础到能自己动手做 Agent按阶段说明每一步该学什么、哪些可以先跳过《LangChain LangGraph MCP 智能体开发实战》视频课7 个模块从私有化部署、EmbeddingRAG 到 MCPAgent 全流程AI 大模型知识库在线可查Agent Skills 从入门到落地、Claude Skills 完全指南等专题按目录浏览即可640 套 AI 大模型行业报告 经典 PDF 书籍看行业落地案例和别人怎么做的时候用得上大模型零基础到精通教学视频跟着敲一遍比只读文档快得多资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「AI」优先通过。资料按「先路线、再动手、最后查漏」的顺序整理好了建议先看学习路线那一份照着它挑一条适合自己当前基础的路径再往下看。
阅读完成 · 觉得有帮助?