개요
master의 재무 대시보드. 계좌 잔액, 순자산 트렌드, 수입/지출 요약을 볼 수 있습니다.
자산
238472.61CNY
부채
0
수입
-336422.75CNY
지출
132231.76CNY
자산
238472.61 CNY
부채
0
수입
-336422.75 CNY
지출
132231.76 CNY
자산 분포
master 자산 구성의 시각적 표현
부채 분포
master 부채 구성의 시각적 표현
Chart is empty.
현금 흐름
수입원에서 지출 및 투자로의 자금 흐름
수입 대 지출
선택한 기간의 각 구간별 총 수입과 지출을 비교하는 막대 차트.
README.md
Beancount
目的
- To accounting./用于记账。
启动
bashfava main.bean
账单导出
工商银行
- Edge 浏览器登录工商银行个人网上银行
- 找到借记卡/信用卡,选择时间段,导出
微信
- 我的,服务,零钱,交易,下载流水,仅用于个人对账
- 选择发送方式,时间段,导出
支付宝
所有交易
- 手机支付宝,我的,账单,开具交易流水证明,用于个人对账,申请
- 注意解压密码
余额交易
- 电脑 PC 网页版,登录,我是个人用户,进入我的支付宝,交易记录,余额收支明细
- 选择时间段,下载查询结果
美团
- 手机美团,我的,钱包,查看全部,账单,下载账单,去下载
- 选择时间段,导出,注意解压密码
京东
- 手机京东,我的,点击钱包,账单模块的查明细,右上角更多,账单导出(仅限个人对账)
建设银行
- 注意,建设银行手机和自助柜员机导出来的流水,double entry generator 的 ccb provider 识别不了,必须是 PC 网上银行导出来的流水才可以
- 登录建设银行网上银行,点击对应卡,明细,选择时间段,下载导出即可
达州银行
- Edge 登录达州银行网上银行,点击对应卡,明细,选择时间段,下载导出即可
银河证券
- 电脑登录海王星金融终端
- 点击导航栏的交易,普通查询,对账单,选择对应时间段,最右边有一个导出图标
余额查询
- 余额宝的每日余额明细只能在余额宝里的资金明细里面查看,无法下载
- 微信的每日余额明细只能在微信零钱的交易里查看,无法下载
生成 Beancount 记录
- 使用命令(double-entry-generator)
bash.\double-entry-generator.exe translate -p <provider> --config <config_file_path> <transactions_file_path>
- 或者自己写的 beanancount-importer-rust 项目
bashcargo run -- --provider yinhe --source ..\..\Beancount\data\galaxy\20260301-20260331.xls.xlsx --config .\tmp\config\galaxy.yml --output .\tmp\tmp.bean --log-level info
- 粘贴到对应文件
- 检查
其他
操作类
- 使用 <C-K> + <C-0> 折叠全部代码/交易记录(vs code)
- 使用 <C-K> + <C-J> 展开全部代码/交易记录(vs code)
预算工具
bashcargo run -- --month 2026-04 --budgets ..\..\Beancount\budget\budgets.yml --mappings ..\..\Beancount\budget\mappings.yml --ledger-dir ..\..\Beancount\transactions\ --scope cumulative --bucket 生活费 --bucket-view detail
注意事项
通用
- 所有自己加的注释,统一用
comment: "\<content\>"格式,而所有对账单里面的备注,都统一用note: "\<content\>"格式
余额宝
- 余额宝是当天发放昨天的收益,也就是说,每个月 1 号是发放的上个月最后一天的收益,余额宝明细显示是“收益,收益的日期”
- 因此按月导入记录,在 balance 余额的时候,balance 次月 1 号,应该看明细的最后一个/第一个(视情况决定),而该项显示的收益日期还会再减一天
- 举个例子,balance 1.1,应该看余额宝 12 月明细的第一个,而第一个显示的收益日期应该是 12.30,但是该项计算的时候,日期应该是 12.31
建设银行
- 使用 double entry generator 生成的建设银行记录往往有 ; 注释
- 这部分内容往往有实际意义,是在订单内的,推荐直接做成元数据
- 格式: comment: "<content>"
- 直接替换方法:在代码编辑器(如 vscode)里替换
"\s*;\s*(.*)为"\n\tcomment: "$1"
微信
- 配置文件收入部分反了,需要改
- 更改微信广告收入后续的 icbc yml 配置文件
银河证券
- 导出之后打开,然后另存为,不然 beancount-importer-rust 会报错
- 银河证券一般不需要对账,直接打开银河证券的“人民币资产”账户,beancount-importer-rust 会生成 balance 余额元数据,直接看元数据跟 fava 自己算的余额一致不一致就行了
- 最后几天可能余额不一致(多半不会一致,所以挑距离月底 3 个工作日前的记录看就行了,因为有些交易涉及到跨月清算的问题,可能清算回来就是下个月了,所以月底最后那几个工作日就不准
拼多多
- 拼多多目前无法导出账单,目前的解决方案是通过一个免费的 Greasy Fork 脚本导出为 Excel
- 登录手机版拼多多在页面上使用脚本即可
- 注意,无法爬取多多买菜的记录
- 建议根据实付金额定位具体的交易订单
- 注意,需要修改该脚本:
- 修改脚本里的 processData 函数,替换为:
javascriptfunction processData(uid, list) { DB.updateAccount(uid, document.cookie); const cleanList = list.map(o => { const sn = utils.getSafe(o, ['order_sn', 'orderSn']); if (!sn) return null; // 时间处理 let rawTs = utils.getSafe(o, ['order_time', 'orderTime', 'pay_time', 'created_time']); let ts = rawTs ? rawTs : Date.now() / 1000; if (ts < 10000000000) ts *= 1000; const goods = (o.order_goods || o.orderGoods || o.goods_list || [])[0] || {}; // ======== 核心修复:金额自适应解析(元/分) ======== // 拼多多首屏金额是字符串如 "16.8"(元),接口金额是数字如 1680(分) const parseYuan = (val) => { if (val === undefined || val === null) return 0; if (typeof val === 'string') return parseFloat(val) || 0; // 字符串代表元,直接转浮点数 if (typeof val === 'number') return val / 100; // 数字代表分,除以100 return 0; }; // 优先拿 display_amount(实付分),兜底拿 order_amount let amountVal = utils.getSafe(o, ['display_amount', 'displayAmount', 'pay_amount', 'amount', 'order_amount', 'orderAmount']); let priceVal = utils.getSafe(goods, ['goods_price', 'goodsPrice']); let finalPaid = parseYuan(amountVal); let finalPrice = parseYuan(priceVal); let gNum = goods.goods_number !== undefined ? goods.goods_number : (goods.goodsNumber || 1); if (!finalPaid && finalPaid !== 0) finalPaid = finalPrice * gNum; // ======== 修复结束 ======== return { orderSn: sn, time: ts, goodsName: utils.getSafe(goods, ['goods_name', 'goodsName'], '未知商品'), goodsImg: utils.getSafe(goods, ['thumb_url', 'thumbUrl', 'hd_thumb_url']), spec: utils.getSafe(goods, ['goods_spec', 'spec']), price: finalPrice.toFixed(2), count: gNum, realPaid: finalPaid.toFixed(2), status: utils.getSafe(o, ['status_prompt', 'order_status_prompt', 'orderStatusPrompt', 'display_status'], '未知'), link: `https://mobile.pinduoduo.com/goods.html?goods_id=${goods.goods_id || goods.goodsId || ''}`, mall: utils.getSafe(o.mall||{}, ['mall_name', 'mallName'], '店铺') }; }).filter(Boolean); const res = DB.upsertOrders(uid, cleanList); if (res.added > 0) { scrollState.lastDataTime = Date.now(); } if (scrollState.active) { updateStatus(`已存 ${res.total} 单 (+${res.added})`); utils.setTitle(`[🚀 抓取中 ${res.total}条] 拼多多`); checkAutoStop(cleanList); } else { updateStatus(`👀 监听中... (新+${res.added})`); } }
- 修改脚本里的 init 函数,只是部分替换:
javascriptfunction init() { if (!location.href.includes('orders.html')) return; // ======== 核心修复:暴力提取首屏数据 ======== try { let raw = null; // 1. 尝试直接从 unsafeWindow 获取 if (typeof unsafeWindow !== 'undefined' && unsafeWindow.rawData) { raw = unsafeWindow.rawData; } else if (window.rawData) { raw = window.rawData; } else { // 2. 如果沙盒隔离严格,直接通过正则从 HTML 源码中硬抠数据 const scripts = document.querySelectorAll('script'); for (let script of scripts) { if (script.innerHTML.includes('window.rawData=')) { const match = script.innerHTML.match(/window\.rawData\s*=\s*(\{.*?\});\s*(window\.|<\/script>)/); if (match && match[1]) { raw = JSON.parse(match[1]); break; } } } } // 3. 找到数据后,塞给 processData 处理 if (raw && raw.ordersStore && raw.ordersStore.orders) { const uid = utils.getUid(); if (uid) { processData(uid, raw.ordersStore.orders); console.log("【PDD脚本】成功拦截首屏自带的最新订单:", raw.ordersStore.orders.length, "条"); } } } catch(e) { console.error("【PDD脚本】首屏数据提取失败", e); } // ======== 修复结束 ======== if (document.getElementById('pdd-bar')) return; const today = new Date(); const lastYear = new Date(); lastYear.setFullYear(today.getFullYear() - 1); // ... 后面的生成 pdd-bar 和 dash 的代码保持不变 ...
- 具体原因参见 Gemini:
plaintext通过分析你提供的脚本代码和抓包数据,我已经找到了问题的原因。 ### 🔍 问题原因:首屏数据未通过 XHR 请求加载 你的脚本核心逻辑是**通过重写 `XMLHttpRequest (XHR)` 来拦截拼多多发出的网络请求**,从而获取订单数据。 但是,从抓包数据可以看出,拼多多订单列表采用了 **SSR(服务端渲染)** 技术。这意味着**最新的一批订单(首屏数据)是直接嵌在 HTML 源码中的**,并没有通过 XHR 发送额外的网络请求。 1. **最新订单在 HTML 里**:在抓包的第一条 `orders.html` 的响应中,包含了一段 `window.rawData = {...}` 的代码,里面赫然写着 4 月、3 月底的最新订单。 2. **XHR 请求从 3.10 开始**:当你的脚本开始自动滚动页面触发加载更多时,页面发出的第一个 XHR 请求的 `offset` 是 `MO-01-260310-115657961090700`。也就是说,XHR 请求直接从 3 月 10 日的订单开始往回拉取了。 **总结**:因为首屏最新订单没有触发 XHR 网络请求,你的脚本监听不到,所以完美漏掉了它们。这也就是为什么你只能抓到首屏之后(3.10 及以前)的订单。 ### 🛠️ 解决方案:主动提取首屏数据 要解决这个问题,只需要在脚本初始化时,主动去读取页面中内置的 `window.rawData`,并把里面的订单塞进你的数据库里即可。 你可以在脚本的 **`6. UI 初始化`** 部分(大约在代码的第 270 行左右的 `init()` 函数开头),加入提取首屏数据的代码。 --- 你说得对,仔细对比了抓包数据和你的脚本源码后,我发现之前的代码确实存在两个严重的问题: 1. **油猴沙盒限制**:在部分油猴环境中,脚本直接访问 `window.rawData` 会获取到 `undefined`,必须使用 `unsafeWindow.rawData` 或者直接从页面 DOM 中通过正则解析出数据。 2. **致命的字段名差异(大小写坑)**:拼多多的接口非常坑,**首屏自带的数据(SSR数据)使用的是驼峰命名法**(如 `orderTime`、`orderGoods`),而**下拉加载的 XHR 网络请求使用的是下划线命名法**(如 `order_time`、`order_goods`)。你的脚本 `processData` 方法只兼容了下划线命名,导致即使拿到了首屏数据也解析不出商品和时间,直接被过滤掉了! 为了彻底修复这个问题,需要同时修改**数据解析方法 (`processData`)** 和 **首屏数据提取逻辑**。 --- 这个问题出在拼多多**首屏数据和接口数据的金额格式不一致**。 通过仔细分析抓包数据,发现了一个大坑: 1. **接口下拉加载的数据(XHR)**:金额是**数字类型**,且单位是**分**(比如 `order_amount: 1680`)。你的脚本直接除以100,得到 16.8,这是对的。 2. **首屏自带的数据(SSR)**:金额是**字符串类型**,且单位已经是**元**(比如 `orderAmount: "16.8"`)。脚本再次除以100,结果就变成了 `0.168`,导致金额错误! ### 🛠️ 解决方案 我们需要写一个小函数,自动判断金额是“元”还是“分”(通过判断它是字符串还是数字)。
