跳转到主要内容

如何用Python脚本自动化Beancount和Fava

Beancount 与 Fava 保持可脚本化:使用 Python 自动生成报告、查询余额,并对账本运行自定义工作流以及日常批处理任务脚本。

Beancount(一款纯文本复式记账工具)和 Fava(它的 Web 界面)具有极强的可扩展性和可脚本化能力。它们的设计让你可以通过编写 Python 脚本来自动化财务任务、生成自定义报告、设置提醒。用一位用户的话来说:“我真的很喜欢把数据保存在这么方便的格式里,而且我喜欢可以随心所欲地自动化一切。没有什么 API 比得上你磁盘上的一个文件,它太容易集成了。” 本指南将带你走一遍创建可脚本化工作流的全过程——从适合初学者的自动化,到进阶的 Fava 插件。

探索一个实时示例账本:

在新标签页中打开 示例账本

从 bea 命令行开始​

在写任何 Python 之前,先看看 bea 是否已经能完成这项任务。它会校验账本、运行 BQL 查询、生成四大财务报表、导入银行导出文件,而全局 --json 会把上述每一项都变成一个可解析的信封,供你的 shell 管道传给 jq。它的退出码是定时任务进行分支判断的契约,因此 cron 或 CI 完全不需要加载脚本。关于目标解析、信封与退出码分支,参见用 bea 自动化记账,当你需要 CLI 未暴露的自定义计算时再回到这里。

入门:将 Beancount 作为 Python 脚本运行​

对于下面这些自定义 Python 脚本,先安装脚本库(pip install beancount beanquery beangulp)。bea 命令工作流改用托管引擎,请按 CLI 快速入门 安装。由于 Beancount 是用 Python 写的,你可以在自己的脚本里把它当作库来用。下面的脚本是用 Beancount 3.2.3、beanquery 0.2.0 和 beangulp 0.2.0 执行的。总体思路是:

  • 加载你的 Beancount 账本:用 Beancount 的加载器把 .beancount 文件解析成 Python 对象。例如:

    from beancount import loader
    entries, errors, options = loader.load_file("myledger.beancount")
    if errors:
        for error in errors:
            print(error)
        raise SystemExit(1)

    加载器会把 entries 和 errors 一起返回。不平衡或无效的文件依然会返回 entries,所以要先检查 errors,在信任数据之前就停下。现在你的所有账户、交易和余额都可以在代码中访问了。

  • 利用 Beancount 查询语言(BQL):不必手动遍历,你可以对数据运行类 SQL 查询。查询位于独立的 beanquery 包中。Beancount 3.2.3 里没有 beancount.query 模块。例如,要按月统计总支出,把加载好的 entries 传给查询并直接执行:

    import beanquery
     
    conn = beanquery.connect("beancount:", entries=entries, errors=errors, options=options)
    cur = conn.execute(
        "SELECT year, month, sum(position) WHERE account ~ 'Expenses' GROUP BY year, month"
    )
    for row in cur.fetchall():
        print(row)

    这里用 beanquery 做数据聚合。它和 bea query 背后是同一个引擎,只不过这里你是在脚本里调用它,从而避免在循环里不断 shell 出去调用外部命令。

  • 搭建项目结构:把你的脚本和账本放在一起组织。一种常见的布局是为 importers(获取/解析外部数据)、reports 或 queries(分析脚本)、documents(存放下载的对账单)分别建目录。例如,有位用户这样组织:

    • importers/ —— 自定义 Python 导入脚本(带测试),
    • queries/ —— 生成报告的脚本(可通过 python3 queries/... 运行),
    • documents/ —— 按账户整理下载的银行 CSV/PDF。

有了这套结构,你可以手动运行脚本(比如 python3 queries/cash_flow.py),也可以用 cron 或任务调度器定时运行,实现工作流自动化。

自动化对账任务​

对账就是确保你的账本与外部记录(银行对账单、信用卡报告等)一致。Beancount 的纯文本账本和 Python API 让自动化这其中大部分流程成为可能。

导入和匹配交易(入门)​

