利益相关声明: 文中的 Beancount-Trans 由我开发并维护,以 MIT 许可证开源,可在自己的机器上部署。下面写的是我处理中国支付账单时的经验,不是中立评测。
上个月结账日,我本来只想把微信和支付宝的官方导出收进 Beancount。Beancount 是一套用纯文本记账的工具:每一笔钱都写成「从哪个账户出、进哪个账户」,账本是普通文件,不绑在某一款应用上。
账单格式又变了,原来的导入器(importer)跑不起来。用文本编辑器打开导出文件,前十几行是账户说明和时间范围,真正的表头藏在中间。我用 Excel 打开再另存,订单号变成了科学计数法。对到半夜,才发现同一杯咖啡:微信里有一条「支出」,信用卡账单里又有一条「消费」,被我各记了一次。
卡住我的往往不是账本语法,而是把「现实世界的账单文件」变成「可以逐笔核对的复式分录」。语法可以慢慢学;每个月这一截如果一直坏,科目树再漂亮也撑不住。本文会讲三件事:中国支付账单为什么难进账本;我自己写导入器时钉死过哪些规则、后来为什么不维护了;以及我把这条流水线做成开源工具之后,仍然搞不定什么。你不必会写 Python。若你更愿意用手写脚本或社区里现成的导入器,中间的字段对照仍然用得上。

Beancount 只要两条腿,账单往往只给你一条
复式记账的最小单位是两条「过账」(posting):钱从哪里来,到哪里去。例如一笔 14.40 元的外卖,账本里通常是:费用账户增加 14.40 元,某张信用卡减少 14.40 元。少一条腿,账就不平。
支付宝、微信的导出并不按这个结构来。它们告诉你「支出」或「收入」,有时还有「不计收支」。商户名能提示你花在哪一类;真正决定资产或负债账户的,常常是「支付方式」那一列,比如零钱、花呗、某张信用卡尾号。导入器要做的,并不是把 CSV 粘进账本,而是把一张「说明文字 + 表头藏在中间」的官方导出,变成可核对的分录,并尽量避免同一笔钱被记两次。
社区里早就有人在做这件事。Beancount 官方有 beangulp(此前叫 beancount.ingest)这套导入框架;中文账单方向也有如 china_bean_importers 这类现成仓库。我后来自己做工具,不是因为那些方案不存在,而是我每月都在重复同一类脏活:文件头、编码、支付方式映射、转账对敲。把脏活产品化之后,我仍然要审核,只是不必每个月先修脚本。
中国账单的五个坑
这些坑与用哪套工具无关。谁来写导入器,都会碰到。
它往往不是干净的逗号分隔文件(CSV, comma-separated values)。 支付宝、微信的官方导出,文件开头通常是账户说明、时间范围、免责声明,真正的列名在十几行之后。编码可能是 GBK,也可能带字节顺序标记(BOM, byte order mark)。编辑器里「看起来正常」,用 Python 默认的 UTF-8 一读就乱。
「收、支、不计收支」对不上复式分录。 账单里的「支出」只告诉你钱往外走了。Beancount 还要知道:从哪个资产或负债账户出去,进哪个费用或资产账户。
转账、理财、信用卡还款会在两边各出现一次。 微信零钱转到银行卡、余额宝申购赎回、用支付宝还信用卡,支付平台和银行会各记一笔。如果两边都当「支出」导入,资产会凭空少一截。
退款和交易关闭要有规则。 「交易关闭」「等待付款」通常不该进账本;「退款成功」往往要冲销原支出。没有预过滤,审核列表会塞满噪音,逐条检查很快就会放弃。
真正决定资产或负债账户的,常常是支付方式。 商户名决定「花在哪一类」;支付方式决定「从哪出」。只盯商户写规则,资产侧永远对不齐。
一眼能看出差距。支付宝导出里,一条外卖大概长这样(字段已脱敏):
2025-07-31 17:55:42,餐饮美食,淘宝闪购,...,宝岛便当(塘下店)外卖订单,支出,14.40,中信银行信用卡(6428),交易成功,20250731220011...,...
我想要的 Beancount,更接近:
2025-07-31 * "淘宝" "宝岛便当(塘下店)外卖订单"
time: "17:55:42"
uuid: "20250731220011..."
status: "ALiPay - 交易成功"
Expenses:Food:Dinner 14.40 CNY
Liabilities:CreditCard:Bank:CITIC:C6428 -14.40 CNY
费用科目来自商户或品类映射;负债科目来自「中信银行信用卡 (6428)」这条支付方式。导入器的工作量,大半花在把现实世界的字段,稳定地映射成这两条腿。支付平台明年改一列名、多一个理财产品状态,手写脚本就要改对应的那几处。这不是假设,是我维护自己的脚本时反复遇到的事。
我自己写过:至少要把这几条钉死
这一节不是 beangulp 教程。它只记录我维护个人导入器时,不钉死就会每个月返工的规则。
只用官方原始导出,不要用 Excel 打开后另存。 Excel 会改编码、改长数字、丢掉前导零。解析失败时,我现在的第一反应是重新从支付宝或微信 官方渠道导出,而不是在表格里修。支付宝一般在「我的」里进入账单再申请下载;微信一般在「我」>「服务」>「钱包」>「账单」里找下载入口。具体文案会改,以应用内为准。用文本编辑器打开,应能看到平台说明文字和完整订单号。
跳过文件头再读表,读入后统一成 UTF-8。 找到真正的列名行(通常含「交易时间」「交易对方」「金额」),之前的行全部丢弃。打印前三条数据行的字段数,应与表头列数一致。
用订单号去重。 同一文件重复导入,或微信与银行各导出一笔支付,都会制造重复。稳定主键优先用平台订单号;没有则用「时间 + 金额 + 对方 + 支付方式」做哈希,并接受偶发碰撞。对同一文件跑两遍,第二遍不应再产生新的分录。
转账与还款要对敲,不要两边都记成支出。 微信零钱转到银行卡,是资产内部转移,不是餐饮支出。还信用卡,是负债减少加上资产减少,不是双重消费。余额宝或零钱通的申购赎回,多数情况下是资产形态变化。拿不准时,我宁可先标成待审核的转账,也不默认进费用账户。
支付方式到账户的映射,单独维护一张表。 「中信银行信用卡 (6428)」「零钱」「花呗」映射到负债或资产账户;商户到费用科目是另一张表。两张表分开,格式变更时才知道该改哪边。
第一版脚本往往一个周末能写完。真正的成本是:支付平台不定期改导出模板;自己换卡、换支付习惯,映射表要跟着改;退款、分笔支付、合单,边界案例没有尽头。格式一变,规则就坏。这不是不认真,而是「用个人时间维护中国支付生态的适配层」本身就不划算,除非你享受写导入器。我有一阵享受,后来不享受了。每个月结账日打开电脑,先不知道该修脚本还是该对账,复盘就会被往后推。
我后来做成了什么
Beancount-Trans 把上面那条证据链收成「导出、上传、审核、写入账本」。它开源,也可以自托管。云端适合先看流程是否成立;账单不想离开自己的机器,就在本地跑。仓库在 GitHub:dhr2333/Beancount-Trans。
它不是「点一下就得到完美账本」。设计上默认是审核模式:解析结果先给你看,你改完再写入。分类不准、转账被当成消费、完全不该进账的行,都要人点头。我反对把「自动导入」理解成零人工。准,比全自动更重要。金额、时间、订单号仍来自原始账单;模型或规则最多在候选账户里排序,不创造数字。
最短路径大致是这样。先按官方渠道导出完整自然月(或「本月 1 号到今天」),文件不要二次另存。登录后选「新建」>「文件上传」,拖入账单。选中文件后点「解析」:统一编码、识别来源、丢掉交易关闭等噪音、按关键字匹配商户与支付方式,再生成带订单号和时间的 Beancount 语句。抽查一笔外卖,支付账户应落在你的信用卡或零钱,而不是一个不明账户。写入后,用订单号或日期在日记账里能找到刚才那笔,借贷两侧金额相等。




