解析 tweets.js:编码、emoji 与中文文本的坑
解压 X 归档之后会看到一个 tweets.js,很多人第一次打开就去调 JSON.parse,然后拿到一句语法错误。文件没有损坏,它只是不是纯 JSON:开头多了一段赋值语句,真正的数组被包在一句 JavaScript 里面。把这段前缀处理掉,剩下的才是标准数据。
解析动作本身不难,难的是解析完之后字符开始出错。emoji 变成问号、中文变成乱码、字数统计永远差一截,这几类现象根因相近。下面按「先拿到数据、再处理字符」的顺序展开。如果你还没搞清楚这个文件里都有什么,可以先读tweets.js 的字段构成,本文往前一步。
剥掉前缀:三步拿到合法 JSON
tweets.js 的第一行大致长这样:window.YTD.tweets.part0 等于后面接一个方括号数组。不同归档版本的前缀略有出入,但结构一致。处理方式有三种,按场景选。
- 快速查看:用文本编辑器找到第一个方括号的位置,把前面的内容删掉,另存为 json 文件。
- 脚本处理:读入整串,从第一个开方括号切到最后一个闭方括号,再交给解析器。
- 批量处理:归档里所有数据文件都是同一模式,写一个函数统一剥前缀,逐文件输出。
第三种最省事,因为归档通常包含推文、点赞、私信等多个数据文件,逐个手改容易漏掉一两个。
编码问题的三种表现
字符出错时先看症状,症状对应根因,别一上来就换库。
| 症状 | 典型根因 | 处理方向 |
|---|---|---|
| 中文显示成乱码或问号 | 用错误编码读取,常见是把 UTF-8 当 Latin-1 解 | 读取时显式指定 UTF-8,输出保持同一编码 |
| emoji 变成方块或空白 | 终端或字体不支持,文件本身没问题 | 先做一次码点检查,再判断是否需要换环境 |
| 字符串长度与肉眼不符 | 把码点、UTF-16 单元、字节数混为一谈 | 统计字符时统一按码点计算 |
第二行值得单独说:多数时候文件是好的,只是查看它的终端或预览工具渲染不了 emoji。把它写进一个 HTML 文件再用浏览器打开,通常一眼就能分辨。
emoji 与代理对:长度为什么对不上
一个 emoji 在肉眼看来是一个符号,在 UTF-16 里可能占两个单元,在字节数里可能是四个。同一个字符串用 length、按码点展开后的长度、字节长度量出来是三个不同的数字,这本身就是最常见的困惑来源。
实际影响在于截断。如果按 length 切片做摘要,很可能把一个 emoji 从中间切开,结果是两个无效的半字符,渲染成一串方块。要按可见字符截断,得先按码点拆分再拼接。
一个便宜的检查方法
把可疑字符串逐字符打印码点,人工看几眼就能判断是数据问题还是渲染问题。码点落在常见区间的是正常字符;出现 55296 到 57343 之间的孤立数值,说明代理对被切开了。
中文、日文与混排文本
中文没有空格分词,按空白切分标题或标签在中文内容上基本失效。要做关键词统计,得改用字符级计数,或者引入专门的分词处理。英文和中文混排时,字数统计的口径要提前定好,否则筛出来的长文本其实是同一批短句。
另一个细节是标点。中英混排的推文里往往同时存在半角和全角标点,直接做去重或匹配会漏掉一部分。在比对前统一做一次标点归一,成本很低但收益明显。
常见报错与修法对照
| 报错或现象 | 修法 |
|---|---|
| Unexpected token w in JSON | 前缀没剥干净,从第一个开方括号开始取 |
| Unexpected end of JSON input | 截取时把结尾的分号或多余字符带进去了,从最后一个闭方括号收尾 |
| 解析成功但字段全空 | 归档可能是分片结构,内容在内层数组里,需要多取一层 |
| emoji 统计数量偏少 | 按 length 计数,改用码点计数 |
解析完之后该做什么
拿到结构化数据只是开始。下一层需求通常是分类:按年份筛、按关键词筛、按风险等级筛。这几件事都需要先在本地建立索引,才能在不依赖网络的前提下反复查询,这也是把归档分析放在本机进行的主要理由,相关取舍可以参考本地分析与云端处理的差别。
索引建好之后,删除才有依据。哪些该留、哪些该删,取决于你给自己定的规则;按时间一刀切通常不是好办法。真要动手时,可以从批量清理旧推文的完整流程开始,按批次推进并保留可回退的记录。
把口径写下来,比换工具更重要
同一个归档,不同人统计出来的「推文总数」经常不一样,差别通常只来自口径:是否计入转推、是否计入回复、是否按码点计数。把这三条先写进脚本注释,后面所有对比才有意义。
解析结果存成什么格式
解析完成之后,第一件事是决定输出格式。三种常见选择各有用处:表格格式便于在电子表格里按年份或关键词筛选,适合人工逐条核对;原始结构格式保留嵌套字段,适合后续再加工;本地索引适合反复查询和跨字段检索。
如果归档要长期留存,建议至少保留一份原始结构格式的输出,不要只留处理过的版本。字段口径以后可能变化,原始数据留着才能重新算。
字段里带逗号时
导出为表格格式时,推文正文里的逗号和换行会直接把列拆乱。解决办法是给每个字段加引号并在内部做转义,简单用逗号拼接会直接拆列。这个错误很隐蔽,通常在导入之后才发现列错位。
不同时期归档的结构差异
归档格式经历过多次调整,不同年份导出的文件结构并不完全一致。早期版本可能把推文分片存放,后来字段命名也做过修改。用同一套脚本处理多个时期的归档时,先跑一次字段探测,确认关键字段都在,再进入正式流程。
探测的做法很简单:读取之后打印第一层对象的字段名,与预期清单对照。缺字段时报错退出,比静默产出空值好得多。
digital-footprint-health.shop 提供免费的足迹体检与本地优先的处理流程。想先看看自己的公开内容里有哪些值得关注,可以从首页开始做一次快速体检;需要成批处理旧推文,可在上传页查看支持的数据格式,具体方案与费用见定价页。
常见问题
tweets.js 能用 JSON.parse 直接解析吗?
不能。文件开头有一段赋值语句,真正的数组被包在里面。需要先剥掉前缀,只保留第一个开方括号到最后一个闭方括号之间的内容,才是合法 JSON。
归档里的中文变成乱码是什么原因?
通常是读取编码不对,例如把 UTF-8 的文件按 Latin-1 解。读取和写出都显式指定 UTF-8 即可恢复;若文本本身已在上一步被错误解码并写回,需要重新从原始归档解压。
为什么统计出来的推文条数跟页面不一致?
多数是口径问题:是否计入转推、是否计入回复、按码点还是按 UTF-16 单元计数。先把三条口径固定下来,再做跨表对比。
emoji 显示成方块,是数据坏了吗?
多数情况下不是。终端或字体不支持渲染时就会显示方块,文件内容完好。把同一段文本写进 HTML 用浏览器打开即可验证。
解析完归档后第一步应该做什么?
先建本地索引:按年份、关键词、风险等级三个维度做一次结构化整理。有了索引再决定删什么,比按时间一刀切更可控。
检查你自己的 X/Twitter 数字足迹
免费本机扫描,你的归档永不离开电脑。
免费开始体检相关阅读
给 200MB 的 X 归档建一份本地搜索索引
下载下来的归档是一个 ZIP,里面 tweets.js 动辄两三百 MB。想在里面找"三年前提过一次的那个地址",靠文本编辑器翻是不现实的。这篇用二十来行脚本把归档转成可检索的本地索引,并说明内存为什么会爆。
X 接口限流为什么让批量删除变慢:配额机制与排队策略
批量删帖慢,慢的不是网络也不是工具,而是写操作配额的窗口计算方式。理解窗口长度、端点配额和 429 的含义之后,就能把删除从「一次性猛冲」改成「可控排队」,中断也不用从头再来。
自己写一个本机推文删除脚本:从归档解析到可续跑批处理
托管删除服务要交出账号权限,官方界面又处理不了几万条。自己写一个本机脚本是第三条路:归档解析、权限范围、批次限流、状态文件四块拼起来,删到一半掉线也能接着跑。附完整的模块划分与失败处理清单。