对于初学者,推荐的做法是使用来自独立 beangulp 包的 importers。Beancount 3 移除了 v2 的 ingest 模块及其 extract 命令。你写一个小小的 Python 类,继承 beangulp.Importer,用来解析某种格式(CSV、OFX、PDF 等)并生成交易。把它注册到一个简短的 ingest 脚本里,然后在托管引擎中通过 bea ingest 运行:

  • 为你的银行 CSV 格式写一个 importer(一个 Python 类,包含 identify()、account() 和 extract() 方法)。
  • 添加一个 ingest 脚本,把 importer 注册进去。bea ingest 会运行该脚本的 identify、extract 和 archive 命令。例如,有个工作流会对 ~/Downloads 里的所有文件运行 extract,把交易输出到一个临时文件。
  • 手工审阅临时文件里的交易并复制到你的主账本中,然后运行 bea check 确保余额对得上。

一个最小示例:statement.csv 含 date,description,amount 列,由下面这个 importer(checking_importer.py)解析:

import csv
import datetime
from beancount.core import data
from beancount.core.amount import Amount
from beancount.core.number import D
import beangulp
 
 
class CheckingImporter(beangulp.Importer):
    def identify(self, filepath: str) -> bool:
        return filepath.endswith("statement.csv")
 
    def account(self, filepath: str) -> str:
        return "Assets:Bank:Checking"
 
    def extract(self, filepath: str, existing):
        entries = []
        with open(filepath, newline="") as f:
            for row in csv.DictReader(f):
                date = datetime.date.fromisoformat(row["date"])
                amount = Amount(D(row["amount"]), "USD")
                meta = data.new_metadata(filepath, 0)
                entries.append(
                    data.Transaction(
                        meta, date, "*", None, row["description"],
                        data.EMPTY_SET, data.EMPTY_SET, [
                            data.Posting("Expenses:Food:Groceries", amount,
                                         None, None, None, None),
                            data.Posting("Assets:Bank:Checking",
                                         Amount(-amount.number, "USD"),
                                         None, None, None, None),
                        ]))
        return entries

ingest 脚本(ingest.py)把它串起来:

from checking_importer import CheckingImporter
from beangulp import Ingest
 
ingest = Ingest([CheckingImporter()])
 
if __name__ == "__main__":
    ingest()

对下载的文件运行它。本地 CSV 不需要任何凭据。先安装系统的 libmagic 库。这条一次性启用命令会把 Beangulp 下载进托管引擎:

bea engine enable beangulp
bea ingest identify --config ingest.py statement.csv
bea ingest extract --config ingest.py statement.csv -o new.beancount

identify 对该文件报告 checking_importer.CheckingImporter。extract 会以 Beancount 格式写出交易:

2024-01-08 * "Grocery Store"
  Expenses:Food:Groceries   120.00 USD
  Assets:Bank:Checking     -120.00 USD

审阅 new.beancount,把条目复制进你的主账本,然后运行 bea check。

一次性转换可以跳过 importer

要转换单份对账单,不一定非得写 importer。把文件粘进 CSV 转 Beancount 转换器,或者对 .ofx、.qfx 和 .qif 下载用 OFX 与 QIF 转 Beancount。这两个工具完全在浏览器里运行,所以对账单永远不会离开你的机器。

虽然这个过程仍然有一步人工审阅,但解析和格式化条目的大量苦活都自动化了。Importer 脚本还可以自动分配分类,甚至设置余额断言(对预期余额的声明),以便捕捉差异。例如,导入后你可能有一行 2025-04-30 balance Assets:Bank:Checking 1234.56 USD,它断言了期末余额。当你运行 bea check 时,Beancount 会_验证所有这些余额断言是否正确_,如果交易缺失或重复就会标记出错误。这是一个最佳实践:为每个对账单周期自动生成余额断言,让计算机替你发现未对平的差异。

自定义对账脚本(中级)​

