公司账上每个周都有一堆订单要核。ERP 里存着订单和结算价,支付宝导出来的日记账里记着实际收付的流水,两边得靠金额一笔一笔对上,把订单号标到日记账上,再回 ERP 里做收付款。
这活不难,烦在来回切换并仔细核对,我就想是不是可以交给WB去做?我不懂啥很高深的AI方面的技术知识,就是单纯的想让它帮我解决问题,开始注册了wb一直没用,完全没方向,好久后才又开始登录研究,这时发现官方奖励的大把积分都已经过期了。然后自己开始摸索,使用的过程中遇到问题就问AI,开始一天我试着只让它标注订单编号掉日记账,它顺利做完了,而且非常满意,今天我就想尝试让它做erp系统的订单和明细对应并把订单收付款操作也做掉。然后就有了下面WB的这些操作。
这篇记一下过程,重点是踩到的几个坑——都不是什么高深技术,但每一个都让我卡了一阵。
文中的系统地址、接口路径、字段名、Cookie 名都做了脱敏,业务数据(订单号、金额)也换成了等同样式的示例值。匹配逻辑和代码结构是原样保留的。
付-10021 / 退-10036 这样的格式,填进日记账「支付宝流水」表的 G 列第一个意外来得很快。ERP 用 curl 请求一切正常,换成浏览器打开就被拦。
具体机制我没深挖,处理办法是起一个本地反向代理,把请求原样转给源站,浏览器访问 127.0.0.1:8899:
import http.server, urllib.request, urllib.error
UPSTREAM = "http://erp.example.com"
PORT = 8899
class Proxy(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.proxy("GET")
def do_POST(self):
self.proxy("POST")
def proxy(self, method):
url = UPSTREAM + self.path
body = None
if method == "POST":
length = int(self.headers.get("Content-Length") or 0)
body = self.rfile.read(length) if length else b""
req = urllib.request.Request(url, data=body, method=method)
for h in ("Cookie", "Content-Type", "Referer", "Accept", "User-Agent"):
v = self.headers.get(h)
if v:
req.add_header(h, v)
req.add_header("Referer", UPSTREAM + "/Main.aspx")
try:
resp = urllib.request.urlopen(req, timeout=30)
data, code, headers = resp.read(), resp.getcode(), resp.headers
except urllib.error.HTTPError as e:
data, code, headers = e.read(), e.code, e.headers
except Exception as e:
self.send_response(502)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(str(e).encode("utf-8"))
return
self.send_response(code)
self.send_header("Content-Type", headers.get("Content-Type", "text/html; charset=utf-8"))
for h in ("Set-Cookie", "Location"):
vals = headers.get_all(h) if hasattr(headers, "get_all") else None
if vals:
for v in vals:
self.send_header(h, v)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
if __name__ == "__main__":
http.server.ThreadingHTTPServer(("127.0.0.1", PORT), Proxy).serve_forever()Set-Cookie 和 Location 必须原样透传,否则登录态建不起来。
登录页有验证码。这个我没绕,开一个可见窗口让人输完,再从 document.cookie 里把会话 Cookie 取出来。后面所有请求带上这个 Cookie 就行,不用再碰浏览器。
还有个小插曲:代理进程僵死的时候,taskkill //PID 在 Git Bash 下不生效,得用 PowerShell 的 Stop-Process -Id <PID> -Force。
订单列表页没有结算价,得逐单进 /order/edit.aspx?id=<订单号>。
我一开始按常规思路去读表格单元格文本,结果全是空的。翻了 HTML 才发现,那个「旅客信息」表里的值都渲染在 <input value="..."> 上,单元格本身没文本。
更麻烦的是页面上还有另一个「结算信息」合计表,表头也带「结算价」三个字,很容易取错。区分方法是看表头里有没有同时出现「旅客姓名 / 结算价 / 扣率」,而且这些表头单元格带着前缀文本(实际拿到的是类似 旅客信息\n\n\t\t旅客姓名 这种),不能按精确相等去匹配,得把整行拼起来用 in 判断:
for row in ROW_RE.findall(tb):
cells = [re.sub(r"\s+", "", TAG_RE.sub("", c)) for c in CELL_RE.findall(row)]
joined = "".join(cells)
if "旅客姓名" in joined and "结算价" in joined and "扣率" in joined:
hdr_ok = True
break锁定表之后,每个旅客行的 input 索引是固定的:
索引 | 字段 | 索引 | 字段 |
|---|---|---|---|
0 | 旅客姓名 | 6 | 票号 |
2 | 销售价 | 7 | 结算价 |
4 | 燃油 | 8 | 结算税 |
同一订单里同名同价的旅客会重复出现,我用 (姓名, 结算价) 做过去重。
另外两个实测经验:源站会限流,批量抓取得加 0.35–0.4 秒延时,超时之后等一两分钟会自己恢复;火车票订单不用去火车票模块找,订单模块里就能搜到,供应商那一栏显示「线上订单」。
这个是三个坑里最阴的。日记账是个 2MB 多的 xlsx,我按习惯用 openpyxl 打开、写 G 列、保存。G 列写对了,但 13 张内嵌 PNG 全没了。
原因是 openpyxl 不保留它不认识的那部分 XML。带图片的 xlsx,要么别用 openpyxl 写,要么上 Excel COM:
import win32com.client
excel = win32com.client.DispatchEx("Excel.Application")
excel.Visible = False
excel.DisplayAlerts = False
wb = excel.Workbooks.Open(PATH)
ws = wb.Worksheets("支付宝流水")
ws.Cells(row, 7).Value = "付-10021"
wb.Save()
wb.Close()
excel.Quit()写完要验一下图片还在不在,别等到同事打开才发现:
import zipfile
z = zipfile.ZipFile(PATH)
len([x for x in z.namelist() if x.startswith("xl/media/")]) # 应该还是 13COM 写入之前先备份。这个我做了两次备份,第二次是因为要做标红操作,又存了一份。
把订单的结算价抓回来以后,剩下的工作是把它们和日记账里的流水行对上。我用了三条证据,可靠性从高到低:
1. 流水号直连。 日记账第 11 列是流水号。退款行的流水号如果和某笔付款行一致,那就直接锁定原订单,不用看金额。比如一笔 41.00 的退款,流水号 ...88421306 和付款行「付-10036」那笔 45.00 完全一样。
2. 结算价 + 结算税 = 付款金额。 主力规则。我先用三个订单验过这条等式才敢批量跑,实跑的例子像这样:
订单结算价 1480.00 + 结算税 0.60 = 1480.60,日记账付款金额 1480.60 ✓
订单结算价 742.00 + 结算税 0.30 = 742.30,日记账付款金额 742.30 ✓3. 多笔拆分。 N 笔退款加起来等于一笔付款,而且这个订单正好 N 个乘客。实际碰到的是 318.00 × 3 = 954.00,对应一个 3 人订单。
对不上的时候不硬凑。我的处理是:摘要为空 + 整数大额,按账期付款标 付-账期付款;下结论之前先在订单号区间里把结算价搜一遍确认真的没有。这次有 2 笔是推断出来的——一笔退款流水号为空,只能按同批退款加同乘客去猜;另一笔摘要为空的整额支出,我把订单号区间 161 个订单的结算价全搜了一遍也没对上,才按账期付款处理。这两笔我单独标了「推测,请核对」,后来和下面那 4 笔退款一起标了红。
顺带一提标注格式本身也有讲究,是照着历史写法来的:付款 付-<订单号>,退款 退-<订单号>,内部划转(账户间转账、提现这类)不标。
标注完日记账,还得回 ERP 里做收付款。我原以为要点一堆右键菜单,翻了 /Finance/payment.js 才发现,「收款操作」「付款操作」最终都走 POST /Ajax/payment_submit.ashx。页面上的 confirm() 弹窗只是前端提示,后端并不校验,直接 POST 就落账了。
既然是纯 HTTP,就不用开浏览器,带 Cookie 走代理即可。
订单参数从 /Json_db/cashier_grid.aspx 拿,加 &rows=300 一次取全量。每行有 main_id(主单)、sub_id(明细)、customer_id(客户)、supplier_id(供应商)、payable(应付)、paid(已付)。
这里有个细节:每个订单在列表里有两行,「已出票」是正数,「退票」是负数。付款要用已出票那一行的 sub_id,用错了金额对不上。
付款(stype=1)的提交字段:
def post_pay(order, sid, supplier, amount, sdate):
data = {
"stype": "1",
"mid": supplier, # 付款填供应商 id
"selnum": "1",
"idlist": str(order), # 主单 id
"sidlist": str(sid), # 已出票行的明细 id
"kf_ssk": f"{amount:.2f}", # 实付
"zk": "0",
"ysk": f"{amount:.2f}", # 应付 - 已付 - 折扣
"sfkcj": "0.00",
"yc": "0",
"pay": "支付宝",
"bank": "支付宝",
"sdate": sdate, # 2026/9/2
"bz": f"付-{order}",
}
body = urllib.parse.urlencode(data).encode("utf-8")
req = urllib.request.Request(SUBMIT, data=body, headers={
"Cookie": COOKIE,
"User-Agent": UA,
"Content-Type": "application/x-www-form-urlencoded",
"Referer": PROXY + "/Finance/cashier.aspx",
})
with urllib.request.urlopen(req, timeout=30) as r:
return r.read().decode("utf-8", "ignore").strip()收款(stype=0)字段基本一样,区别是 mid 填客户的 customer_id,金额取应收那一侧。
权限这块,页面隐藏域 pay_flag / recv_flag 得是 1,为 0 的话前端直接 alert 没权限。支付方式这两个下拉框的值是从 /Finance/cashier.aspx 的 #pay / #bank 里取的,「支付宝流水」这张表两个下拉都填「支付宝」。
每笔提交完我都会回查一次 cashier_grid.aspx,确认那一行的 paid(已付)变成金额了才算成功——接口返回 1 不代表一定对,回查才是真的。这次 9 笔付款全部回查通过。
这是整件事里我唯一主动停下来的地方。
4 笔退款,金额看着都能对上,但我没敢录。原因有两条:
差额 = 实收 - 应收 的计算,在负数下会算错,还可能把差额自动挂到预存款上去。方向搞反的代价是把账记错,这比慢一点严重得多。所以我把这 4 笔在日记账里标了红,留给同事手动录。标红用的还是 Excel COM,保住那 13 张图。
自动化做到这里刹车,是我觉得最该留下的一个判断:金额对得上不等于方向对。
跑完这一轮,我心里对这类事情的分界线更清楚了。
重复抓取、机械比对、格式固定的写入,这些很适合交出去。规则明确,做错了回查一次就能发现。这次扫了 161 个订单的结算价、写了 16 笔标注、提交了 9 笔付款,都属于这一类。
需要业务判断的地方就不适合了。退款的方向、摘要为空的大额支出、证据不足的推断,这些让规则去猜,猜对了没人夸你,猜错了账就乱了。
还有一条经验是「先跑空再落库」。write_codes.py 我写成默认只读取预览,加 --apply 才真写。每一次真写之前,先把文件复制一份。这 16 笔我写了两次备份,第二次是因为标红又动了一遍文件。
最后是那 13 张 PNG。如果没验图片数量,openpyxl 那一下就把同事的附件全弄没了,而且是在我保存之后、她打开文件之前,谁也不知道。写别人的文件之前,先搞清楚那个文件里到底装了什么。
这套流程我没让它散在一堆临时脚本里,最后收成了一个可复用的 WorkBuddy Skill:ERP 登录、订单抓取、金额匹配、Excel 写入、收付款提交都在里面,踩过的坑也一并写进去了。下次再跑,说一句「把这周的订单对应到日记账」就行。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。