1. 为什么AI导出总是乱、崩、变先讲个真实场景。我平时写技术文档习惯让AI帮我起草内容没得说但一到“导出”这一步就血压飙升复制到公众号编辑器标题层级全没了粘贴到Word表格直接裂成两半最崩溃的是公式明明页面上排版得很好复制过来要么变成一串$$x^2y^2$$要么变成一张模糊的小图片连字号都调不了。这个问题的核心其实是内容在“格式层”出现三层断裂结构断了、样式丢了、公式废了。AI页面里的内容是活生生的HTML里面用了一堆复杂的CSS类名来渲染数学公式、代码块、嵌套表格。但复制到剪贴板以后目标软件根本不知道这些类名是什么意思于是要么用最原始的默认样式去猜要么干脆放弃结构把内容直接拍平成一段文字的“大锅烩”。后来我认真看了几个AI页面的源码发现一个很有意思的现象这些页面上“看起来”只是文本的东西底层里通常藏着完整的元信息。比如公式MathJax渲染之后HTML里经常还保留着一份MathML或LaTeX源码表格虽然是视觉上渲染出来的但DOM里仍然是规范的行列结构。也就是说我们要的原始数据其实一直都在只是缺一个“负责任的搬运工”。这就是我开发Format Duck的原因。你可以叫它“格式鸭子”这名字有点怪但功能很简单把我从AI页面复制出来的脏HTML解析成一份干净的中间结构再按Markdown、Word、微信公众号等目标重新排版。公式尽量还原成可编辑的原生公式表格能保持行列代码块能保留缩进。这篇文章我会把我的设计思路、完整流程、踩过的坑都写一遍给遇到同样问题的朋友一条能直接照抄的路。1.1 三个典型翻车现场你肯定遇过至少一个第一个翻车现场是表格。AI生成一张比较数据表型号、参数、价格、备注四列浏览器里看着挺正常。你复制到Word表格变得忽宽忽窄备注栏挤成竖排复制到飞书文档边框消失行高失控整张表像被压扁了。更离谱的是某些AI对话模板为了排版好看会给表格套一个很复杂的CSS布局复制出来之后Word只拿到了单元格的文本行列归属全乱了。第二个现场是公式。这是最让人炸毛的。我原本只是想写个文档提到E mc^2页面渲染得多漂亮。复制到Word以后公式变成一个叫“E mc2”的普通文本或者变成一张低分辨率图片放大以后边缘全是锯齿。如果是带分数、根号、求和符号的长公式症状更严重根号变成√求和下标挤成一排希腊字母变成?或者alpha;这种莫名其妙的东西。旧文书人每次看到这种结果都得花十分钟把公式重新敲一遍。第三个现场是代码和列表嵌套。AI回答经常给出多级列表比如“第一步、第二步、第2.1小步”复制后所有层级都变成同级别的圆点代码块更是重灾区缩进全没了所有行贴着左边缘排成一长串有的甚至把注释中的特殊字符当成HTML标签给吃掉了。这些症状单独看好像只是“格式问题”但积累多了人工整理一篇三天两头的文档成本完全失控。1.2 问题背后的根因A面是CSS类名B面是剪贴板协议为什么AI内容这么容易“乱、崩、变”根本原因在于浏览器复制内容时会把HTML连同CSS类、嵌套结构一起放进剪贴板的“富文本”区域但绝大多数看不了这个协议。AI网站为了自己的渲染效果类名通常是自己定义的比如markdown-table-fixed、math-inline、code-sample-line-clamp。Word和其他编辑器不懂这些类名能做的就是“尽量猜”猜错自然就乱了。公式则是另一层问题AI页面渲染公式的组件比如MathJax、KaTeX不是直接把文本印在网页上而是把原始公式通过JavaScript转换成图形结构。复制的时候浏览器拿到的只是渲染后的DOM片段有的包含隐藏的MathML有的只给一段img标签有的甚至什么都没有。说白了所有格式化问题的根源都是同一件事原始数据并没有丢丢的是“如何解释原始数据”的上下文。所以不要寄希望于“复制粘贴”这个动作本身它天然丢失上下文。你需要一个专门负责解释的工具把隐藏的源码结构还原成可用的格式。这就是Format Duck存在的正当理由。2. “格式鸭子”的定位与总体设计我当时给自己定了一个原则不做一个复杂的重量级排版软件只做一个“中间翻译层”。它不负责替你设计页面只负责把AI的内容无损拆解成一种标准数据结构再由它去适配各种输出目标。简单说它要接得住脏HTML、吐得出一篇完整文档。2.1 为什么叫“格式鸭子”它的核心价值是什么“鸭子测试”在编程圈很出名说的是“如果一个东西看起来像鸭子、叫起来像鸭子那它就是鸭子”。我想借这个梗做一个好记的定位你给我的东西看着像一段AI回答我就把它格式化得像一段正经文档。Format Duck的项目目标有三个关键词无损、去样式、可复现。无损指的是表格行列、公式源码、图片链接、代码缩进这些核心信息不能丢去样式指的是把AI页面上那些花哨的、不自解释的CSS类名的痕迹全部剥掉换成目标工具能理解的样式可复现指的是同一份内容今天导出和明天导出结果一致不会因为AI平台的改版或者浏览器版本变化而随机抖动。项目形态上我做了一个本地网页版再加一个命令行工具。本地网页版适合大部分人打开浏览器粘贴内容点一下“格式化”预览正常后点“导出Word”就完事。命令行工具适合批量处理场景比如你有几十份AI会话记录要一次性转成Markdown或者入库写个循环就行。两种形态共用同一个引擎下面都是Python跑的一套解析流程。2.2 整体设计先换成统一JSON结构再做输出先看整体流程。输入的来源很杂可能是从AI网页直接复制出来的富文本HTML也可能是AI页面提供的“复制为Markdown”还可能是一份.txt聊天记录甚至可能是别人发给你的一段残缺代码。如果你直接拿这些格式去喂给Word或者公众号不炸才怪。所以Format Duck第一步永远是把输入内容转换成“中间JSON”。这种JSON每个节点都带类型比如paragraph、heading、table、code_block、formula、image、list。节点里保存的是纯粹的结构信息不掺任何私有CSS类名。这一步做扎实了后续输出什么格式都是顺理成章的事要生成Markdown就把节点拼接成Markdown语法要生成Word就把节点交给转换器要生成公众号HTML就把节点输出成带固定行内样式的HTML。我特别说一下公式这块的中间结构。公式节点不会只存一段渲染后的图片路径而是会同时存latex、mathml、image_base64三种备选字段。默认使用LaTeX源码和MathML两者都没有才落到图片。为什么要做三种因为不同AI页面能提供的源不同有的能右键复制出LaTeX有的只有MathML隐藏标签有的真的只有一张图片必须“收到什么就用什么”。2.3 技术选型Python为主体Pandoc做翻译后段工具的开发语言我选了Python没有悬念。解析HTML用BeautifulSoup加lxml页面做得简单一点用Flask写一个本地Web窗口不依赖重型前端框架。真正干脏活累活的是三个阶段组件第一个是清洗器。它做的事情包括剔除script、style、svg这些和正文无关的节点把pre里的HTML实体还原回真正的换行和空格把嵌套过深的列表展平再重构对残缺的标签做闭合处理。清洗的原则是“少删多修”能保留的信息尽量保留。第二个是结构抽取器。它会把清洗后的DOM树映射到前面说的paragraph、heading、table、code_block等节点上同时识别出独立的数学公式区域。第三个是格式输出器。它负责把中间JSON渲染成目标格式。这里我直接用了Pandoc作为底层转换引擎自己只写前置清理和后续修补。Pandoc有一个很大的优点它能把LaTeX或MathML准确转换成Word自己使用的OMML公式对象也就是转出来的公式在Word里是“活”的能双击编辑能和其他公式一样参与编号。3. 核心细节拆解公式、表格、代码块是怎么处理的这一节我想重点讲三个最容易被搞坏的东西也是Format Duck最花功夫的地方。3.1 公式还原三步先找LaTeX再找MathML最后才OCR先说结论别一上来就做OCR识别公式图片是下下策准确率再高也有误差而且那张图一旦被转成图片式公式在Word里就没法重新编辑了。我处理公式的顺序是这样的第一步找LaTeX方言。如果用户粘贴的是纯文本里面带$...$、$$...$$、\(...\)、\[...\]这种包裹的直接按照公式源处理。这些字符串在AI回答里非常常见因为很多AI页面已经允许“复制为Markdown”而Markdown里公式就是保留LaTeX写法的。第二步从剪贴板HTML里找MathML。如果用户是从网页直接复制的富文本页面里如果用了MathJaxDOM里通常会有隐藏的math标签里面是MathML源码。BeautifulSoup能把这部分提取出来。这里有一个非常关键的操作提取出math之后要在原文里把这个节点删掉否则后面输出时会出现“公式重复显示”的问题。第三步如果以上两种都没有页面只给了一张公式渲染图那么只能走OCR。但Format Duck的做法不是直接把这张图塞到Word里而是先尝试识别LaTeX识别成功后再走公式转换管线。当然识别失败的图片我还是会保留为图片节点总比丢内容强。提取到LaTeX或MathML之后怎么变成Word里的原生公式这里我直接把中间JSON导出成一个轻量的Markdown文件里面包含$$...$$公式块再调用Pandoc把Markdown转成docx。Pandoc在做这个转换时会把LaTeX公式转换成Word能识别的OMML公式。最终用户打开Word看到的公式是可光标点击、可右键编辑的原生公式不是一张图。这里面有个要特别提防的小坑LaTeX里如果夹杂了不常见的宏包命令比如自定义的\mycommand、\stirlingPandoc会直接忽略甚至报错。我的做法是建立一个“未知命令清洗字典”把这些自定义命令替换成基础写法。比如\stirling{n}{k}它会先手工查一下语义看到是斯特林数就转成\left\{\begin{matrix} n \\ k \end{matrix}\right\}。没有字典的命令Duck会输出警告让你手动决定怎么处理。3.2 表格处理的重点不要逐格复制要整体结构还原表格之所以容易崩是因为很多人复制AI页面的时候实际上是“一个格子一个格子”地复制到剪贴板中间一旦遇到空行或者隐藏节点行列结构就断了。Format Duck里的表格处理有一个硬性规定整个表格必须作为一个整体节点处理。具体做法是这样的清洗器先在DOM里找到每一个table标签然后把里面的thead、tbody、tr、th、td以及rowspan、colspan这些关键属性抽取出来转成表格节点。在输出Word时我会强制生成一个表格样式并关闭Word的“自动调整列宽向窗口”功能否则Word默认会把每列都拉成等宽结果就是大段空白挤压在表格里。我给的默认方案是根据单元格内容长度估算一个初始列宽允许导出后再微调。嵌套表头也要处理。很多AI表格里表头是两层的第一层是“参数/数值”第二层有“最小值/最大值/默认值”。如果结构性转换做得不好输出以后会变成一行奇怪的文本把“最小值”当成了新行。Format Duck会把多层级表头转换成Markdown里支持的多行表头再走Pandoc转docx的那条路。单元格里的代码片段也要单独保护。假如某个单元格里写着model.predict(x)如果按普通文本处理下划线、星号可能被误解析成Markdown标记导致最终输出里出现一堆反斜杠。处理办法是在抽取表格时对单元格内容做一次“文本转义检查”发现代码特征就把该区域标记为inline_code而不是普通文本。3.3 代码块与图片缩进、语言标记、Base64都别丢代码块乱掉是另一大痛点。AI页面里代码块通常会放在precode结构里内部内容用HTML实体编码过比如lt;divgt;。如果你直接对HTML做字符串去除标签忙了半天代码里全是转义符号。Format Duck对这类节点会用get_text()让BeautifulSoup自己解码实体从而保留代码文本。读取之后我要求语言标记必须保留像python、bash、sql这些存在code_block节点的language字段里输出到Word时就会给代码块套一个等宽字体样式输出到Markdown时会变成带语言标记的三反引号块。图片的问题主要在Base64。AI页面里很多图片是“内联的”也就是data:image/png;base64,....这种大字符串直接嵌在HTML里。如果你用正则去提取标准图片标签可能会发现它把整段几十万字符的Base64当作图片还截断了。Format Duck的做法是识别data:协议的值先解析出图片类型然后做一次重新编码保证Base64字符串完整如果是路径型图片链接就把相对路径改成绝对路径并默认下载到本地而不是引用一个随时会失效的网络地址。图片还有一个大小阈值。默认超过2MB的图片Duck不会直接内嵌进Word而是把图片以独立文件形式保存在一个附件目录里文档里用相对路径引用。这样做是为了避免Word打开一个几十MB的docx直接卡死。阈值可以在配置里改但我建议普通文档别改大。4. 实操过程从复制AI内容到拿到干净Word文档前面原理说了不少下面给一个完美的、可以照抄的实操流程。你将看到Format Duck怎么把一个“乱、崩、变”的AI内容一步步变成一份结构正常的Word文档。4.1 安装与启动本地环境一包搞定我的项目用Python 3.9以上环境依赖已经打包在requirements.txt。装好Python之后执行git clone https://example.com/format-duck.git cd format-duck pip install -r requirements.txt python -m duck.web这样浏览器会自动打开http://127.0.0.1:8080出现一个绿色的粘贴框。如果你不喜欢网页版也可以直接用命令行duck clean input.html --output out.docx --format docx命令行模式平时我批量处理聊天记录用得多网页版适合单篇微调。第一次启动建议先生成一份默认配置文件duck init它会生成一个duck_config.yml里面能看到表格列宽策略、公式OCR开关、图片阈值等选项想改都改在这里。4.2 单篇处理AI回答变成Word全程四步第一步在AI页面里使用浏览器自带的“复制”能力把回答正文整体选中并复制。注意这里不要用“导出为纯文本”要复制富文本因为富文本里才可能带着公式的MathML信息。如果你复制的是纯文本公式的LaTeX反而可能丢掉。第二步在Format Duck网页里把内容粘贴到输入框。点击“解析”后左边是原始输入右边是结构化预览。结构化预览是一块只读区域你看到的不是我修复后的排版而是一个“节点树”能清楚看到哪些区块被识别为标题、哪些是表格、哪些是公式、哪些是图片。如果某个表格被识别成了纯段落马上就能发现。第三步修正明显的识别错误。比如一个多行表格被解析成了两个表格你可以在节点树上拖拽合并。一般情况不用手动操作但公式较多的时候建议检查一下公式节点的“源类型”是不是latex或者mathml。如果显示image加ocr_confidence: low理想做法是回到AI页面换一种复制方式或者手动补一份LaTeX源码。第四步点击“导出Word”。导出的docx里公式是原生可编辑的代码块设置了等宽字体表格是固定的行列结构。文件下载后打开我建议做两件事检查页边距Word的默认页边距偏大检查图片位置有些长图片默认靠左对齐需要手动设置居中。这个过程很快因为大头问题都已经解决了。我自己实际测试过一份包含40多条公式、6个表格、10段代码的内容以前手动整理至少1小时用这流程10分钟以内搞定。最花时间的反而是根据Word的默认编号规则重新编号公式手动操作多得单独说。4.3 批量处理聊天记录多份AI会话一日归档还有一类常见需求是把过去一周的AI聊天记录全部归档成Markdown文件方便后续检索。这类记录通常是从AI平台导出的txt或json里面会有很多重复的提示词、日期分行、系统消息。Format Duck支持直接输入一个目录批量清洗。duck batch ./raw_records/ --pattern *.txt --output ./docs/ --format md批量模式会自动做三件事把聊天记录按“用户提问”和“AI回答”切分成段落并识别出时间戳把AI回答里的代码块、公式块统一收拾干净依照“用户问题截断成标题”的规则给每个文件生成一个文件名避免几十个文件都叫chat.txt。批量模式里有个很有用的参数--skip-dedup默认开着会自动去掉重复段落。第一次跑完后我建议人工抽两三个文件检查格式因为聊天记录里经常有“用户让AI修改上一段文字”的情况这种上下文本身是残缺的再好的工具也不知道该把哪段删掉。5. 常见问题与排查技巧实录这一段全是实践过程中真实遇到的坑。我整理成一张速查表再挑几个重点详细说。5.1 速查表5个高频异常怎么处理异常现象直接原因我的处理方案表格总宽度超出页面单元格内容过长Word自动调整列宽在Word里选中表格右键“表格属性”关闭“自动调整列宽向窗口”手动设置第一列宽度公式显示为图片无法编辑原始HTML里没有MathML只有图片回到AI页面右键公式选“复制为TeX”没有这个选项就把图片转成LaTeX再导入代码块里的缩进丢失复制过程中HTML实体没有被正确解析用“复制为Markdown”而非富文本复制或者将代码块区域单独复制到纯文本编辑器再过一遍图片全部丢失图片是相对路径原AI页面登录后才能访问配置里开启download_all_images: true让Duck在解析阶段就把图片下载到本地导出Word后中文全变“宋体”且样式混乱docx默认样式对中文字体处理不当配置模板里指定doucment-font: 宋体并设置w:eastAsia属性标题再单独指定黑体表格只是一个索引下面展开讲讲最要命的几个。5.2 公式编号这个工具真没替你全做完这可能是全文最想说的一句实话。如果你要把公式编号成“(1)、(2)”指望一个导出工具自动搞定Word里的公式编号基本不现实。Word的公式编号是域功能牵扯到居中、制表位、交叉引用这套机制非常复杂不是文档转换器能自动生成的。Format Duck的做法是如果你在LaTeX源码里写了\tag{1}它会保留这个编号文本并排到公式右侧输出之后Word看起来是对的但如果你后续要在公式前面再插入一个公式编号不会自动更新。解决办法是在Word里把公式编号改成“自动编号域”选中编号按CtrlF9插入域输入SEQ相关指令然后所有公式重新用域编号。这个步骤不复杂但操作细节比较啰嗦属于Word排版基本功建议基础工具能力不足的朋友先在简短文档上练一遍再来处理长文档。如果你真的不想处理Word域那也有一个简单方案输出Word的时候直接选择“公式不带编号”编号这种排版工作留到最终排版阶段手动统一处理。这个方案主动放弃了一步到位但在绝大多数项目里反而更省心。5.3 剪贴板里复制不到公式怎么办有些AI问答页面做了特殊处理浏览器“复制”命令只能复制显示文本MathML隐藏节点根本进不了剪贴板。这个时候请换一种复制方式优先检查页面是否提供“复制为Markdown”按钮这种按钮通常把公式写成LaTeX源码粘贴结果是纯文本里带着$$正好能被Duck识别如果没有就在公式附近右键看MathJax是否弹出了“复制为TeX”的菜单以上都没有只能截图后使用OCR但这会降低准确率长公式尤其不稳定。这类问题不是靠工具猛怼就能解决的而是要把解决问题的主战场放到“获取源格式”这一步上去。所以我经常在Duck的界面里放一句提示先保证复制动作发生在正确的内容形态上。如果源本身残缺再牛的解析也只是“有毒废物再利用”。5.4 JSON导出与格式化的隐藏坑还有一个很多人会忽略的问题内容被结构化之后如果要保存成json格式供其他程序使用容易踩到编码坑。Python的json.dump默认会把非ASCII字符转成\uXXXX如果你存进数据库的是中文技术文档打开文件后满屏转义符。Format Duck内部的JSON输出指定了ensure_asciiFalse同时写入时用utf-8编码保证中文、希腊字母都以原样可读的形式保存。这个细节看着不起眼但如果你是做二次开发的同学遇到乱码时多半就是这一步没有处理。另外JSON里公式节点的content字段保存的是LaTeX源码而不只是渲染文本因为你无法保证所有下游系统都能渲染Markdown或MathML。下游系统如果只认纯文本再用latex2text之类的规则降级处理就行。总之在中间JSON层保存最接近源头的表示永远是对的。6. 后续扩展方向与个人体会Format Duck的第一版很朴素但它的架构给了我很大的扩展空间。目前我正在做三个方向的改进。第一个是给公式识别加一个完全离线的OCR引擎用本地小模型识别常见公式不发任何请求适合对数据敏感的场景第二个是增加“样式映射”模板库把公众号排版常用的标题色、代码高亮色统一存放导出HTML时直接套用第三个是支持“重新排序”AI回答里经常出现“先说结论再解释”的结构我想在中间JSON层面提供拖拽段落重排的功能这样不用到Word里大动干戈。我自己在实际使用中的体会是工具解决的是“格式错乱”的显性问题背后更重要的是内容生产的习惯先把结构整理好再考虑样式先用无损的中间格式做保存再做各种渠道发布。Format Duck最核心的价值并不是给Word做了一次临时的“修补”而是帮你养成了一种“先解构、再输出”的思考方式。最后再分享一个小技巧无论你用什么工具做完AI内容导出之后不要急着关闭原始页面。先把导出文档滚动一遍重点看三个位置——表格跨页处的分栏有没有乱、公式编号是不是连续、代码块结尾的缩进是不是完整。只要这三处没问题其他样式问题通常都好修。有时候Format Duck会输出一段警告日志比如“某条公式缺失LaTeX源已降级为图片”看到这类警告别忽略它就是你晚些时候可能踩中的炸弹。把警告当成待办事项处理掉导出工作才算真正完成。