想要更多控制权,你可以写一个自定义 Python 脚本来比对银行的交易列表(CSV 或通过 API)与你的账本条目:

  1. 读取外部数据:用 Python 的 csv 模块(或 Pandas)解析银行 CSV 文件。把数据规范化为一个交易列表,比如每笔都包含日期、金额和描述。
  2. 加载账本交易:像前面那样用 loader.load_file 拿到所有账本条目。把这个列表过滤到相关账户(比如你的活期账户),也许再按对账单的日期范围过滤。
  3. 比对并找出不匹配:
  • 对每笔外部交易,检查账本里是否已存在完全相同的条目(按日期和金额匹配,可能还要看描述)。如果没找到,就标记为“新”的,也许把它输出成一条 Beancount 格式的交易供你审阅。
  • 反过来,找出该账户里那些没有出现在外部数据源中的账本条目——它们可能是录入错误,或是尚未在银行清算的交易。
  1. 输出结果:打印一份报告,或者用缺失的交易创建一个新的 .beancount 片段。

举个例子,一个叫 reconcile.py 的社区脚本正是干这个的:给定一个 Beancount 文件和一个输入的 CSV,它会打印一份应被导入的新交易列表,以及任何不在输入中的现有账本过账(可能是分类错误的迹象)。有了这样的脚本,每月对账就简单到只需运行它,然后把建议的交易追加到账本里。一位 Beancount 用户说,他们“每个月都会对所有账户做一次对账流程”,并用越来越多的 Python 代码来消除导入和对账数据中的大量手工工作。

小贴士: 对账时,利用 Beancount 的工具来保证准确性:

  • 如前所述,使用余额断言,对账户余额做自动化检查。
  • 如有需要,使用 pad 指令,它可以为微小的舍入差异自动插入平衡条目(谨慎使用)。
  • 为你的 importer 或对账逻辑写单元测试(Beancount 提供了测试辅助工具)。例如,有个工作流是取一份样本 CSV,写出带预期交易的失败测试,然后实现 importer 直到所有测试通过。这能确保你的导入脚本在各种情况下都能正确工作。

生成自定义报告和摘要​

虽然 Fava 提供了许多标准报表(利润表、资产负债表等),你仍可以用脚本创建自定义报告。它们可以从简单的控制台输出,到格式丰富的文件或图表。

查询数据以生成报告(初级)​

在最基础的层面,你可以用 Beancount 查询语言(BQL)拿到汇总数据,然后打印或保存。例如:

  • 现金流摘要: 用查询计算净现金流。“现金流”可以定义为某些账户余额在某段时间内的变化。用 BQL,你可以这样写:

    SELECT year, month, sum(position)
    WHERE account ~ 'Income' OR account ~ 'Expenses'
    GROUP BY year, month

    这会按月份把所有收入与支出过账净额算出来。用 ~ 加正则表达式来过滤:在 beanquery 0.2.0 里 LIKE 是语法错误。过账携带的是 position,不是 amount。每一行持有的是一个 Inventory,所以每种货币都单独列出,而不是被换算到一起。收入是负值、支出是正值。你可以通过 bea query 运行它,也可以像前面那样用 beanquery 的 Python API,然后再格式化结果。

  • 分类支出报告: 按分类查询总支出:

    SELECT account, sum(position)
    WHERE account ~ 'Expenses'
    GROUP BY account
    ORDER BY sum(position) ASC

    这会生成一张按分类的支出表。每个合计都是一个以其原始货币计价的 Inventory。不要给聚合结果套上 round():不存在 round(inventory, int) 这样的函数,所以 round(sum(position), 2) 无法编译。你可以在一个脚本里跑多个查询,并把结果输出为文本、CSV 甚至 JSON 供进一步处理。

有位用户觉得用 Fava 或脚本分析财务数据是件“轻而易举”的事,他说自己用一个 Python 脚本通过查询语言从 Beancount 中取出数据,然后放进 Pandas DataFrame 里准备一份自定义报告。比如,你可以用查询取出月度合计,然后用 Pandas/Matplotlib 画一张随时间变化的现金流图。BQL 与数据科学库的组合,让你能构建出超出 Fava 默认能力的报告。

高级报告(图表、性能等)​

