缘由

在 2020 年,我曾经尝试过使用 Beancount 进行记账。但当时,我每一笔开销都是手动记录的。在最初的兴趣消退后,就再没有兴致去记账了。最近,感觉需要统计一下自己的开销比例,就重新翻出了 Beancount。当然,我要吸取上次的教训,选择直接导入信用卡账单,而非手动记录开销。最终的结果,是我在除夕忙了一整天,得到了一个初步可用的 python 程序,和这篇博客文章。

为何撰写本文

Amex CA 可以直接从网页上下载 CSV 格式的账单,内容非常简单,脱敏示例如下

12/14/2022,"Reference: 003"," 44.05","SHOPPERS DRUG MART","",
12/14/2022,"Reference: 002"," 6.76","SOBEYS","",
12/16/2022,"Reference: 001"," 12.99","MEMBERSHIP FEE INSTALLMENT","",

可是,我在网上找了很多圈,处理这种 CSV 好像都是 too trivial case, 包括官方文档在内,对这个环节的描述都是凤毛麟角。

文章看了很多,工具也尝试了好几个,但最后,还是要自己撸代码,写一个 Importer。

代码实现

参考 Matt TerwilligerGist, 我写出了一个简单的 Importer。

https://gist.github.com/Endle/1033eb36135b50e19ea64ccc39be5ca7

基类 importer.ImporterProtocol 只有两个必须实现的函数

identify()

返回 bool,判断是否要处理当前文件。有些人写的 config.py 比较完善,只要运行 bean-extract config.py ofx.csv ~/Downloads/statements/*, bean-extract 就会根据 identify() 的结果,确定要用哪一个 Importer。在这里,我的代码比较简单。

extract()

这个函数处理 CSV 账单。哓哓文章里介绍了如何沿袭 CSV Importer 的结构写 Importer,但在起步阶段,我没有遵循这个结构,而是手动读 CSV 以后逐行处理。

    def extract(self, f):
        entries = []     
        with open(f.name) as f:
            for index, row in enumerate(csv.reader(f)):
                txn = self._process_row(index, row)
                entries.append(txn)
        return entries

如何在 Windows 下安装 Beancount (Cygwin vs WSL)

买一台新电脑的计划还是搁置状态,所以我只好在 Windows 笔记本上先凑合用。官方手册上说,It’s a breeze if you use Cygwin. 但我在执行 python3 -m pip install beancount-import smart_importer 时,总会在安装依赖 scikit-learn 时卡住。

接下来,我就换到 WSL 里运行了。我在执行 sudo pip3 install m3-cdecimal 时失败了,但跳过这一步我也没看到有什么影响。

后记

我也为这是一个很简单的需求,但没想到,真做起来还是花了不少的时间。有很多人上传了自己实现的 importer,但在不了解 context 和设计思路的情况下,想要拿来直接用很困难。这个页面 列举了很多 Importer,比如 Clojure 实现的 csv2beancount. 还有 高策 等人用 Go 实现的 double-entry-generator.

此外,这个页面还有一些输出到 Ledger 和 HLedge 的工具。这篇文章 介绍了一些区别。