首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 WorkBuddy 自动化开具粤通卡 ETC 发票:浏览器接管 + 逐月批量开票实战

用 WorkBuddy 自动化开具粤通卡 ETC 发票:浏览器接管 + 逐月批量开票实战

原创
作者头像
财税亿公子
发布于 2026-09-03 22:40:40
发布于 2026-09-03 22:40:40
2670
举报

用 WorkBuddy 自动化开具粤通卡 ETC 发票:浏览器接管 + 逐月批量开票实战

作者:亿公子(税务合规 / 大额居间费方向自媒体博主) 适用场景:浏览器自动化、RPA 批处理、财务票据管理

一、为什么要把"开 ETC 发票"交给 WorkBuddy

我是做税务自媒体的,公司名下记账卡每个月都有几十笔高速通行费要开票。手动登录票根、逐月点选、勾行程、填邮箱……一个月一次,又碎又烦,还容易漏月。

后来我用 WorkBuddy 的浏览器自动化能力(agent-browser + Playwright)把整条开票链路做成了可复跑的脚本:一键接管已登录的浏览器,逐月扫描"可开票"行程,自动勾选、填邮箱、提交,最后只差"确认发布"式的一个真人点按。

这篇把核心思路和可直接抄的代码拆给你,重点讲三个真实踩坑:Vue 单页应用的路由坑、Element Plus 月份选择器、以及最要命的"月份切换异步竞态"。


二、整体思路

代码语言:markdown
复制
agent-browser 开可见浏览器(用户扫码登录)
        ↓
Playwright 用 CDP 接管同一浏览器会话
        ↓
菜单「我要开票」→ 点卡片 → 进入月份表格
        ↓
逐月设置查询月份 → 读取"可开票"行程 → 勾选
        ↓
底部「确认」→ 核对页填邮箱 → 「确认开票」
        ↓
成功页「继续开票」→ 循环下一个月

核心原则:让 AI 跑机械循环,人只在登录和最终提交时点一下。


三、环境准备

代码语言:bash
复制
# 1. 安装浏览器自动化 CLI(全局)
npm install -g agent-browser

# 2. 下载 Chromium(较大,建议后台跑)
agent-browser install

# 3. 本机准备 Python 环境(Playwright 接管用)
python -m venv venv
venv\Scripts\pip install playwright
playwright install chromium

Windows 关键:起浏览器必须带 --no-sandbox 和 --headed,否则要么连不上,要么弹不出可见窗口让你扫码。agent-browser open "https://mag.96533.com:8243/personService/index.html" --headed --args "--no-sandbox"


四、第一步:用 Playwright 接管已登录的浏览器

agent-browser 本质也是 Chromium,我们可以用 Chrome DevTools Protocol(CDP)让 Playwright 直接连上同一个会话,这样用户在窗口里扫的码、登的录,脚本都能接着用。

先找到浏览器调试端口:

代码语言:bash
复制
# Windows 查监听端口与进程
powershell -Command "Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -gt 9000 } | Select-Object LocalPort, OwningProcess"

连上它(端口号以你实际为准,常见 49xxx):

代码语言:python
复制
import asyncio
from playwright.async_api import async_playwright

CDP = "http://127.0.0.1:49283"   # 换成你查到的端口

async def main():
    async with async_playwright() as p:
        b = await p.chromium.connect_over_cdp(CDP)
        pg = b.contexts[0].pages[0]   # 复用已登录的标签页
        await pg.bring_to_front()
        print("当前页:", pg.url)
        # 后面所有操作都基于 pg
        await b.close()

asyncio.run(main())

五、第二步:绕过 Vue 路由坑,进对入口

粤通卡的"电子发票"菜单其实内嵌了票根子系统,而且 CheckBillingInfo 这个原始路由在我们自动化环境下会报 "undefined" is not valid JSON,直接挂掉。

正确入口是:菜单「我要开票」→ 点卡片 .etcItem → InvoiceDetail 页(带月份选择器)。如果路由 push 不灵,就用菜单点击:

代码语言:python
复制
# 进「我要开票」并点卡片
await pg.evaluate("""() => {
    const el=[...document.querySelectorAll('*')].find(e=>(e.innerText||'').trim()==='我要开票');
    if(el) el.click();
}""")
await pg.wait_for_timeout(1500)
await pg.evaluate("""() => {
    const card=[...document.querySelectorAll('.etcItem')].find(e=>e.innerText.includes('粤B')); // 用你的车牌关键字
    if(card) card.click();
}""")
await pg.wait_for_timeout(2000)

六、第三步:月份选择器 + 异步竞态(全文最关键的坑)

Element Plus 的月份选择器面板里,月份是中文数字(一月…十二月),年份用箭头按钮切。更重要:切月后表格是异步刷新的,Playwright 有时读到的是上一月的旧数据——直接开票就会开错月。