对于更进阶的需求,你的脚本可以计算诸如投资业绩之类的指标,或创建可视化输出:

  • 投资业绩(IRR/XIRR):由于你的账本包含了所有现金流(买入、卖出、分红),你可以计算组合收益率。例如,你可以写一个脚本,筛选出投资账户的交易,然后计算内部收益率。有现成的库(或公式)可以在给定现金流数据的情况下计算 IRR。一些社区开发的 Fava 扩展(如 PortfolioSummary 或 fava_investor)正是这么做的,为投资组合计算 IRR 及其他指标。作为脚本,你可以对一系列投入/取出加上期末价值,套用一个 IRR 函数(来自 NumPy 或你自己写的)。

  • 多周期或自定义指标:想要一份每月储蓄率(储蓄与收入之比)的报告?一个 Python 脚本可以加载账本,汇总所有 Income 账户和所有 Expense 账户,然后计算 savings = income - expenses 及其百分比。这可以输出一张漂亮的表,甚至生成一份 HTML/Markdown 报告存档。

  • 可视化:你可以在 Fava 之外生成图表。例如,在脚本里用 matplotlib 或 altair,基于账本数据画一张净资产随时间变化图。因为账本有全部历史余额(或者你可以遍历条目累加出来),你可以生成时间序列图。把这些图表保存为图片或交互式 HTML。(如果你更喜欢应用内可视化,参见下面的 Fava 扩展一节,了解如何在 Fava _内部_添加图表。)

输出选项: 决定如何交付报告:

  • 对于一次性分析,打印到屏幕或保存为 CSV/Excel 文件可能就够了。
  • 对于仪表盘,可以考虑把数据生成一个 HTML 文件(可以使用 Jinja2 之类的模板库,甚至直接写 Markdown),在浏览器中打开。
  • 你也可以接入 Jupyter Notebook 获得交互式报告环境,不过那更偏向探索而非自动化。

从你的账本触发警报​

可脚本化工作流的另一个强大用途,是根据财务数据中的条件设置提醒。由于你的账本会被定期更新(而且可以包含未来日期的事项,如即将到期的账单或预算),你可以用脚本扫描它,并在发生重要事件时收到通知。

低账户余额警告​

为了避免透支或维持最低余额,你可能希望在任何账户(比如活期或储蓄)跌破阈值时收到提醒。可以这样实现:

  1. 确定当前余额:通过 loader 加载 entries 后,计算相关账户的最新余额。你可以通过聚合过账或使用查询来做。例如,用 BQL 查询某个特定账户的余额:

    SELECT sum(position) WHERE account = 'Assets:Bank:Checking'

    这会返回该账户的当前余额(其所有过账之和)。或者,用 Beancount 的内部函数构建资产负债表。例如:

    from beancount.core import realization
    tree = realization.realize(entries)
    acct = realization.get_or_create(tree, "Assets:Bank:Checking")
    balance = acct.balance  # an Inventory of commodities

    只传 entries:第二个参数是 min_accounts,不是 options 映射。然后取出数值(比如 balance.get_currency_units('USD') 返回以 USD 计的 Decimal 金额)。和查询聚合一样,余额会把每种货币单独保留。不过对大多数情况来说,用查询更简单。

  2. 检查阈值:把余额与你预设的限额比较。如果低于它,就触发提醒。

  3. 触发通知:这可以简单到向控制台打印一条警告,但要真正做提醒,你可能会发邮件或推送通知。你可以接入邮件(通过 smtplib)或 IFTTT、Slack 的 webhook API 之类的服务来推送提醒。例如:

    if balance < 1000:
        send_email("Low balance alert", f"Account XYZ balance is {balance}")

    (用你自己的邮件服务器信息实现 send_email。)

通过每天运行这个脚本(用 cron 任务或 Windows 任务计划程序),你就能获得主动预警。因为它用的是账本,所以能把包括你刚添加的在内的_所有_交易都考虑进去。

即将到来的付款截止日期​

