一键下载银行电子账单,我就是想要!
这个标题未免也有点太浮夸了((
由于我平时一直用 gnucash 记账,所以有很大量的需要从各类网站下载并导入电子账单的需求。但是 各种 SB 银行的 IT 系统 需要手动填写日期范围,并且经常使用默认文件名,并且有些只能按月下载,并且……(真是罄竹难书)所以一怒之下花了两个周末的时间手搓了九个脚本辅助下载这些账单。
Credits to chatGPT, gemini,copilot老师,没有他们的帮助我没有办法完成这项壮举(明星/播音腔)。我们 隆重地 把代码放在了 github 和 greasyfork 上面。
项目结构和技术路线
大体的想法是用 violent monkey 挂 user.js 脚本,然后找下载账单的规律,这样我们就可以实现下载账单自由。
当然事情不可能这么一帆风顺——最要命的问题就是,我们这种小偷小摸无异于逆向 js 工程代码……实际上最后我并没有去死磕混淆过的代码,有很多时候是走了一些取巧的路径。
开发环境这次选用了 powershell + bun/typescript 的方式。改动代码之后可以通过 bun watch + violent monkey 实现自动刷新,还是很爽的。typescript 加上一些自己写的小东西就会舒服很多。
数落银行 IT 系统时间
BOA
印象不太深刻了,由于是第一个配了蛮旧的环境
Wells Fargo
WF 比较直接,只要找一个 request 包就行了(但是好像对 post url 路径识别比较苛刻……)
Chase
chase 用了 RequireJS 来 load 他们家的模块,这就给我们提供了极大的便利(什么 token 帐号?我直接 import!)。
但有个问题是找了很久一直找不到 csrftoken 在哪里算得:
后来从 firefox 的 devtools 里看到 request 的 trace stack,是从一个 hashlib 的dynamicFormSubmit
里填token的,token 又是从一个 session storage 里面拿的。
最后搞清楚了,chase 的做法是悄悄地生成一个 hidden form 然后偷偷 submit,所以在devtool 的 network 里一直看不到 request,有点不光明的赶脚。
调试了一下包的发送方式之后轻松拿下(并不
Amex
运通的问题是很难直接搞到每个账户的 account_key ——所幸点到哪个账户,那个账户的 key 就会出现在地址栏里
后来发现这个是不靠谱的(捂脸)而且他们家的前端完全不用 id ……看来是 react 一类的流派。最后是从 “Statements & Activity” 里想办法关联了一下可以下载的账户。
顺带一题,迷惑 response:
GET
https://global.americanexpress.com/api/servicing/v1/financials/documents?end-date=2025-08-10&start-date=2025-05-22&=&file_format=quicken&limit=3000&status=posted&accountKey=&client_id=AmexAPI
message: Incorrect number of account tokens. Valid count is between 1 to 1
调试了很久没明白为什么不成功,仔细一看,&=&
是什么鬼啊!!!
Wise
token验证机制非常难搞,one-time-token 死活生成不出来,最后是一个必须要进到密码验证步骤之后才能用的残废品
Venmo
用的是 nextjs,还行,特点是 HTML 文件里有个 <script id="__NEXT_DATA__" type="application/json">...
的玩意儿,可以直接拿账户 id。
paypal
give up,sb 合订本 pdf,csv 导出只需等待(卡妈:谁在叫我?
Sofi
用的 react,还比较简单
Healthequity
give up,提交表单的时候有神秘 base64 字符串
Charles Schwarb
give up,业务逻辑太复杂
Fidelity
感觉是一众里最高级的了,因为用了 graphql。但很快我就被打脸了,在代码里发现了诸如
operationName "getTransactions”
operationName "GetState”
的不一致性(这素在……)。而且返回的字段里还有
{
"data": {
"getTransactions": {
"orders": [],
"historys": [
...
historys
素在?
另一个要吐槽的点是 date 字段是整数,但就是偏要加字符串引号才行(是不是 graphQL 的bug?
最后成品没有直接死磕 requests,而是偷了一个加载过程中的 graphQL query,改了一下时间直接用。