文本处理 本地计算 开箱即用 内置示例 不留痕迹

零宽字符检测与清理

从网页、PDF、聊天窗口复制来的文字里,常常夹着几个「看不见的字符」:长度统计对不上、搜索搜不到、代码编译报奇怪的错,都是它们造成的。本工具逐字符扫描并列出每个不可见字符的类型与位置,清理时只删这些字符,其余一个不动。

从网页、PDF、聊天窗口复制来的文字里常夹着看不见的字符:零宽空格、零宽连字、字节序标记等。它们会带来一串实际麻烦 —— 字数统计对不上、搜索明明有这段却搜不到、代码编译报奇怪的语法错误、数据库里长度比看上去长。这个工具逐字符扫描,列出每个不可见字符的类型与位置。

清理时只删这些不可见字符,其余内容一个不动,所以清理后「看起来没变」是正常的(它们本来就看不见)。有两类字符不在清理范围内:表情符号后的变体选择符(如心形、对勾后面的码位)与正常的中文标点 —— 删掉它们会改变显示效果。

使用步骤

  1. 把文本粘进输入框,扫描结果会列出每个不可见字符的类型与行列位置。
  2. 对照位置列表回原文确认来源(常见是网页复制、Word 粘贴或接口返回)。
  3. 点清理,只移除不可见字符,其余内容保持不变。
  4. 复制结果替换原文;若清理的是代码或配置,替换后再跑一次编译或校验。

计算原理与示例

覆盖哪些不可见字符

扫描范围是三类共 19 个码位:一是零宽格式符,包括零宽空格 U+200B、零宽不连字 U+200C、零宽连字 U+200D、词连接符 U+2060;二是字节序标记 U+FEFF、软连字符 U+00AD、蒙古文元音分隔符 U+180E、阿拉伯字母标记 U+061C;三是双向文本控制符 U+200E / U+200F 与 U+202A~U+202E、方向隔离符 U+2066~U+2069。第三类平时很少见,但它们会悄悄改变一段文字在界面上的显示顺序。

为什么变体选择符不在清理范围

U+FE0E 与 U+FE0F 这两个变体选择符刻意不清理。它们不是「多余的隐身字符」,而是有实际作用的修饰符:文本选择符 U+FE0E 让某些符号按黑白文字形式显示,emoji 选择符 U+FE0F 让心形、对勾等符号按彩色 emoji 显示。把它们删掉会直接改变正文外观,因此只提示、不清理。

位置列表怎么用来定位

结果区会给出三样东西:清理后的干净文本(可一键复制)、把每个不可见字符替换成 {ZWSP} 这类标记的「标记版」(用来在长文里一眼看到它藏在哪),以及位置列表——每行给出类型、第几个字符、第几行第几列。行列都从 1 开始数,列按字符数计算,可以直接照着去原始文本里定位。位置超过 200 处时只列前 200 处并给出提示。

代码示例

JavaScript 检测与清理

// 三类常见不可见字符
const ZERO_WIDTH = /[\u200B\u200C\u200D\u2060\uFEFF\u00AD\u200E\u200F]/g;

const scan = (s) => {
  const hits = [];
  [...s].forEach((c, i) => {
    if (ZERO_WIDTH.test(c)) hits.push({ i, code: 'U+' + c.codePointAt(0).toString(16).toUpperCase() });
    ZERO_WIDTH.lastIndex = 0;   // 全局正则要复位,否则 test 会跳字符
  });
  return hits;
};

const clean = (s) => s.replace(ZERO_WIDTH, '');

scan('a\u200Bb');  // [{ i: 1, code: 'U+200B' }]
clean('a\u200Bb'); // 'ab'

Shell 命令行排查与批量清理

# 查看不可见字符(cat -A 会把特殊字符显式标出)
printf 'a\u200bb\n' | cat -A

# 用 Python 精确列出码位(排查接口返回的数据很有用)
python3 - <<'PY'
s = open('data.txt', encoding='utf-8').read()
for i, c in enumerate(s):
    if c in '\u200b\u200c\u200d\u2060\ufeff':
        print(i, hex(ord(c)))
PY

# 批量清理
perl -CS -pe 's/[\x{200B}\x{200C}\x{200D}\x{2060}\x{FEFF}]//g' data.txt > clean.txt

常见问题

什么是零宽字符,它是怎么跑进我的文本里的?

零宽字符是 Unicode 里一类「占据码位但不占宽度、也不显示任何形状」的字符,最典型的是零宽空格 U+200B。它们通常不是你打出来的,而是复制粘贴带进来的:网页为了防止内容被自动采集会在文字间插入零宽空格,PDF 转换、Word 导出、手机输入法联想、代码编辑器自动格式化也都可能带入。肉眼完全看不出差别,但长度、搜索、比对会全部受影响。

清理后看起来完全没变,是不是没生效?

这正是零宽字符的特点——它们本来就不可见,所以清理前后「看起来」必须一样,这才说明处理正确。要确认效果请看结果下方的字符数统计(清理前后会差出被删除的个数),或者对比标记版文本里是否还有 {ZWSP} 之类的标记。

为什么心形、对勾后面的选择符没有被清理?

那是变体选择符 U+FE0E / U+FE0F,它们不是多余的隐身字符,而是「显示方式开关」:加 U+FE0F 让符号按彩色 emoji 显示,加 U+FE0E 让它按黑白文字显示。删掉会改变外观,所以本工具只统计、不删除。如果你的场景确实要清空所有非 ASCII 字符,应该改用「Unicode 转义」或正则替换工具按范围处理。

文本里混进零宽字符会有什么实际影响?

常见有四类:一是长度统计和字数限制对不上(多算了几个字符);二是搜索、查重、数据库唯一约束失效(两个看起来一模一样的字符串其实不相等);三是代码或配置文件报出「无法解释的语法错误」(标识符里被插了零宽字符);四是排版出现莫名其妙的断行或对不齐。给同事发链接时,URL 里混入零宽字符还会导致打不开。

位置列表里的行列能直接跳到原文吗?

位置是给人工定位用的参考:行号按换行符统计、列号按字符数(UTF-16 码元)统计,都从 1 开始。因为不同编辑器的换行与字符计数口径可能不同(比如把 \r\n 当一行还是两行),建议优先用「标记版」结果——把 {ZWSP} 这类标记连同上下文一起搜,命中位置最准。

零宽字符在数据库里会占长度吗?

会。数据库字段长度按字符数计算,零宽字符同样计入,所以一个「看起来只有 10 个字」的昵称可能实际存了 25 个字符,触发长度超限报错。更麻烦的是唯一索引:两个带不同零宽字符的相同文本不会被视为重复。入库前做一次清理是常见做法。

同一段文字两次检测的数量不一样,为什么?

通常是因为中间经过了别的操作:比如从浏览器复制时网页脚本又插入了新的追踪用零宽字符、编辑器自动把某些控制符删掉了、或者你在两次检测之间改了内容。请确保两次检测用的是同一份文本(建议先粘进纯文本编辑器,再统一从这里复制)。

清理后的文本会改变原来的排版吗?

不会。本工具只删除那 19 个不可见码位,空格、换行、标点、正常文字一个都不动,字符间的相对顺序也完全保持。唯一的变化就是少掉那些看不见的字符,所以清理前后在页面上看起来完全一致。

延伸阅读

来自本站原创文章,讲清这个工具背后的算法与口径。