如果你用 Beancount 追踪账单或截止日期,你可以标记未来付款,并让脚本提醒你。在 Beancount 中表示未来义务的两种方式:

  • Events:Beancount 支持用 event 指令记任意带日期的备注。例如:

    2025-05-10 event "BillDue" "Mortgage payment due"

    这不会影响余额,但会记录一个带标签的日期。脚本可以扫描 entries 中 Event.type == "BillDue"(或你选择的任何自定义类型)的 Event 条目,并检查日期是否在比如从今天起 7 天之内。如果是,就触发提醒(邮件、通知,甚至弹窗)。

  • 未来交易:有些人会为预定付款等事项录入未来日期的交易(倒填日期)。这些在日期到来之前不会出现在余额里(除非你按未来日期出报表)。脚本可以找出日期在不久未来的交易并列出它们。

利用这些,你可以做一个“备忘”脚本,运行时就输出一份即将到期的任务或账单列表。如果你想让它在 Google Calendar 或某个任务管理器里自动创建提醒,就接入相应的 API。

异常检测​

除了已知的阈值或日期,你还可以为异常模式编写自定义提醒。比如,一项通常按月发生的支出没有出现(也许你忘了付账单),或者某个分类本月的支出异常高,你的脚本都可以把它标记出来。这通常需要查询近期数据并与历史比较(这可能是个进阶话题——也许会用到统计或机器学习)。

在实践中,很多用户依靠对账来捕捉异常(意外交易)。如果你会收到银行通知(比如每笔交易的邮件),你可以用脚本解析它们并自动加入 Beancount,或者至少核实它们已被记录。有一位爱好者甚至把银行配置成发送交易提醒邮件,打算自动解析并追加到账本。这类事件驱动的提醒可以确保没有交易会漏记。

使用自定义插件和视图扩展 Fava​

Fava 本身就已经通过它的扩展系统实现了可脚本化。如果你想让自动化或报告直接集成到 Web 界面里,你可以用 Python 写一个 Fava 扩展(也叫插件)。

Fava 扩展如何工作: 一个扩展就是一个 Python 模块,定义了一个继承 fava.ext.FavaExtensionBase 的类。你通过一个自定义选项把它注册到 Beancount 文件里。例如,如果你有个文件 myextension.py,里面有个类 MyAlerts(FavaExtensionBase),你就可以在账本里加入以下内容来启用它:

1970-01-01 custom "fava-extension" "myextension"

Fava 加载时,会导入那个模块并初始化你的 MyAlerts 类。

扩展可以做几件事:

  • 钩子(Hooks):它们可以挂进 Fava 生命周期中的事件。例如,after_load_file() 会在账本加载后被调用。你可以用它来运行检查或预先计算数据。如果你想在 Fava _内部_实现低余额检查,after_load_file 可以遍历账户余额,也许把警告存起来(不过要把它们呈现在 UI 上可能还需多做点工作,比如抛出 FavaAPIError 或用 JavaScript 显示通知)。
  • 自定义报表/页面:如果你的扩展类设置了 report_title 属性,Fava 会在侧边栏为它新增一个页面。然后你为该页面的内容提供一个模板(HTML/Jinja2)。这就是你创建全新视图的方式,比如 Fava 默认没有的仪表盘或摘要。扩展可以收集它需要的任何数据(你可以访问 self.ledger,它有所有条目、余额等),然后渲染模板。

例如,Fava 内置的 portfolio_list 扩展会新增一个列出你投资组合持仓的页面。社区扩展走得更远:

  • 仪表盘:fava-dashboards 插件允许定义自定义图表和面板(使用 Apache ECharts 之类的库)。它读取一份要运行的查询的 YAML 配置,通过 Beancount 执行它们,并在 Fava 中生成一个动态仪表盘页面。本质上,它把 Beancount 数据和 JavaScript 图表库绑在一起,生成交互式可视化。
  • 投资组合分析:PortfolioSummary 扩展(用户贡献)会计算投资摘要(分组账户、计算 IRR 等),并在 Fava 的 UI 中显示出来。
  • 交易审查:另一个扩展 fava-review 帮助按时间审查交易(比如确保你没漏掉任何收据)。

要自己创建一个简单扩展,先从继承 FavaExtensionBase 开始。例如,一个新增页面的最小扩展可能长这样:

