Beancount复式记账:接地气的Why and How
zsx2019/11/17 in 记录整理 / 1 / 766

好久没写文章了,干脆把我最近的研究拿出来凑个数。想不到吧,竟然是一篇和技术完全没关系的文章(。这篇文章是一篇面向个人的非专业记账教程,旨在安利Beancount,并介绍Beancount的一些适应中国国情的使用方式。如果你喜欢手动记账,那Beancount可能是最好用的记账工具;如果你嫌手动麻烦,只想快速地完成记账工作,Beancount也很适合你。

Why 记账?

最近几个月,看到我微信朋友圈的朋友都会发现我似乎一直在折腾记账。当然,这不是一时兴起,我记账已经有好几年了。

记账的最原始动机自然还是「穷」了。在消费主义盛行的当代,要对自己(被资本主义人为创造出)的欲望进行克制,似乎并不是一件容易的事情。《乐记-乐本》所论曰:「夫物之感人无穷,而人之好恶无节,则是物至而人化物也」。如果能通过记账了解自己的财务情况,从「节流」的角度看,就可以遏制自己的非必要商品购买欲望;从「开源」的角度看,也能对自己的收入情况有一定的把握,从而可以想办法通过各种手段增多自己的收入来源;通过对历史数据的分析,也能对自己使用分期付款等金融工具的风险抵御能力有一定的清晰认识。

──记账,就是一个了解更完整的自己的过程。为了达成这个目的,则需要一套科学的记账方法和科学的记账软件。最近几个月前,Byvoid的博文让我发现了Beancount这么个东西。在几个月的试用和磨合之下,我认为这是目前市面上最符合我的需求的记账工具。

使用这套工具,还是需要一定的前置条件的:

  • 能比较熟练地使用各类开发者用的文本编辑器,例如Visual Studio Code、Sublime Text或是Vim。
  • 对Python和virtualenv有一定的了解,至少需要会启动。
  • 如果不打算深入研究,那么读几篇中文教程足矣,日常使用时基本碰不到英语;当然,想要进一步开发的话,读英语文档的能力还是必不可少的。

Why Beancount?

它的第一个特点是开源,这对我来说是最重要的特性。

  • 我无法相信「云记账」软件,我不知道它们背后的公司会不会拿我的账本做大数据分析,也不知道它们内部有没有内鬼会卖我数据,更无从得知它们的系统是否存在各类极度弱智的未知漏洞。数据存在我的本地,也只在我的本地,是最安全的。如果需要云备份,我的开发能力也足以让我在上传到云前对账本进行加密。
  • 闭源的记账软件永远存在着功能缺失。我曾经是MoneyWiz用户,这个软件对我来说,界面操作卡顿、手感极差;无法导入任何一家来自中国的支付机构的账单。我不能对这个软件做出任何符合我需求的改进。
  • Python语言也是重要的加分。虽然个人不喜欢也并不熟悉Python语言,但相比C++和Golang编写的其他账本程序来说,脚本语言还是更方便进行二次开发的。

它的第二个特点,是完全基于纯文本

  • 我拥有着对账单进行大规模查找和重构的能力,使用包括正则表达式之类的任何趁手的工具,对账单进行任何我需要的修改。
  • 我可以使用版本管理软件,如Git,保证我每次修改账单的结果可回退,从而保护账单的完整性。也可以使用Dropbox等同步工具进行多终端账本同步。类似GnuCash等基于SQLite的软件并不好这么做,MoneyWiz这种不能指定存储路径,只能手动进行备份的软件更是灾难。
  • 我随时随地都可以将账本从这套工具转移到另一套工具,避免被绑死在一辆战车上。

复式记账」是它的第三个特点。这一点我会在接下来的文章内说明。

在以上几个特性的帮助下,自然便会衍生出「功能强大」这个每个软件都会自夸的特性。它吸引我的功能还包括这些:

  • 没有货币概念,记录的是commodity(通货),你不仅可以拿来记钱,也可以拿来记信用卡积分,记挖SRC送的漏洞金币等一切能记录的东西。
  • BQL,这是一种类似SQL的语言,可以快速对指定的交易、余额等一切进行查询。
  • Fava。这一套GUI美观程度让人怀疑这到底是不是开源软件(对不起我黑了一把开源软件。
  • 生态链。Visual Studio Code等主流编辑器、Alfred等快捷启动器均有其插件。自然,也有不少人也在提供各类基于BeanCount的云服务,例如基于Telegram记账的costflow.io、直接做云记账的beancount.io等。

当然了,对于开发者而言,它也极其易用;我在这里不得不贬损几位同类软件:

  • GnuCash:我并非会计有关专业,打开这个软件的瞬间就被它的「专业」感吓到了。这款软件的汉化不完全,我的英语词汇量也完全无法支持大量专业术语。另外,这一款软件Bug还挺多的。
  • MoneyWiz 3:在我的MacBook上表现出明显的卡顿和迟缓,手感极差。且它的GUI界面大量依赖鼠标,记账效率极低。
  • 网易有钱、随手记等记账软件:广告等且不论,在手机上戳戳戳实在是麻烦到爆了。并且,它们也已经不能拉取支付宝和微信账单了,不是么。
  • 不过很抱歉,我并不是很清楚它对于非开发者的上手难度。

Why 复式记账?

记账方法有两种:单式记账法、复式记账法。

单式记账法,也就是所谓的「流水账」。在「流水账」中,对于一笔交易,只记录余额的增减,并不在乎钱的来源或去向。这显然不够好用,因此,我们日常记账时都至少会记录这些:

  • 来源:钱是从哪个账户花的?是信用卡,还是余额宝?在不记录「来源」的情况下,可以将持有的总资产视为「来源」。
  • 金额:花了多少钱?
  • 去向:花到哪儿去了?

可以注意到,我们肯定会记录钱的来源与去向,这是在单式记帐法上的修修补补,是一种相对蹩脚的记账升级方式。如果出现这些情况,又要怎么记账呢?

  • 和朋友们一起吃饭,钱由你一个人支付,之后朋友们AA还钱。
  • 出差,事先垫付差旅费,回头找公司报销。

我相信每个人、每个记账软件,针对这种生活中的复杂交易,都有不同的解决方案。强行记账也不是不行,就是会对你本月的支出和收入产生极其大的影响。当然,我知道记账软件同样也都有「报销」功能。只是「奥卡姆剃刀」原则指出:「若无必要,勿增实体」。「报销」的产生,本来就只是因为流水账功能太孱弱,不得已而为之。与其使用补丁摞补丁的记账方式,不如直接使用「复式记账」。

「复式记账」听起来很复杂,只有公司的专业财务才用。其实并不是这样的。抛弃流水账的思维方式吧,让我们转变一下思维。我接下来会无视《企业会计准则》里拗口的说明,也不使用会计学的专业名词。我们是为自己记账,自己的账,我想怎么记,就怎么记,不需要遵守会计准则。只要记帐能保证自己收入与支出平衡,能准确反映自己的资产变化情况,那就足够了。因此,我下面所说的复式记账都基于Beancount,而非来自于《企业会计准则》的「借贷记帐法」。

Beancount 复式记账

Beancount,正如其名,「数豆子」。简单粗暴,直接把1元人民币当成一个绿豆好了。现在想像一下,在你的面前有一个装满绿豆的大桶,这是你的资产(Assets)桶。你工资收入5000元,等同于天上掉下5000个绿豆掉到你的桶里。你吃饭消费20元,等同于把你的绿豆从桶里丢掉20个。这就是单式记账,只有绿豆的数量变化,不记录绿豆的来源与去向。

我们再加上两个桶,分别是费用(Expenses)收入(Income)桶。这样的话:

  • 收入:资产(Assets)桶 +5000,收入(Income)桶-5000。
  • 吃饭:资产(Assets)桶 -20,费用(Expenses)桶 +20.

消费就等同于把你的绿豆从资产(Assets)丢到了费用(Expenses),而收入就等同于把收入(Income)里的绿豆挪到了资产(Assets)

──你可能会发现一个问题,「收入」桶里的豆子从哪儿来呢?

我们把时间跨度拉长到人的一生来。人活这一辈子,赚的钱,要么花出去了,要么就变成了资产。把收入桶看成是一个装满你一生劳动成果的桶,人生就是不断的把这个桶里的豆子挪到另外两个桶里(听起来很凄惨)。几个桶里的豆子总数不会变多,也不会减少,永远保持固定的数量

然而,我们并不知道我们一生的豆子总数到底会是多少;那就换个思路,把桶变成一个无底大桶,就算没有豆子也能变出豆子来,也就是预支。回到上面的例子,从 收入(Income)桶 里拿出5000个豆子给资产(Assets)桶收入(Income)桶里的豆子数量就变为-5000个了;同样,资产(Assets)桶的豆子数量为+5000个。可以看出,几个桶里的豆子总数一定为0,总量永远不变。

──如果总数不为0,一定是账记错了。

Beancount的原理就是把记账看成是倒豆子,提供了五个大桶,分别是:

  • 资产 Assets,正数,记录现金、银行存款;
  • 负债 Liabilities,正数,记录信用卡、房贷、车贷等;
  • 费用 Expenses,正数,记录各种消费等;
  • 收入 Income,负数,记录工资、奖金等;
  • 权益 Equity,负数,净资产,存放在记账开始前已经有的权益。

《国际会计准则》里提出了会计五要素,与这里的五个大桶正好对应。(我国《企业会计准则》里是会计六要素,多了一个「利润」,不过这里我们用不着)。在这五个大桶里,你可以不断地放小桶(也就是开账户)。之后,记账也就变成在桶中转移豆子了。

假设我2019年开始记账,手上现在有5000元现金:

  • 净资产 -5000元,Assets:现金 +5000元。
  • 我吃一顿饭,信用卡消费50元。
    • Liabilities:信用卡 -50元
    • Expenses:饮食 +50元
  • 我发了工资,收到了5000元
    • Income:工资 -5000元
    • Assets:现金 +5000元
  • 我把信用卡还上了。
    • Assets:现金 -50元
    • Libailities:信用卡 +50元
  • 我用现金和信用卡买了一部小米Mix Alpha。
    • Assets:现金 -4950元
    • Libailities:信用卡 -15050元
    • Expenses:手机 20000元

从开始到结束,每一笔交易都满足:(Income + Liabilities) + (Assets + Expenses) + Equity = 0

可以发现,这种记账方式非常地清晰:

  • 每笔交易在不同账户的数字加起来和为 0
    • 如果加起来不为0就能证明记账记错了,一下子就能发现记账错误。
    • 允许一笔交易里出现多个账户,能够应付有多个对象出现的复杂交易。
  • 直接将钱财的来源与去向写得清清楚楚,把交易与账户建立了关联。

顺带一提,不要在意账本里的正负号。这只是把中国使用的「借」「贷」换成了正负号而已,「有借必有贷,借贷必相等」。我们看账本只需要在乎绝对值,符号没什么实际意义。(资产为负数的话算我没说)

「实践是检验真理的第一标准」,让我们开始吧。

使用

创建一个空白账本main.bean

python -m venv env
source env/bin/active
pip install beancount fava
fava main.bean

之后打开浏览器 localhost:5000 即可

基本用法请参考以下数篇文章,嗯,我懒得写了。

记账进阶

在Beancount的帮助下,你有了详细记录每一笔收入和支出的能力;甚至可以做到把Beancount的记录当成一天的日记的效果。这也是我特别喜欢用Beancount记账的原因。下面,我会基于我的日常生活经验,给出一些相对比较麻烦的账的例子:

退款

退款很好记啊,记为收入不就成了么?就我个人看来,这会使得个人收入畸高,同样地对应帐目的支出也会偏高。我喜欢这么记,从哪儿来的,还到哪儿去:

2019-11-10 * "购买手机"
    Expenses:Mobile    10000 CNY
    Assets:Cash       -10000 CNY

2019-11-11 * "手机降价,保价"
    Expenses:Mobile    -1000 CNY
    Assets:Cash         1000 CNY

货币转换

Beancount没有预先定义任何货币,因此我们可以记录任何我们想记的东西。

代币

很多时候,我们的收入/支出并不是法币,而是代币。例如,挖漏洞获得SRC平台的金币。这种情况下,你有两种记账方案。一种是实际兑换为人民币的时候才记为收入,另一种,是干脆把这种金币也记录在内,这样像我这种强迫症就感觉比较爽。只要在fava右上角点击convert to CNY,就可以忽略这些奇奇怪怪的代币了。

2019-01-01 * "TSRC" "挖洞收入"
    Income:BugBounty      -300 TAQB      ; 腾讯安全币
    Assets:SRC:Tencent     

2019-01-01 * "TSRC" "兑换书"
    Assets:SRC:Tencent    -10 TAQB       ; 支出10个安全币
    Expenses:Book                        ; 兑换到了一本书

2019-01-01 * "TSRC" "兑换人民币"
    Assets:SRC:Tencent    -100 TAQB @@ 1000 CNY     ; 支出100个安全币
    Assets:Cash           1000 CNY       ; 得到1000人民币(我随便定的汇率别打我.jpg)

你甚至可以拿来记录调休/年假:

2019-04-01 * "公司" "加班调休"
  Income:Overtime                              
  Assets:Leave             1.00 DAY

2019-05-01 * "公司" "使用调休"
  Expenses:Health
  Assets:Leave            -1.00 DAY

礼品卡

你用九折的价格买了一大堆京东e卡,之后使用这些e卡进行消费。你有几种记账方案。

1970-01-01 open Assets:JD JD
1970-01-01 open Assets:Cash CNY
1970-01-01 open Expenses:Something CNY

2019-01-01 * "京东" "购买京东e卡"
  Assets:JD             1000 JD @@ 900 CNY
  Assets:Cash          -900 CNY

2019-01-01 * "京东" "买东西"
  Expenses:Something     500 CNY @@ 500 JD
  Assets:JD             -500 JD

以上账的意思是,你用900个CNY豆子,买了1000个JD豆子。这1000 JD仍然是你的资产而不是消费。之后,把500个JD豆子当成500个CNY豆子进行了消费。这种方式你可以比较清楚地看清京东卡的余额,通过对比JD和CNY的绝对值也能看到你通过购买京东卡究竟省下了多少钱。

当然,你也可以不用 JD,直接记成CNY,这样可以认为你用900元买了1000元人民币,也不是不行。

2019-01-01 * "京东" "购买京东e卡"
  Assets:JD             1000 CNY @@ 900 CNY
  Assets:Cash          -900 CNY

2019-01-01 * "京东" "买东西"
  Expenses:Something    
  Assets:JD             -500 CNY

如果你打算只使用人民币记账的话,你也可以把购买京东e卡作为你的收入,虽然我觉得这么做账不太好看,我喜欢第一种把京东e卡和人民币隔离开来的做法。

2019-01-01 * "京东" "购买京东e卡"
  Assets:JD            1000 CNY
  Income:JD            -100 CNY
  Assets:Cash          -900 CNY

自然,这种记账方法也适用于信用卡里程、希尔顿积分等一切可以直接购买的商品。

多币种记账

你消费了一笔日元,信用卡以美元入账,之后你打算选一天人民币对美元汇率较好的时候手动以人民币还款。

2019-10-01 * "日元支付" 
  Expenses:Something
  Liabilities:CreditCard -84.52 USD @@ 9000 JPY

2019-10-10 * "信用卡还款"
  Liabilities:CreditCard 592.27 CNY @@ 84.52 USD
  Assets:Cash

可以看到,只需要进入账单的金额平了即可。

应收应付

垫付报销

垫付有多种情况,聚餐时的AA制、公司出差时事后报销,都会导致不得不进行垫付。当然,垫付并不代表实际支出,它只是代表你的钱从你的现金账户里,转移到了「应收账款」这个账户,你的净资产仍然不变。这种情况下应当记账记为:

2019-09-30 * "出门吃饭"
    Assets:Cash            -200 CNY
    Expenses:Eating          50 CNY
    Assets:Receivables      150 CNY

2019-09-30 * "朋友A付款"
    Assets:Receivables      -50 CNY
    Assets:Cash              50 CNY

当然,因为Beancount开一个账户很容易,更详细的记账方式是:

2019-09-30 open Assets:Receivables:A CNY
; ......
2019-09-30 * "出门吃饭"
    Assets:Cash              -200 CNY
    Expenses:Eating            50 CNY
    Assets:Receivables:A       50 CNY
    Assets:Receivables:B       50 CNY
    Assets:Receivables:C       50 CNY

2019-09-30 * "朋友A付款"
    Assets:Receivables:A      -50 CNY
    Assets:Cash                50 CNY

2019-10-01 close Assets:Receivables:A 

不过这样显得比较麻烦就是了,我觉得统计每个朋友AA给你多少钱这个数据没多大意义。

如果是公司出差等报销时间会拖得比较长的情况下,我推荐使用tag_pending这个官方插件。开启此插件,并给待报销的交易加上^tag即可。该插件会为所有总额不为0的标签自动加上#PENDING,一目了然。

plugin "beancount.plugins.tag_pending"

2019-09-30 * "垫付机票" ^201909-out
    Assets:Cash           -1000 CNY
    Assets:Receivables     1000 CNY

2019-10-01 * "机票报销" ^201909-out
    Assets:Receivables    -1000 CNY
    Assets:Cash            1000 CNY

财务代管

你是一个小团体的财务负责人,这个团体的钱全部都在你手上,团队的收入交给你,团队的支出由你报销。但是,团队的钱并不是你的钱,它们应当被作为负债而存在。当然,这只是资产负债表扩大而已,你的净资产仍然不变。

2019-09-30 open Liabilities:Team
2019-10-01 * "财务移交"
    Liabilities:Team    -10000 CNY
    Assets:Cash          10000 CNY

当你需要为团队成员报销的时候,相当于,你的资产和负债都相应地减少了一部分。

2019-10-02 * "报销"
    Assets:Cash        -50 CNY
    Liabilities:Team    50 CNY

在这种情况下,直接阅读Liabilities:Team的日记账,即可获得所有这个小团体的收入和支出明细信息。

你作为团队队长,被承诺了一笔奖金和一笔报销金额(应收账款),但除了你自己要拿一部分以外,你还要给你的团队成员分奖金以及给他们报销(应付账款)。在没收到钱的时候,你用自己的钱给团队成员做了报销,过了半年之后,你才拿到了全部的钱。

2019-10-01 * "奖金"
    Assets:Receivables:Competition    10000 CNY ; 你被承诺了10000元奖金
    Income:Competition                -1000 CNY ; 其中只有1000元是你的应得
    Liabilities:Payable               -9000 CNY ; 剩下9000元是队友们的

2019-10-01 * "报销团队成员A路费"
    Assets:Cash                       -1000 CNY ; 你用你的钱给队友报销了路费
    Assets:Receivables:Reimbusement    1000 CNY ; 这是主办方应该付给你的钱

;...

2020-06-01 * "收到奖金"
   Assets:Cash                        11000 CNY ; 你收到了11000元
   Assets:Receivables:Reimbusement    -1000 CNY ; 你的待收账款:报销 -1000元
   Assets:Receivables:Competition    -10000 CNY ; 你的待收账款:奖金 -10000元

2020-06-01 * "发奖金给队员A"
   Liabilities:Payable                 1000 CNY ; 从你的现金中发奖金给队员A
   Assets:Cash

如果奖金金额过大,那可以记成另外一种货币,比如CNYY,以把已有的钱和未来承诺的奖金分开。

中转账户

我们可以建立一个Assets:ZeroSum的中转账户,方便我们对自己的账本进行更多的处理。如其名字,这个账户是一个余额永远为0的账户。这个账户可以做这些事情:

  • 报销分为已付和未付;可以将已付的报销从Assets:Receivables转移到Assets:ZeroSum,这样查看Assets:Receivables就会仅剩未付报销,让账本更直观。(如果需要修改账本建议使用Git进行版本管理)
  • 遇到其他一些钱只是过你的手,把你当成临时中转的情况;不记账固然可以,但今后查帐可能会出现疑虑。记账吧又需要交易对手,因此需要一个中转账户临时使用。

中转账户的基本用法如下:

9999-99-99 balance Assets:ZeroSum 0 CNY

2019-10-01 * "代收货款"
    Assets:ZeroSum     -1000 CNY
    Assets:Cash         1000 CNY

2019-10-01 * "付货款"
    Assets:ZeroSum      1000 CNY
    Assets:Cash        -1000 CNY

更高级一些的ZeroSum用法,可以参考 https://github.com/redstreet/beancount_plugins_redstreet/tree/master/zerosum 插件

投资

由于我并不炒股,因此证券投资请阅读:https://wzyboy.im/post/1317.html

你可以使用bean-price导入各种其他货币(法币和加密货币)对人民币的当前价格,也可以把基金、股票等一切你想记录价格的东西记录在内。我的bean-price脚本目前提供了对于中行人民币汇率、CoinMarketCap加密货币汇率、同花顺基金价值三种来源的抓取:https://github.com/zsxsoft/my-beancount-scripts

预算

我不做预算,做预算的方法见:http://morefreeze.github.io/2016/10/beancount-advance.html

移动设备记账

不太推荐在移动设备上使用Beancount进行记账,有以下几个原因:

  • Beancount没有比较好用的手机App( 目前有一个比较简单的:https://github.com/xuhcc/beancount-mobile ),只能在服务器上部署账本后把fava暴露在公网里。
  • Beancount需要输入的信息较多,使用手机键盘效率较低,不太适合使用。

如果想在手机上随时随地看账本的话,比较合适的方式是,把账本部署在服务器后让fava为公网提供服务,而不是在手机上打开账本。关于这一点,请阅读接下来的「账本安全」一节。

如果只是需要对现金收入支出进行记账的话,建议还是使用随手记、网易有钱等App备忘,之后再统一导入到Beancount里。

BQL

BQL是Beancount提供的一种类似SQL的查询语言,虽然很强大,不过就我个人来看不是太好用。至于BQL的使用请自行阅读官方文档,我在这里只记录几个我摸索后得出的,可能比较常用但是看文档不是很好猜出来怎么写的查询例子。

select * where account ~ "Expenses" and month = 12 and year = 2019

以上语句可以得到2019年12月所有的支出。~代表通过正则表达式匹配。

select * where account = "Liabilities:CreditCard:A" and ((month = 10 and day < 10) or (month = 9 and day >= 10)) and currency = "CNY"

以上语句,可得到 9/10 - 10/10 期间,通过A行信用卡支付的所有人民币交易。

select * where account = "Liabilities:CreditCard:A" and abs(number) > 1000

以上语句,可以得到通过A行信用卡支付的,入账金额大于1000个豆子(而不是1000元人民币)的所有交易。

select * where account = "Liabilities:CreditCard" and abs(number(convert(units(position), 'CNY'))) > 100

以上语句,可以得到通过A行信用卡支付的,入账金额大于100元人民币的所有交易。这里进行了货币转换操作。

SELECT
    account, sum(cost(position)) as total, month
FROM
     year = YEAR(today()) and month = MONTH(today())
WHERE
    account ~ 'Expenses:*'
GROUP BY month, account
ORDER BY total, account DESC

统计本月所有支出( 来自http://morefreeze.github.io/2016/10/beancount-advance.html ),顺带一提这个直接在fava右上角指定日期后看损益表即可。

如果不指定账户和金额,打算根据指定日期、指定货币等查询交易的话,相对比较麻烦。因为复式记账的原因,一笔交易总有两个以上账户产生关联,于是每次查询,针对一笔交易,都会至少返回两条结果。这种情况下,你可能需要group by id

自动化记账

到这里,需要你会编程了。

我相信大部分人和我一样,不太乐意每天手动记账,这显然过于麻烦;自动记账,这也是「网易有钱」等记账软件曾经主打的一个功能。不过由公司做这个很明显违反了支付宝的协议。但我们作为个人,是可以自己拉自己的账单的。支付宝也提供了这么一个功能,有了账单,就可以实现自动导入了。

自然,Beancount也提供了一个bean-extract工具帮助你导入账单,它可以根据你提供的规则解析各种CSV文件。作为习惯使用信用卡支付的国外用户来说这已经足够。不过,对于我们这些身处中国大陆的用户来说,这个轮子是「方」的。中国是一个第三方支付工具比(直接用)银行卡支付覆盖率更广的国家。

选择支付手段

想要实现尽量完善的自动记账,必须选择合适的支付手段。不是所有的支付方式都会为你提供账单,我认为从账单的详细程度可以从一定角度上看出某个支付工具的专业性。强烈建议只使用以下三种支付方式,因为它们通常都可以提供可以在电脑上打开的、详细的账单:

  • 支付宝
  • 微信
  • 各家银行

为了达成完美的自动对账,你应该拒绝使用或尽量避免使用:

  • 现金
  • 支付宝
    • 仅使用余额宝和银行卡(包括借记卡和信用卡支付)
    • 不使用支付宝余额、花呗、余利宝、网商银行等所有其他支付手段。
    • 由于支付宝的账单不包含支付渠道,一旦使用这些手段,你就必须在导入账单完成之后,打开支付宝App,一笔一笔找到每笔的StupidAlipay对应的交易修复支付渠道。
  • 美团
    • 2019年中旬起,美团提交给微信/支付宝的账单只有订单号,没有具体支付店家了。你不知道你到底是美团打车、还是美团外卖、还是美团团购。
    • 对于外卖需求,最好使用饿了么而非美团外卖。饿了么在支付宝内的订单可显示具体店家。
    • 如果一定要用美团,最好只使用微信/支付宝作为支付手段,不使用美团支付。这样至少能看到准确的交易时间和订单号。
  • 京东
    • 在京东只使用微信支付作为支付手段。
    • 使用京东白条、零钱和小金库,你无法在电脑上获得任何账单,只能在手机上看。因此,你也只能手工录入京东账单。
  • 各种弱智贷款软件、各种奇奇怪怪的理财软件

并不是说完全不能使用这些支付手段。这些支付手段的共同问题是,你很难在电脑上对它们进行对账。个人认为,对于一切不能在电脑上对账的电子支付工具,都要对它们的专业度和诚意打一个巨大的问号。在这里我也要喷一下支付宝把不少账单都转移到手机上的弱智行为,再喷一下京东只在手机上提供账单的行为。我想反问一下那几位产品经理:你们都是在手机上算账的?

很多时候这些支付手段都有一些小红包、小奖励;现金支付也很难避免。个人认为,如果只是几毛几分的小优惠,那就完全不值得把自己的银行卡信息交给它们,既对自己的隐私有害,也浪费自己手工对账的时间。遇到大红包,或是遇到非现金不可的情况下,就只能在支付的当天手工记账,以最大限度保证自己的帐务正确了。

我的工作流

我的脚本

因为中国的特殊国情,因此,我编写了一系列脚本用于从支付宝、微信等导入账单:https://github.com/zsxsoft/my-beancount-scripts ,具体使用请直接查看GitHub。如果你持有的银行卡恰好和我一样,那你就可以一行代码都不写,只需要改改配置文件就好啦。如果不一样的话,你就得自己去写对应的导入脚本了。

顺带一提,我不喜欢Python,也不熟悉它,代码风格等都非常随意。因为这只是我的个人使用脚本,我也不打算提供任何技术支持,或是将它工程化。请不要向我提出编写XX银行脚本的需求。

Why not bean-extract?

使用bean-extract而非我的脚本,同时导入第三方支付和银行卡账单,百分之百会导致账单重复,必须人工去重。你可以像wzyboy一样只把支付宝、微信作为一个和银行对接的支付渠道,在它们内部一分钱都不留,这样就不需要导入支付宝和微信的账单了。不过我认为这种方式并不妥当,因为支付宝和微信的账单信息量远大于银行账单,抛弃它们有些得不偿失。仅导入银行卡账单的话,可能会出现这种谜之情况,让人无从下手:

你也可以像lidongchao一样,他的这篇文章提供了bean-extract使用支付宝和微信账单的导入规则。这篇文章写,他只导入支付宝和微信账单,手工填写支付宝中每一笔交易的支付渠道,银行卡仍然由人工记账。个人认为,他的工作流还是挺麻烦的。

如果你还是决定使用bean-extract的话,可以参考这篇文章,编写属于你的导入器:https://yuchi.me/post/beancount-intro/

我的工作流

推荐的导入顺序:支付宝、微信、余额宝、银行卡账单。

  1. 先导入支付宝和微信账单,因为这两个支付方式留有最多的信息,方便对账。
  2. 导入余额宝账单。该导入器不会添加任何交易,其唯一的作用是,将已经导入的支付宝账单里时间和金额匹配的交易的支付手段改为余额宝。
  3. 导入银行卡账单。由于账单去重的存在,不会重复导入之前通过支付宝和微信已导入的交易,且会自动修复支付方式不正确的支付宝账单。
  4. 对于支付宝,检查一下还有多少笔交易在使用Assets:Company:Alipay:StupidAlipay这个账户。它有可能是余额支付、花呗支付、网商银行支付等。与支付宝账单对照,手动将所有的StupidAlipay改为正确的支付手段。所以最好不要使用花呗,支付宝余额也尽量调为0,这样就能免去重新对账的麻烦了。
  5. 手动添加balance指令,并对照余额是否正确,账单是否全部导入。

项目管理

关于交易的标签、账本的划分等,可直接阅读:https://www.byvoid.com/zhs/blog/beancount-bookkeeping-4

账本划分

我个人认为,Byvoid使用三种划分方式,实在是过分麻烦。有fava不用,为什么要面对着一些纯文本账单来做「纵向分析」呢?由于我基本是使用自动记账的,我的项目管理方式非常简单粗暴,如图所示:

即:

  1. 有大event就写到一起去,方便报销、算账,也能作为一个简单的旅行日记。在每一个event文件的头部加上pushtag,尾部加上poptag,也方便进行标签管理。
  2. 日常支出由于大部分是自动导入的,因此全部直接以「支付渠道/日期」进行划分。如果害怕某些事项忘记需要手工填写,也直接往对应的文件填写。
  3. 如有各类基金投资等特殊需求,再开一个别的文件。

版本管理与账本安全

由于Beancount是纯文本记账的关系,因此最方便的版本管理工具就是Git了,然后上传到GitHub Private Repo即可;如果不太放心GitHub,还可以使用 https://github.com/AGWA/git-crypt 对账本进行二次加密。需要注意的是,若对整个Repo进行加密,则无法在GitHub网页上查看diff,在方便程度上会略有损失。

使用Dropbox、坚果云等带版本管理的同步工具也可以,使用这些工具的话,可以配合rclone进行加密:https://rclone.org/crypt/。

如果想要在手机上编辑账本的话,由于目前暂时没有支持各类网盘的Beancount客户端,因此目前唯一的办法是把账本部署在服务器上之后,把fava开在公网上。由于fava目前完全没有权限相关功能,且我怀疑BQL部分可能存在安全隐患,因此直接让fava公开在0.0.0.0是非常不安全的。

最低限度的安全措施如下:

  1. 将fava仅暴露在127.0.0.1
  2. 使用Nginx将其转发到公网,并配置好HTTPS证书。
  3. 加一层Basic Auth,这是最简单的密码认证,要求访问者至少输入一个密码:https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
  4. 使用fail2ban,限制密码错误重试次数:https://serverfault.com/questions/421046/how-to-limit-nginx-auth-basic-re-tries/854889

BTW,我只在自己个人电脑上查看和编辑账本,没有部署到服务器上。毕竟我觉得Apple T2的安全性可以信赖,我才不相信自己配的服务器没有漏洞(

尾声

我已经将最近一两年内的所有账单,上万条记录全部导入进Beancount;因为其是文本数据库+Python编写的缘故,我原以为性能会变得极为低下,不过事实证明并无大碍,没有感受到明显的延迟。

记账是一件挺麻烦的事情,不过,Beancount成功让我把记账变成了一项业余娱乐活动,可喜可贺可喜可贺.jpg

如果本文对你有帮助,你可以用支付宝支持一下:

Alipay QrCode
maqingxi at 2019/11/28[回复]
曾经也有一阵子记帐,也用过挖财一类的程序,但没能坚持下来。