解法:每次切月后,校验每一行的"交易日期"是否真的落在所选年月,不符就等一会儿重试。

代码语言:python
复制
import asyncio

MONTH_CN = ["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]

async def set_month(pg, year, month):
    await pg.click('.el-date-editor--month', timeout=5000)
    await pg.wait_for_timeout(400)
    # 切到目标年份
    for _ in range(6):
        yr = await read_year_label(pg)   # 读 .el-date-picker__header-label 里的 4 位数字
        if yr == year: break
        arrow = '前一年' if yr > year else '后一年'
        await pg.click(f'button[aria-label="{arrow}"]')
        await pg.wait_for_timeout(300)
    await pg.locator('.el-month-table .cell', has_text=MONTH_CN[month-1]).first.click()
    await pg.wait_for_timeout(1200)

async def read_rows_validated(pg, year, month):
    rows = await pg.evaluate("""() => [...document.querySelectorAll('.el-table__body-wrapper tbody tr')]
        .map(tr => [...tr.querySelectorAll('td')].map(td => td.innerText.replace(/\\s+/g,' ').trim()))""")
    valid = [r for r in rows if len(r) >= 6 and r[3].startswith(f'{year}-{month:02d}')]
    return valid   # 只返回日期真正匹配的行的,避免开错月

经验值:切月后等 1.2 秒 + 日期校验,基本能 100% 避开竞态。


七、第四步:批量开票主循环

勾选行 → 底部「确认」→ 核对页填邮箱 → 「确认开票」→ 成功页「继续开票」。

代码语言:python
复制
async def issue_month(pg, year, month):
    await set_month(pg, year, month)
    rows = await read_rows_validated(pg, year, month)
    if not rows:
        return f"{year}-{month:02d}: 无未开票行程"
    # 逐行勾选(点 label.el-checkbox 才触发 Vue)
    for _ in range(3):
        boxes = await pg.locator('.el-table__body-wrapper tbody tr label.el-checkbox').all()
        for bx in boxes: await bx.click()
        await pg.wait_for_timeout(400)
        break
    # 底部确认
    await pg.evaluate("""() => {
        const b=[...document.querySelectorAll('div.confirmBtn')].find(e=>e.innerText.includes('确认'));
        if(b) b.click();
    }""")
    await pg.wait_for_timeout(1500)
    # 核对页填邮箱(必填!否则提交无反应)
    await pg.fill('input[placeholder="请输入接收发票的电子邮箱"]', 'you@example.com')
    await pg.evaluate("""() => {
        const b=[...document.querySelectorAll('div.confirmBtn')].find(e=>e.innerText.includes('确认开票'));
        if(b) b.click();
    }""")
    await pg.wait_for_timeout(2000)
    return f"{year}-{month:02d}: 已提交 {len(rows)} 笔"

主循环就是遍历你想开的月份列表,逐月调用 issue_month,把结果写进 JSON 留档。


八、踩坑速查表

坑

现象

解决

浏览器起不来

连接失败 os error

open 加 --args "--no-sandbox"

登录态丢失

票根子系统刷新后"请重新登录"

票根是独立子系统,需重新短信验证码登录

路由报错

CheckBillingInfo 白屏/JSON 错

改用「我要开票」→ .etcItem 入口

开错月份

表格显示上一月数据

切月后按行日期校验(第六节)

提交无反应

填完点「确认开票」没动静

邮箱框是必填,先填再提交

程序化点击无效

eval click 返回成功但不提交

最终"确认"步骤留给真人点


九、小结

ETC 发票这类"每月一次、步骤固定、界面是 Vue 老 SPA"的活儿,特别适合用 WorkBuddy 的浏览器自动化来承包。整套打法可以复用到任何类似的票据/报表后台:

  1. agent-browser 开可见窗口承接登录;
  2. Playwright + CDP 接管同一会话,零重复登录;
  3. 月份/日期选择器重点防异步竞态;
  4. 确认类按钮多是对 div 的自定义点击,认准文案去 querySelectorAll 找。

把循环脚本化后,我每个月只要点一次"确认开票",剩下的 WorkBuddy 全包了。欢迎在评论区聊聊你用 WorkBuddy 自动化的财务场景 👍


声明:文中卡号、手机号、邮箱均为占位示例,实操请替换为你的真实信息;敏感操作请确保本人授权。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 用 WorkBuddy 自动化开具粤通卡 ETC 发票:浏览器接管 + 逐月批量开票实战
    • 一、为什么要把"开 ETC 发票"交给 WorkBuddy
    • 二、整体思路
    • 三、环境准备
    • 四、第一步:用 Playwright 接管已登录的浏览器
    • 五、第二步:绕过 Vue 路由坑,进对入口
    • 六、第三步:月份选择器 + 异步竞态(全文最关键的坑)
    • 七、第四步:批量开票主循环
    • 八、踩坑速查表
    • 九、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档