from fava.ext import FavaExtensionBase
 
class HelloReport(FavaExtensionBase):
    report_title = "Hello World"
 
    def __init__(self, ledger, config):
        super().__init__(ledger, config)
        # any initialization, perhaps parse config if provided
 
    def after_load_file(self):
        # (optional) run after ledger is loaded
        print("Ledger loaded with", len(self.ledger.entries), "entries")

如果你把它放进 hello.py,并在账本里加上 custom "fava-extension" "hello",Fava 就会显示一个新的“Hello World”页面(你还需要在 templates 子文件夹里有一个模板文件 HelloReport.html 来定义页面内容,除非该扩展只用钩子)。模板可以使用你附加到扩展类上的数据。Fava 使用 Jinja2 模板,所以你可以在那个模板里把你的数据渲染成 HTML 表格或图表。

注意: Fava 的扩展系统很强大,但被认为“不稳定”(随时可能变化)。如果你要做自定义页面,它需要你对 Web 开发(HTML/JS)有一定了解。如果你的目标只是运行脚本或分析,把它们保持为外部脚本可能更容易。当你想要为你的工作流打造量身定制的应用内体验时,再用 Fava 扩展。

集成第三方API和数据​

可脚本化工作流的优势之一,就是能引入外部数据。以下是一些常见的集成:

对于托管估值价格,实时价格 提供托管式的 include,无需定时抓取价格的脚本。在选择器里挑好支持的资产对和计价货币即可。下面这些本地文件式工作流对于上游 Beancount、Fava 和可复现报告依然有用。托管刷新不会在你的账本里产生 Git 提交。

  • 汇率与商品:上游 Beancount 本身不抓价格,但它提供 price 指令让你提供汇率。你可以自动化抓取这些价格。例如,脚本可以查询某个 API(Yahoo Finance、Alpha Vantage 等)获取最新汇率或股价,并追加一条 price 条目到你的账本:

    2025-04-30 price BTC 30000 USD
    2025-04-30 price EUR 1.10 USD

    有 bea price 这样的工具,由托管引擎中的 Beanprice 支持,可以抓取每日报价并以 Beancount 格式输出。你可以用 bea engine enable beanprice 一次性启用它,然后安排 bea price main.beancount 每晚运行,更新一个 prices.beancount include 文件。或者用 Python:比如用 requests 库调用 API。Beancount 的文档建议,对于公开交易的资产,你可以“调用一些代码来下载价格并为你写出相应指令。” 换句话说,让脚本去查询并插入 price 行,而不是你手动去做。

  • 股票投资组合数据:和汇率类似,你可以接入 API 获取详细的股票数据或分红。比如 Yahoo Finance API(或 yfinance 之类的社区库)可以取某只股票的历史数据。脚本可以用你持有的每只股票的月度价格历史更新账本,从而实现准确的历史市值报告。一些自定义扩展(如 fava_investor)甚至会在展示时即时抓取价格数据,但最简单的是定期把价格导入账本。

  • 银行 API(开放银行/Plaid):除了下载 CSV,你还可以用 API 自动获取交易。像 Plaid 这样的服务会聚合银行账户,并允许程序化访问交易。在进阶设置中,你可以用一个 Python 脚本通过 Plaid 的 API 每天拉取新交易并保存到文件(或直接导入账本)。有位高阶用户搭建了一套系统,让 Plaid 喂进他的导入流水线,使他的账本几乎全自动。他指出,“没有什么能阻止你注册 Plaid API 并在本地做同样的事”——也就是说,你可以写一个本地脚本获取银行数据,然后用你的 Beancount importer 逻辑把它解析成账本条目。有些地区有银行提供的开放银行 API,也可以类似地使用。

  • 其他 API:你可以集成本预算工具(导出计划预算,与 Beancount 中的实际数比较),或者用 OCR API 读取收据并自动匹配到交易。因为你的脚本能完全访问 Python 生态,你可以集成从邮件服务(发送提醒)到 Google Sheets(比如把每月财务指标更新到表格)再到消息应用(通过 Telegram 机器人给自己发一份摘要报告)的一切。