我自己用的时候,大部分笔数规则能对上;卡住的通常是新商户、新支付方式、以及转账还款。花时间的地方从「修解析」变成了「改那几条不对的」。这不是零成本,只是成本终于发生在结账本身上。
仍然搞不定的,以及你可以怎么选
有些事它刻意不做,有些是能力边界。
它不提供消费当时打卡。如果你需要的是每次付钱后立刻记一笔,这不是同一类工具。完全零配置、从不校对,也做不到:新卡、新商户、平台改列名,都要人看一眼。Excel 另存过的文件经常识别失败或字段错位,这不是偶尔的小毛病,是来源识别依赖文件开头的官方痕迹。银行账单可以走同一条「上传、解析、审核」路径,覆盖并不等于所有银行、所有卡种;我日常仍以微信和支付宝为主。映射规则在下一次解析时生效,不会自动改写已经写入的历史。同名文件通常不能再传一份;你改名后再解析,仍可能产生重复分录。云端意味着文件会经过我的服务器;在意这一点,就自托管。分类辅助不经大模型也能跑,但候选排序若用了模型,仍然要以账单字段为准,不要把「建议科目」当成凭证。
和手写 beangulp、或直接用 china_bean_importers 相比,差别主要在维护责任。自己写或用社区脚本,格式一变,要自己跟(或等仓库更新)。我把适配层收在产品里,换来的是你要接受「审核界面」和「我覆盖不到的卡」。享受写导入器的人,继续写完全合理。已经认可纯文本账本、却每个月被中国账单拖死的人,才是我做这东西时想帮的那一类。
Beancount 值钱的地方从来不是某一款应用,而是你手里那份可验证、可迁移的账本。导入器无论手写还是产品化,只是把微信、支付宝账单,用还能接受的成本接进这份账本。我给自己做审核管线,是因为结账日我不想再先修脚本。账本格式是公开的,文件属于你。
相关阅读
- 「说人话」的复式记账语言:Beancount(少数派):复式记账与 Beancount 入门
- 记账神器 Beancount 教程(少数派):语法与本地账本实践
- dhr2333/Beancount-Trans:本文提到的开源仓库
