财务建模中级阅读约 9 分钟

在 Google Sheets 中自动化自由职业者的费用追踪

在 Google Sheets 中构建自动更新的费用分类器,支持动态列映射、供应商规则表,并将数据输出至损益表或资金消耗模型。

本指南将带你在 Google Sheets 中构建一套自动化费用分类系统:将杂乱的银行和支付平台导出数据规范化处理,通过可维护的规则表将供应商名称映射至对应类别,最终将整洁的月度实际数据输入损益表结构——这份数据既可用于资金消耗模型,也可直接呈现给投资方。

开始前的准备

  • 拥有可访问 Google Sheets 的 Google 账号
  • 来自银行、Stripe 或财务软件(如用友、金蝶)的 CSV 导出文件
  • 具备 SUMIFS、INDEX/MATCH 及数组公式的基本使用经验
  • 与实际损益表科目匹配的费用类别清单(如软件、人力成本、市场推广等),而非税务申报表中的科目分类
  • (可选)数据量超过 2,000 行时,需要 Google Apps Script 访问权限

分步指南

1

创建三标签页架构

将数据流拆分至 3 个标签页:Import(导入)、Expenses(费用)和 Summary(汇总)。这听起来显而易见,但大多数失效的费用追踪表都败在同一个问题上:有人把 CSV 导出数据直接粘贴到了含公式的区域,公式随即被覆盖,数据变成了原始状态,汇总也随之失效。

  • Import**:原始粘贴区——此处永远不放公式。CSV 数据在此落地后保持原样,不做任何处理。
  • Expenses**:规范化层。此标签页中的每一行都通过公式从 Import 中提取数据,完成清洗和分类。
  • Summary**:纯汇总层。通过 SUMIFS 对 Expenses 数据按类别和月份进行分组统计。
  • Rules**:独立标签页,用于将供应商关键词映射至费用类别。此标签页与所有公式逻辑完全分离,便于独立维护。

Pro Tip

通过"数据 → 保护表格和范围"功能锁定 Import 标签页,防止误操作。粘贴区极易因意外输入而损坏,防护措施不可省略。
2

构建动态列定位器

银行导出文件、Stripe 付款记录与财务软件的数据,每个月的列顺序并不一致。某银行可能把日期放在 A 列,而你的财务软件可能将其放在 D 列。当你更改导出设置或新增数据字段时,列的顺序还会进一步变动。

解决方案是通过表头行查找来定位正确的列——依列名而非列位置。在 Expenses 标签页中,定义 3 个辅助单元格,分别存储所需字段的列索引:

=MATCH("Date", Import!$1:$1, 0)
=MATCH("Description", Import!$1:$1, 0)
=MATCH("Amount", Import!$1:$1, 0)

通过名称框将这三个单元格分别命名为 col_date、col_desc、col_amount。随后在 Expenses 行中动态提取数据:

=INDEX(Import!$A:$Z, ROW(A2), col_date)
=INDEX(Import!$A:$Z, ROW(A2), col_desc)
=INDEX(Import!$A:$Z, ROW(A2), col_amount)