使用第三方 API 时,记得保管好你的凭据(API 密钥用环境变量或配置文件),并在脚本中优雅地处理错误(网络问题、API 宕机)。通常明智的做法是缓存数据(例如,把抓取到的汇率存起来,免得反复请求同一个历史汇率)。

模块化、可维护脚本的最佳实践​

随着你搭建可脚本化工作流,让代码保持有条理且健壮:

  • 模块化:把不同关注点拆进不同的脚本或模块。例如,把“数据导入/对账”与“报告生成”与“提醒”分成不同脚本。你甚至可以给你的账本建一个小 Python 包,模块如 ledger_import.py、ledger_reports.py 等。这让每一部分都更易理解和测试。

  • 配置:避免硬编码值。把账户名、阈值、API 密钥、日期范围等用配置文件或脚本顶部变量来定义。这让调整无需深入改代码。例如,在顶部定义 LOW_BALANCE_THRESHOLDS = {"Assets:Bank:Checking": 500, "Assets:Savings": 1000},你的提醒脚本就可以遍历这个 dict。

  • 测试:把你的财务自动化当成关键任务代码来对待——因为它确实是!为复杂逻辑写测试。Beancount 提供了一些测试辅助工具(内部用于 importer 测试),你可以利用它们来模拟账本输入。即便不用花哨的框架,你也可以拿一份假 CSV 和预期的输出交易,断言你的导入脚本生成正确的条目。如果你用 pytest,可以轻松集成这些测试(就像 Alex Watt 用一个包裹 pytest 的 just test 命令所做的那样)。

  • 版本控制:把你的账本和脚本纳入版本控制(git)。这不仅给你备份和历史,还鼓励你以受控的方式做改动。你可以给你的“财务脚本”打发布标签,或在调试问题时审阅差异。有些用户甚至用 Git 追踪他们的财务记录,以观察随时间的变化。只是要注意在仓库里忽略敏感数据(如原始对账单文件或 API 密钥)。

  • 文档:为未来的自己记录你的自定义工作流。仓库里一份 README,说明如何搭建环境、如何运行每个脚本、每个脚本做什么,在几个月之后会价值连城。也给代码加注释,特别是任何不显然的会计逻辑或 API 交互。

  • Fava 插件维护:如果你写 Fava 扩展,保持简单。Fava 可能会变,所以功能更有针对性的小扩展更容易更新。避免重复太多逻辑——尽可能用 Beancount 的查询引擎或已有的辅助函数,而不是硬编码可能对账本变化敏感的计算。

  • 安全:由于你的脚本可能处理敏感数据并连接外部服务,要小心对待。不要暴露 API 密钥,并考虑在安全的机器上运行你的自动化。如果你用托管方案或云(比如用 GitHub Actions 或某台服务器跑 Fava),确保你的账本数据在静态时加密,并确保你对隐私影响感到放心。

遵循这些实践,你就能确保即便你的财务状况(以及工具本身)在演变,你的工作流依然可靠。你要的是能年复一年复用、只需极小调整的脚本。

结论​

Beancount 和 Fava 为技术娴熟的用户提供了一个强大、灵活的平台,来完全自定义个人财务追踪。通过编写 Python 脚本,你可以自动化对账等繁琐任务,产出为你量身定制的丰富报告,并通过及时提醒把自己的财务尽在掌握。我们涵盖了一系列从基础到进阶的示例——从简单查询和 CSV 导入,到完整的 Fava 插件和外部 API 集成。在实现这些时,从简单开始、逐步构建。哪怕只是几个小的自动化脚本,也能节省数小时的工作并大幅提升准确性。记住,因为一切都是纯文本和 Python,你拥有完全的控制权——你的财务系统随你一起成长,随你的具体需求弯曲。祝脚本愉快!

来源: 上述技巧取自 Beancount 文档和社区经验。想进一步阅读,参见 Beancount 官方文档、社区指南和博客,以及 Awesome Beancount 仓库,里面有指向有用插件和工具的链接。

来源:https://beancount.io/zh/docs/Solutions/scriptable-workflows