知网下的 .caj,其实就是个尾巴被塞了东西的 PDF

问题不大
问题不大
发布于 2026-10-30 / 1 阅读

从知网下了一篇论文,想在手机上翻两眼,微信里点开却是"文件已损坏"。换个阅读器,有的能开、有的白屏;发到电脑上又正常。折腾了半宿才弄明白:那个 .caj 根本不是什么特殊格式。

它本来就是 PDF,只是尾巴被塞了东西

知网给的 .caj,绝大多数用十六进制看第一行就是 %PDF-1.6——它就是一份标准 PDF,只是文件结尾被追加了一块知网自己的私有数据(记录下载者、订单号那类信息,XML 结构,FileProperty-Package)。

阅读器打开文件只看文件头,所以能认出来;但翻到尾部撞见一段不属于 PDF 规范的东西,有的就直接判"损坏"——微信内置预览和安卓自带阅读器是最容易翻脸的两个。

所以正确的动词不是"转换",是"清理重写":把尾部的垃圾块去掉,再把文件内部的索引重建一遍。

先认文件头,再决定走哪条路

.caj 是个套壳后缀,里面至少藏着三种完全不同的东西。别急着找工具,先看头四个字节:

head -c 4 "论文.caj"
文件头 里面是什么 怎么处理
%PDF-1.6 / %PDF-1.4 PDF 内核,尾部被污染(最常见) mutool clean,一条命令
首字节 0xC8 私有文本指令流,正文不在页面里 专用脚本解析,见下文
CAJ / HN / KDH 知网早期原生格式 开源工具 caj2pdf

判断技巧还有一条:看结尾。用 tail -c 64 "论文.caj" 看一眼,如果最后不是 %%EOF,而是一段 </FileProperty-Package> 之类的 XML,那基本可以确诊是第一种。

最常见的那种:一条命令就完了

mutool clean -gggg -d -s "论文.caj" "论文.pdf"

mutool 是 mupdf 自带的工具,Termux 里 pkg install mupdf 就有了,几兆大小。四个参数各管一件事:-g 做垃圾回收(写四个是最高强度),-d 丢掉文档结构树和 XML 元数据残留,-s 重排并重建交叉引用表——最后这个是关键,前三步删掉的是对象,只有重建了索引,文件才算重新变成一个合法 PDF。

有两件事千万别做。

一是别直接把 .caj 改名成 .pdf。文件名骗得过电脑,骗不过阅读器——尾部那段垃圾还在,微信照样报损坏。二是别用文本编辑器或 sed 手工把尾部那段 XML 删掉。PDF 的交叉引用表里记着每个对象在文件中的字节偏移,硬删一段,后面所有偏移全部错位,文件从"部分打不开"直接变成"彻底报废"。清理必须交给会重建索引的工具。

转完必须验三件事

mutool info "论文.pdf" | grep -E "PDF-|Pages:"
mutool draw -F txt "论文.pdf" | head -25
head -c 20 "论文.pdf"; echo; tail -c 20 "论文.pdf"

页数和源文件一致;文字层能提出中文(能提=不是扫描件,可搜索可复制);首尾是 %PDF-1.6 和 %%EOF。

还有一步肉眼环节:把页面渲染成图看一眼,防乱码方块和错位。

D=$(mktemp -d)
mutool draw -o "$D/p%d.png" -r 100 "论文.pdf"

图片出来能直接看图核对:首页标题对不对、末页参考文献全不全、有没有缺字。这一步花的三十秒,比事后发现"少了一整页"再返工值钱。

那次的实际数字

源文件 198KB、六页;转完 527KB、还是六页。体积涨了一倍多,因为重建索引和对象表本身就是有成本的,但页面内容一个字节没动。文字层能正常提取全角中文,首页标题、作者、刊物年份和末页的责任编辑都在。

人物坐在桌前读论文,桌边摊着一叠纸
看到文件头是 %PDF 的那一刻,事情就简单了

难对付的那种:正文就在文件里,工具却导不出来

第二种(首字节 0xC8)才是真的麻烦。这类文件用开源的 caj2pdf 跑,它会告诉你"成功",页数、插图数都报得出来——但导出的 PDF 里一张正文都没有,只剩几张插图。这不是工具写错了,是这类 CAJ 的正文既不是图片、也不是 PDF 页面,而是一段私有文本指令流,caj2pdf 根本没实现它。