此后,即便下个月的导出文件将 Description 从 B 列移至 C 列,也不会出现任何问题。表头行查找会自动定位,所有下游公式随之同步更新。

    Pro Tip

    针对来源不同而列位置不固定的字段,添加第四个 MATCH 公式——"Memo"(备注)、"Reference"(参考编号)、"Counterparty"(交易对手方)等字段在不同银行的导出文件中位置各异。
    3

    规范化供应商名称字符串

    银行导出文件中的原始供应商名称字符串往往问题重重:尾部空格、大小写混用、OCR 字段产生的多余空白,以及编码异常字符。同一供应商的两笔交易,若其中一笔带有多余空格,SEARCH 公式便可能将其视为完全不同的条目。

    在 Expenses 标签页中,添加 Vendor_Clean 列,依次执行三步转换:

    =TRIM(UPPER(SUBSTITUTE(C2, "  ", " ")))
    

    TRIM 去除首尾空白字符,UPPER 统一转为大写以消除大小写差异,SUBSTITUTE 合并有时出现在导出备注字段中的双重空格。所有下游公式均引用这一列——原始导入描述保留在其专属列中,作为审计记录。

    对于日期规范化,添加 Date_Clean 列。从多个来源汇总数据时,MM/DD/YYYY、YYYY-MM-DD 与"2026年4月16日"等混合日期格式十分常见。将其统一转换为标准值:

    =IF(ISNUMBER(D2), D2, DATEVALUE(D2))
    

      Pro Tip

      将 Date_Clean 的单元格格式设置为"日期"而非"常规"。DATEVALUE 返回的是日期序列号,在应用日期格式之前,显示的将是类似"46000多"的数字。
      4

      构建供应商规则表

      Rules 标签页包含一个两列表格:A 列为 Keyword(关键词),B 列为 Category(类别)。当有新供应商出现或需要重新归类时,只需修改此处。

      KeywordCategory
      ZOOMSoftware
      GOOGLE CLOUDSoftware
      AWSSoftware
      NOTIONSoftware
      FACEBOOK ADSMarketing
      LINKEDINMarketing
      STRIPE FEEPayment Processing
      GUSTOPayroll
      UNITED AIRLINESTravel

      以下几条设计原则至关重要。关键词应匹配供应商名称中最具区分性的部分——例如不应使用"GOOGLE"(范围过宽,会将 Google Drive、Google Ads 和 Google Cloud 归为同一类),而应使用"GOOGLE CLOUD"或"GOOGLE ADS"等更精确的字符串。每个类别的规则数量不设上限,以确保准确性为准。若某支付平台对同一供应商生成了 7 种不同的字符串变体,就需要添加 7 条关键词规则。

      在各类别内部,关键词应按特异性排序——更精确的规则优先。分类器返回第一个匹配结果,因此若表中同时存在"FACEBOOK ADS"和"FACEBOOK",前者必须排在前面。

        Pro Tip

        添加第三列 Notes(备注),记录每条规则的设置原因。当你半年后回头查看这张表,疑惑"AMZN MKTPLACE"为何被归入 Software 而非 Miscellaneous 时,这些说明将为你节省大量时间。
        5

        编写 INDEX/MATCH 分类公式

        分类公式会在规则表中扫描所有可能出现在已清洗供应商名称字符串中的关键词。在 Expenses 标签页中,添加 Category 列:

        =IFERROR(INDEX(Rules!$B$2:$B$100, MATCH(TRUE, ISNUMBER(SEARCH(Rules!$A$2:$A$100, E2)), 0)), "Uncategorized")
        

        按 Ctrl+Shift+Enter 将其作为数组公式输入(在较新版本的 Sheets 中,也可将内部表达式包裹在 ARRAYFORMULA 中)。公式返回关键词首次出现在已清洗供应商名称中的类别——支持部分匹配,因此"GOOGLE CLOUD STORAGE"可正确命中"GOOGLE CLOUD"规则,尽管两者并不完全一致。

        "Uncategorized"(未分类)的兜底值比看起来更重要。若对未匹配行返回 "",这些交易将从 SUMIFS 汇总中悄然消失——SUMIFS 会跳过空白单元格。使用显式字符串则意味着未匹配的费用会作为独立类别出现在汇总中,让你清楚地看到哪些条目尚未被映射,从而判断何时需要更新规则表。

          Pro Tip

          公式运行后,筛选 Category 列中的"Uncategorized"条目并按金额降序排列。金额最大的未匹配交易,就是最优先需要添加规则的供应商。
          6

          构建 SUMIFS 汇总网格

          Summary 标签页按类别和月份汇总金额合计。在 A 列纵向排列类别,在第 1 行横向排列月份(格式为每月第一天,例如 2026-01-01)。对网格中的每个单元格使用以下公式:

          =SUMIFS(Expenses!$F:$F, Expenses!$G:$G, $A2, Expenses!$H:$H, ">="&B$1, Expenses!$H:$H, "<"&EDATE(B$1, 1))
          

          其中 F 列为 Amount(金额),G 列为 Category(类别),H 列为 Date_Clean(规范化日期)。EDATE(B$1, 1) 将日期范围限定在自然月内,无需手动硬编码月末日期。

          在底部添加 Total(合计)行(各月列的 SUM),以及 Budget(预算)行(手动输入各类别的计划支出)。差异值即为实际值减去预算值:

          =B3 - B2
          

          对差异单元格应用条件格式:超出预算显示为红色,低于预算显示为绿色。这是一个有用的检查层,但并非最终输出——它是数据输入层。

            Pro Tip

            谨慎使用整列引用。Expenses!$F:$F 在实践中可以工作,但对仅有 200 行实际数据的表格扫描 1,000,000 行会浪费大量重算时间。确定数据量后,将范围限定为 Expenses!$F$2:$F$5000。
            7

            将汇总数据接入损益表结构

            这一步是大多数费用追踪指南忽略的关键环节。第 6 步构建的汇总网格对个人记账有所帮助,但它并非财务模型——只是一张平铺的数据表。若要将其用作资金消耗模型或面向投资方的损益表输入,你需要一个独立的 P&L 标签页,将 Summary 视为实际数据层加以调用。

            在 P&L 标签页的 A 列设置完整的科目结构,与正式利润表中的成本呈现方式保持一致:

            科目1月2月3月
            营业收入
            营业成本
            — 主机及基础设施
            — 支付处理费
            毛利润
            运营费用
            — 软件及订阅
            — 人力成本
            — 市场推广
            — 差旅费
            运营费用合计
            EBITDA

            P&L 中每个实际值单元格均从 Summary 通过直接引用或 SUMIFS 提取,将 P&L 科目映射至 Summary 类别:

            =SUMIFS(Summary!$B:$B, Summary!$A:$A, "Software")
            

            对于需要合并至一个 P&L 科目的类别(例如"主机及基础设施"可能汇总 AWS、Google Cloud 和 Cloudflare 的 Summary 行),使用多个 SUMIFS:

            =SUMIFS(Summary!B:B,Summary!A:A,"Software") + SUMIFS(Summary!B:B,Summary!A:A,"Hosting")
            

            这就是整体架构:分类器填充 Summary,Summary 作为实际数据输入层,P&L 标签页则是模型本身。营收数据直接录入 P&L 或通过独立数据源接入。至此,月度实际数据将自然流入资金消耗率和现金跑道计算——相关结构请参阅如何计算初创公司的现金跑道与初创公司资金消耗仪表盘。

              Pro Tip

              在 P&L 中添加 Budget(预算)行,引用独立的假设标签页,而非直接硬编码数值。P&L 中的预算差异公式为 =Actuals - Budget,任何预算调整只需修改假设标签页中的一个单元格即可。
              8

              使用 Apps Script 实现自动化数据处理

              第 5 步中的数组公式在每次重算时执行 N × M 次比较——Expenses 中的每一行都要与规则表中的每一行进行匹配。500 行费用数据对应 50 条规则时,每次重算需要进行 25,000 次查找。当数据量达到 10,000 行时,查找次数将增至 500,000 次,届时每次修改单元格都会导致表格卡顿数秒。

              数据量超过 2,000 行时,建议将分类逻辑迁移至 Apps Script。逻辑与公式完全一致,但仅执行一次并以批量写入的方式输出结果:

              function categorizeExpenses() {
                const ss = SpreadsheetApp.getActiveSpreadsheet();
                const expenses = ss.getSheetByName('Expenses');
                const rules = ss.getSheetByName('Rules');
              
                const expData = expenses.getDataRange().getValues();
                const ruleData = rules.getRange(2, 1, rules.getLastRow() - 1, 2).getValues();
              
                const categories = [];
              
                for (let i = 1; i < expData.length; i++) {
                  const vendor = String(expData[i][4]).toUpperCase(); // Vendor_Clean 列
                  let matched = 'Uncategorized';
              
                  for (const [keyword, category] of ruleData) {
                    if (vendor.includes(String(keyword).toUpperCase())) {
                      matched = category;
                      break;
                    }
                  }
                  categories.push([matched]);
                }
              
                // 批量写入——切勿在循环内执行写操作
                expenses.getRange(2, 6, categories.length, 1).setValues(categories);
              }
              

              关键原则:永远不要在循环内调用 setValue()。每次单独写入都会触发一次与 Sheets API 的往返请求。对于 10,000 行数据,在循环内写入可能需要 15 分钟以上。而最后的批量 setValues() 可在 10 秒内完成相同的操作。

              通过"扩展程序 → Apps Script → 触发器"将其绑定到触发器,设置为在编辑时自动运行,或在 Import 标签页发生变化时触发。截至 2026 年 4 月,Apps Script 执行时长上限为个人账户 6 分钟、Workspace 账户 30 分钟——批量写入模式可确保在 Google Sheets 最大 1,000 万单元格的体量下,仍轻松控制在两类账户的时限范围内。

                Pro Tip

                若需链式执行多次批量写入,请在 setValues() 之后添加 SpreadsheetApp.flush()。若缺少此调用,Sheets 可能会缓冲变更并以错误的顺序执行。

                总结

                你所构建的是一条数据处理管道,而非简单的追踪工具。Import 是可兼容任意导出格式的粘贴区,Expenses 负责规范化与分类,Summary 负责汇总,P&L 负责消费这些数据。每一层各司其职,并将结果传递给下一层——这意味着当银行更改 CSV 格式或你新增供应商类别时,只需修改一处,其余部分自动更新。

                大多数费用管理方案的症结在于将汇总网格视为最终输出。它并非如此——它是实际数据的供给层。一旦将其接入含营收数据和完整科目结构的 P&L 标签页,它就从记账工具升级为真正的财务模型。

                查看 ModelMonkey 方案——支持 Google Sheets 和 Excel,可通过一次对话完成分类设置、公式搭建和损益表结构配置。

                常见问题

                INDEX/MATCH 分类公式为何需要按 Ctrl+Shift+Enter 输入?

                该公式使用 ISNUMBER(SEARCH(...)) 同时检查所有关键词行,需要数组运算支持。若未按 Ctrl+Shift+Enter(或使用显式的 ARRAYFORMULA 包裹),Sheets 会将其作为标量运算处理,仅对规则表的第一行求值。实际表现为:若分类器对每个供应商都返回同一个类别,说明你忘记以数组公式形式输入了。

                公式应向下延伸多少行合适?

                若为个人独立运营,将 Expenses 公式延伸至 5,000 行即可;若汇总多个来源或处理全年数据,则延伸至 20,000 行。SUMIFS 和 P&L 公式本身使用有界范围,因此 Expenses 底部的空行不会影响汇总结果。避免延伸至 1,000,000 行——大规模公式集中使用整列引用会导致每次编辑时重算缓慢。

                同一供应商出现 5 种不同字符串变体时如何处理?

                在规则表中为 5 种变体各添加一行,均映射至同一 Category。若各变体共享一个有区分性的子字符串(例如均包含"STRIPE"),则一条包含该子字符串的规则即可覆盖所有变体。若各变体没有公共字符串,则需为每种变体单独设置规则。这是正常现象——支付处理平台和 POS 系统本就会产生不一致的供应商字符串,健壮的规则表应对此加以处理,而非试图在上游清洗。

                该方案能否处理多币种费用?

                公式逻辑与币种无关——它仅依据供应商名称字符串进行分类,不受币种影响。若需追踪多币种费用,在 Expenses 中添加 Currency(币种)列和 FX_Rate(汇率)列,将金额换算为报告币种。Summary 中的 SUMIFS 应引用换算后的列(=Amount * FX_Rate),而非原始 Amount。实时汇率可通过 =GOOGLEFINANCE("CURRENCY:EURUSD") 获取,或针对特定报告期直接硬编码。

                如何防止规则表无限膨胀?

                每季度审查一次。筛选 Category 列中的"Uncategorized"条目,为每月支出超过约 350 元且仍未分类的供应商添加规则。删除已不再使用的供应商规则——无效规则会无谓地拖慢数组公式的扫描速度。维护良好的规则表通常包含 30 至 80 条规则;若你的规则表已接近 200 条,可能存在可以合并的重叠或冗余规则。