好消息是文字完整地躺在文件里,只要按结构解出来,能做出带文字层、可搜索可复制的 PDF。实测的结构是这样:

  • 页表从偏移 0x50 开始,每页 20 字节,依次是文本段偏移、长度、图片数、页号、保留、下一段偏移;
  • 页文本段是"控制标签 + 字符单元"混在一起的小端 u16 流:0x8001 后面跟 Y 坐标,0x8002 跟 X 坐标,0x80CC 是一组字形记录,其余 0x8000~0x80FF 一律当标签按四字节跳过;
  • 剩下的四字节才对:前两个是横坐标,后两个是 GBK 码,而且是字节反序存放的——必须先按小端读出码值,再拼回 bytes([码>>8, 码&0xFF]) 用 gb18030 解码,直接按字节解全是乱码;
  • 字符不会和标签打架,因为 GBK 首字节从 0x81 起,0x80 本身非法,所以落在标签区间的一定是标签;
  • 英文字母数字另有一套映射:高字节 0xA0、低字节 0xC1~0xFA 时,真实字符就是低字节减 0x80;
  • 插图记录 12 字节,后面紧跟图片数据,下一条记录要按"12 + 长度"往后跳;而且图片在文件里是上下倒序存的,嵌进 PDF 前必须翻转,否则整张图是倒的。

按这套解出来,五页正文按原坐标重新排版,插图按原页成组排在后面,一共七页,文字层可搜索。

后来又修的三处

能用之后复查,发现三处值得改,改的都不是解码逻辑:

一是双栏论文的文字层原来左右交替,复制出来是碎片。改成按基线聚类、按行内大空隙切栏段,先输出左栏读完再输出右栏——字符还画在原坐标,版面不变,但复制和搜索都连贯了。

二是年份、页码、分类号这类全角数字(2007、G623),搜 2007 搜不到。改成画字前把全角数字字母转半角,全角标点一律不动,否则中文逗号顿号会被破坏。一份文件修正了四百多个字。

三是四张插图原来各占一页,白页太多。改成按原页成组、一页两张,带"原文第 N 页 插图"的标题,九页压到七页。

还有两件是格式本身的限制,改不了:插图没法回到正文原位(这类 CAJ 里图片不带页坐标,只记了属于哪一页),只能成组排在正文后面;宽字距的英文页眉在文字层里可能被插进多余空格,复制下来要手工去一下。

我踩过的几个坑

/tmp 在 Termux 里不可写,报 Permission denied,中间图一律用 mktemp -d 生成目录。

mutool clean -s 拿 /dev/null 当输出"验证"会出事——它会反推出一个 out.pdf 悄悄丢在当前目录,你以为没留文件,其实留了。

中文名、空格、引号三样凑齐的时候,变量不加双引号必报"文件不存在",因为 shell 按空格把文件名拆成了两个参数。

输出名和源文件名只差扩展名,手滑写成同一个路径就把原件覆盖了。原始 .caj 留着,出问题能重来。

一整个压缩包全是 caj 的时候

现实里更常见的是"导师发来一个压缩包,里面十几篇"。流程是:解压出所有 .caj → 逐个转换(脚本按文件头自动分流)→ 每份都核一遍页数、别跳 → 重新打包。

Termux 里没有 zip 命令(只有 unzip),要用 Python 的 zipfile 模块打包,中文名才不乱码:

python3 -c "
import zipfile,glob,os
with zipfile.ZipFile('论文_PDF版.zip','w',zipfile.ZIP_DEFLATED) as z:
    for f in sorted(glob.glob('*.pdf')): z.write(f, os.path.basename(f))"

耗时大头在原生型:一百多页的 caj2pdf 单份要跑几分钟,属正常。其余两种都是秒级。

桌面上摊开的论文纸与文件夹
一叠打印稿:真正要留下的只是能读的那几页

转不动就别硬转

原生型(CAJ/HN/KDH)真转不动的时候,有三条退路,按省事程度排:

一、回知网重新下载,下载页选"PDF 下载"而不是 CAJ。这是最省事的一条,值得优先考虑——很多折腾都是在为一次点错买单。

二、手机上装 CAJViewer 官方 App,打开后走"另存为"或"打印为 PDF"。

三、电脑端用知网研学(E-Study)打开,导出 PDF。

工具能做到哪一步是有边界的,识别出边界、换条路,比在一个导图像的工具上磨一个小时划算。

▍ 本文由自建 Agent 调度内容流水线自动完成:写稿 → 模型 API 生图 → R2 图床 → 排版 → MCP 落站。 ▍ 配图由模型 API 生成、托管在对象存储上;内容仅供参考。