1. 项目全景:香火钱管理为什么需要一套自动化系统
1.1 线下清点的老大难问题
我最早接触这个需求,是帮一位做寺务信息化的朋友做技术评估。他所在的寺庙不算大,但每逢初一、十五和节假日,功德箱和募捐箱里的纸币、硬币能装满好几个收纳箱。寺里安排两位义工轮流清点,一数就是大半天,还不能完全避免数错、漏记、重复登记。更麻烦的是,这些零钱里混着大量硬币,银行清点时还要收一笔不小的手续费,折算下来一年光清点成本就相当可观。
如果你去过寺庙就能注意到,现在的功德箱早就不是只有投币口的老样式了,很多箱子侧面贴着收款码,香客扫码就能随喜捐赠。这就引出第二个问题:线上渠道的钱怎么记账?
1.2 线上渠道带来的对账新麻烦
扫码捐赠的钱会进入寺庙对公账户绑定的微信支付、支付宝商家账户,或者景区代收平台。每家渠道都有独立的管理后台,交易记录格式不一样,提现到账时间也有差异。寺务负责财务的同修,月底要把线下手工登记的流水和线上好几个后台的流水合并起来,再按事先约好的比例分给寺院日常维护、慈善公益、修葺基金等几本账。全靠人工复制粘贴到Excel里,不仅费时间,而且容易出错,一旦对不上账,就得翻出每一笔原始记录重新核对。
我接手时他们最大的痛点是:每个月至少花两个整天做这件事,还有一次因为漏掉一笔延时到账的款项,导致月末分账差了三千多块,查了好久才找到原因。这种场景不需要什么高深的人工智能,缺的就是一个能定时、自动、稳定地把多渠道数据汇总起来的工具。
1.3 Selenium在其中扮演什么角色
Selenium本质上是一个浏览器自动化框架,它模拟真人操作浏览器的行为,点击、输入、滚动、读取页面内容都可以做。在我这个项目里,它的角色是替代人工去登录各个收款渠道的管理后台,把交易明细表格抓取下来,再交给后端的对账分账模块处理。
有人会问,为什么不直接调这些平台的开放接口?答案很现实:大部分中小商户渠道的开放接口申请门槛高、审核周期长,有些甚至不对外开放。而Selenium只需要你有登录账号和密码,就能像人一样操作后台,把数据拿下来,对寺庙这种体量的小单位来说,这是成本最低、见效最快的路径。我会在后面的方案设计章节详细讲为什么这么选,以及它的边界在哪里。
这个项目适合谁参考?一是做寺务、校友会、社区基金会等非营利组织财务工作的同修,二是给这类机构做技术义务支持的志愿者,三是在企业里做RPA(机器人流程自动化)实践,正好需要一个不算复杂的真实业务练手的人。读完之后你能得到一套完整的、可以直接改改就用的网页数据采集与自动分账流程,包括我踩过的坑和总结的避坑方法。
2. 方案设计:从需求拆解到技术选型
2.1 分账规则怎么定
自动分账的前提是把分账规则说清楚。这里我不是讲财务制度,而是讲规则在系统里怎么表达。寺里的善款一般分成几本账:寺务日常开销、慈善公益支出、殿堂修缮基金、机动储备金。每一笔捐赠要按固定比例拆分到这些账目上。
举个具体的例子,假设分账比例是寺务40%、慈善25%、修缮25%、储备10%。那么一笔100元的随喜捐款,系统会自动生成四条分录:寺务40元、慈善25元、修缮25元、储备10元。线上渠道的手续费也要处理,比如微信和支付宝提现时平台会扣千分之几的手续费,我建议把手续费单独建一个“渠道费用”科目,不要直接摊进四个分账项目里,否则每月的分账结果会跟银行流水对不上。
规则在系统里就是一张配置表,用Python的字典或者数据库表都能存,结构大致是:
SPLIT_RULES = { "日常寺务": 0.40, "慈善公益": 0.25, "修缮基金": 0.25, "机动储备": 0.10, }用比例的好处是简单、透明,寺务会议上可以说清楚每一笔钱去了哪里。如果以后想按固定金额、按渠道差异或者按月份动态调整,只需要改这张表,不需要改动抓取代码。
2.2 为什么选择Selenium而不是API
我在方案选型时其实先在纸上列了三条路:官方开放接口、数据库直连、Selenium界面自动化。
官方开放接口这条路最正规,但实现周期不可控。以微信支付商家平台为例,申请接口需要商户号、证书、回调地址等一系列配置,还要经过平台审核,寺里没有专职技术人员,光搞定证书和回调就够呛。支付宝开放平台同理,签约能力要审核、创建应用要等待。更重要的是,有些代收渠道压根没有开放接口,只有网页后台,你除了自动化浏览器没有别的办法。
数据库直连更不要想,人家不会把生产数据库开放给你。剩下唯一普适的方案就是Selenium,它不依赖任何一方的合作,只要你有合法的登录权限,就能把后台数据取出来。当然,它的代价是需要维护浏览器驱动的版本兼容性,页面改版后选择器可能失效。这些坑我会在后文的实操章节逐一交代。
考虑到寺务人员不全是技术出身,我最终还把抓取脚本封装成了一个带命令行交互的小工具,让他们只需要双击运行一个启动文件,按提示按一下回车,脚本就会自动完成登录、抓取、对账、分账、出报表的全流程。
2.3 整体架构与数据流
整套系统的数据流分为五个环节:渠道采集、数据清洗、自动对账、规则分账、结果输出。
渠道采集由Selenium负责,打开三个管理后台,分别抓取微信支付、支付宝、景区代收平台的交易流水。数据清洗用Pandas处理,把不同后台的表格字段统一成同一个格式:交易时间、渠道、流水号、金额、捐赠人备注。自动对账把清洗后的数据跟线下手工登记的Excel比对,找出只有线上记录没有线下记录、或者只有线下记录没有线上记录的差异项。规则分账按照配置好的比例生成各科目的汇总表。结果输出最终生成一个月度善款分账报表,同时把明细导出成Excel和CSV。
我画了一张非常简单的数据流图放在设计文档里,大概长这样:
[微信后台] --Selenium抓取--> [原始流水] [支付宝后台] --Selenium抓取--> [原始流水] [代收平台] --Selenium抓取--> [原始流水] | v [统一格式清洗] --> [与线下Excel对账] --> [差异核对] --> [按规则分账] --> [月度报表]这套架构最大的好处是每个环节解耦,哪怕某一天微信后台改版,Selenium脚本挂了,影响只停留在采集环节,不会污染后续的对账数据。我可以单独修复微信采集脚本,其他渠道照常跑,这对长期维护非常重要。
3. 实操篇:Selenium采集与自动登录的关键步骤
3.1 环境准备与浏览器驱动
环境准备这块,我用的是Python 3.10 + Selenium 4.x,浏览器选了Chrome,配合ChromeDriver使用。为什么不用Firefox或者Edge?纯粹是因为ChromeDriver跟Selenium 4配合最稳定,社区踩坑资料最多,遇到问题搜起来方便。如果你机器上装的是Edge,也可以用EdgeDriver,Selenium 4对Edge支持也很好,驱动可以从微软官网直接下载,这里不展开。
安装部分很简单,两条命令:
pip install selenium pandas openpyxlChromeDriver要特别注意版本号必须跟本机Chrome版本严格对应。我见过太多人在这里翻车,浏览器自动升级到131,驱动还是128的,一运行就报session not created错误。建议的做法是把Chrome的自动更新关掉,或者定期检查驱动版本。写这个项目时我踩过这个坑,后面在避坑章节会详细说。
启动浏览器的基本写法:
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--window-size=1280,800") # 如果部署在服务器上,需要无头模式 options.add_argument("--headless=new") driver = webdriver.Chrome(options=options)有同行朋友的可能是来学习一下,算是半公益性质的技术服务,需要很注重稳定性;所以部署在我自己电脑上的时候,我更多地采用有头模式,这样抓取过程可以直观看到每一步操作,出问题也方便排查。
3.2 登录态管理与验证码处理
登录是所有渠道后台绕不开的一关,最麻烦的又是验证码。我实测下来,微信支付商家后台和支付宝商家后台在常用设备上登录,基本只需要扫码或者短信验证码,很少出图形验证码。但偶尔换机器登录,就会要求输入图形验证码。
图形验证码识别不建议硬刚,我的方案是:登录的时候弹出二维码,让操作人员用手机扫码确认一次,然后把登录成功后的Cookie持久化保存到本地文件里,下次启动直接加载Cookie,跳过扫码。Cookie有效期一般是几天到几周,过期了再扫码一次就好。
保存和加载Cookie我封装成了两个函数,核心逻辑是这样:
import pickle def save_cookies(driver, path): with open(path, "wb") as f: pickle.dump(driver.get_cookies(), f) def load_cookies(driver, path): with open(path, "rb") as f: cookies = pickle.load(f) for cookie in cookies: driver.add_cookie(cookie)注意一个细节:加载Cookie之前,必须先用driver.get()打开一次目标域名,让浏览器建立会话上下文,否则add_cookie会报“cannot add cookie for non current domain”。我一开始没注意,白忙活了半天。
如果加载Cookie之后登录状态还是失效,就回到扫码流程。我的建议是不要过度追求全自动登录,保留一个“半自动扫码”兜底入口,让脚本在检测到页面跳转到登录页时,暂停并发送企业微信通知给操作员,操作员扫码后按回车继续。这样既稳定又省心。
3.3 抓取交易明细的通用写法
进入渠道后台之后,核心操作是找到交易记录列表、按日期范围筛选、把表格内容读出来。我总结了一个通用的写法:等待元素出现、循环翻页、逐行读取表格。
等待元素一定要用显式等待,不要用time.sleep固定等待,因为网络波动会让固定等待要么过长浪费时间、要么过短元素还没加载出来。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(driver, 20) rows = wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, "table tbody tr")) )拿到每一行之后,用find_element读取各个单元格的文本数据。这里要注意表格里的金额、时间可能带有空格、换行、货币符号,清洗时统一用正则或者replace处理掉。
翻页比较常用的是“下一页”按钮,点击之前要判断按钮是否可用,避免最后一页还硬点导致死循环:
next_btn = driver.find_element(By.CSS_SELECTOR, ".next-page") if "disabled" in next_btn.get_attribute("class"): break next_btn.click()微信后台的页面是动态渲染的,翻页后表格内容变化有延迟,所以在每次翻页之后要重新等待目标行的出现。这套写法的核心思想是:不要假设页面是静态的,每步操作后都要重新做等待和校验。
3.4 反爬与稳定性对策
很多人一听Selenium就担心被检测、被限制。真实经验是:对于自有账号后台,平台一般不会刻意封锁,但依然要有基本的道德和技术自觉,比如控制抓取频率。
我给自己定的规矩是:登录操作间隔不少于30秒,翻页和读取表格操作间隔不少于5秒。在代码里就是简简单单的:
import time time.sleep(5)频繁请求会造成一定的带宽压力,也给目标平台服务器增加不必要的负担,我们拿的是自己单位的数据,保持低频是基本原则。
另外一个稳定性对策是增加失败重试机制。我写了一个简单的装饰器,让核心抓取函数在抛出网络异常时最多重试三次:
import functools def retry(max_tries=3, delay=10): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for i in range(max_tries): try: return func(*args, **kwargs) except Exception as e: print(f"第{i+1}次尝试失败: {e}") time.sleep(delay) raise RuntimeError("重试次数已耗尽") return wrapper return decorator把这段代码加到采集函数上,挂掉之后会自动重跑,不需要人盯着。还有一点重要建议:把每一步的关键截图保存到本地,出了问题可以回溯看现场。比如登录成功截一张、每翻一页截一张、报表生成后截一张,这在排查问题时非常有用。
4. 核心实现:自动对账与分账引擎
4.1 对账逻辑与差异处理
对账是整个系统最见功力的部分,因为线上线下数据只要出现差异,背后往往藏着真实业务问题。我的对账逻辑设计成三个维度:金额对账、笔数对账、单号对账。
金额对账最简单,把同一时间段线上流水总额和线下手工账总额相减,差额在0.01元以内视为平账。笔数对账比较总数,比如线上显示本月有153笔扫码捐款,线下记了150笔,差的3笔就要逐条定位。单号对账最精确,以微信或支付宝的商户订单号为唯一键,把线上流水和线下登记逐笔匹配,匹配不上的标记为差异项。
实际操作中,我发现半数以上的差异都来自三个原因:一是香客扫码后取消支付,但后台产生了未支付订单记录;二是线下手工登记漏掉了某笔大额捐款;三是线上延迟到账导致统计周期边界不一致。前两类需要人工确认,第三类只需把时点重新校准就能消掉。
我把差异项输出到一个单独的Excel sheet,命名为“待核对清单”,每一行包含渠道、时间、金额、备注、差异原因预判,寺务财务只需要照着清单去核实,不用再全表翻找。
4.2 分账引擎的规则计算
分账引擎接收对账后确认无误的“有效流水”,按照比例规则生成拆分结果。它本质上就是一组Pandas的分组聚合操作,我写了一个函数,逻辑清晰,便于日后加新规则:
import pandas as pd def run_split(df, rules): records = [] for _, row in df.iterrows(): amount = row["金额"] for account, ratio in rules.items(): records.append({ "流水号": row["流水号"], "渠道": row["渠道"], "金额": amount * ratio, "科目": account, }) result = pd.DataFrame(records) return result.groupby("科目", as_index=False)["金额"].sum()比例运算会产生浮点数尾差,比如100元按0.1的比例算出10.000000000000002这种结果。解决办法是最后用round(amount, 2)处理一遍。如果对精确位数有更严格的要求,可以引入decimal.Decimal,但在我这个场景里分账结果只取到分,round已经够用。
分账结果还要生成一个汇总视图,按科目列出本月总额、笔数、占总金额比例,方便寺务会议时直接展示。
4.3 定时任务与异常通知
抓取和分账流程,我最终把它放到了定时任务里。开发阶段我在本机用任务计划程序跑,每天凌晨2点执行一次,避开业务高峰,也减少对平台后台的干扰。
为了部署方便,我用Python的schedule库做了个轻量定时器,不用依赖外部服务:
import schedule schedule.every().day.at("02:00").do(run_daily_job) while True: schedule.run_pending() time.sleep(60)真正上线之后我在想,万一凌晨定时任务抓取失败,等天亮才发现,影响了整个白天的分账效率怎么办?所以我加了一个异常通知模块。用钉钉群机器人的自定义webhook,出错时往群里推一条消息,消息里带上错误类型、出错渠道、截图文件路径。代码很简单,就是构造一个带markdown文本的POST请求,大家在自己群里创建机器人拿到webhook地址就能用。这样即使凌晨任务挂了,早上看一眼群消息就知道处理方向,不用等财务同志来喊。
定时任务还有一个细节要提醒:为防止上一次任务没有跑完下一次又开始,我用了一个进程级的锁文件,任务开始时创建文件,结束时删除,如果检测到锁文件存在就直接跳过本轮执行,避免多个进程同时抓取导致数据重复或账号异常登录。
5. 上线后的避坑指南与实战心得
5.1 我实测踩过的坑
第一个坑是Chrome自动升级导致驱动失效。项目跑了两周之后某一天突然全部失败,排查半天发现是本机Chrome在后台自动升级了一个小版本,ChromeDriver没跟上。后来我关掉了Chrome自动更新选项,并写了一个启动时自动检查驱动版本的脚本,遇到版本不匹配就报警。如果有朋友部署在服务器上,推荐用Docker封装固定的Chrome和Driver镜像,能从根本上绕过这个问题。
第二个坑是微信后台的页面结构在重大更新后变化很大。某个月初再跑脚本,发现原来的表格选择器全部定位不到元素。我花了整整半天重新定位。后来学聪明了,把所有页面元素的选择器统一集中在一个配置文件里,页面改版只改配置文件,不用改代码逻辑。这算是我这次项目里比较值的一个经验。
第三个坑是浮点数尾差造成分账报表不平。肉眼检查时没事,用Excel求和对比时差了0.01元,虽然是小事,但在财务场景里哪怕一分钱的不平也会显得不专业。处理办法是我之前提到的round精修,另外我在报表里保留了四舍五入前的原始结果,方便审计追溯。
第四个坑是Cookie过期时间不固定,有的渠道三天就失效,有的能撑一个月。我放弃了预判,改成每次运行前先做登录状态检测,检测到失效就停下来发通知,让值班的人扫一次码。虽然做不到全自动,但至少不会产生静默失败。
5.2 常见问题速查表
为了便于维护,我把项目运行中遇到的问题整理成了一张速查表。这里挑几条重点分享:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动报session not created | ChromeDriver版本与浏览器不匹配 | 下载匹配版本的ChromeDriver,或关闭浏览器自动更新 |
| 页面元素找不到 | 页面改版或动态加载未完成 | 改用显式等待,检查元素选择器是否失效,更新到配置文件中 |
| 登录状态丢失 | Cookie过期或浏览器清理了缓存 | 重新扫码登录,更新Cookie文件,加登录状态检测 |
| 抓取到的金额带符号和空格 | 页面渲染格式 | 用正则清洗字段,统一转成浮点数 |
| 对账总差0.01元 | 浮点数计算尾差 | 对金额字段做round处理,保留原始值备查 |
| 定时任务重复执行 | 上一次任务未结束 | 加进程锁文件,跳过重叠执行 |
这张表的价值在于,即使我没参与现场维护,寺里另一位懂一点技术的义工也能照着表独立处理大部分问题,不需要每次出问题都找我。
5.3 这套方案的扩展空间
这套自动分账系统在寺庙场景跑顺之后,我发现它的设计思路可以直接迁移到其他场景。比如社区爱心食堂的捐款管理、校友会的会费与捐赠对账、小商家的多平台流水汇总,甚至是家庭里几个账户之间的月度账目自动核对。本质上,凡是“人工登录后台下载表格再手动拆账”的重复劳动,都可以用这套“浏览器自动化+规则引擎”替代。
如果要扩展,我认为有两个方向比较有价值。一是增加OCR识别能力,把线下手工登记纸质表格拍照后自动识别并录入系统,彻底去掉人工录入环节;二是把报表模块打通到在线文档,自动生成在线分账报表,寺务成员打开链接就能看到当月善款去向。这两个方向我现在已经在陆续尝试,等跑稳定了再写一篇新的实践分享。
最后说点个人体会。做这个项目给我最大的感触是,技术不一定非要多新、多花哨,Selenium这种老牌工具用在恰当的业务场景里,照样能给一个非营利小机构省下不少人力。关键在于先想清楚流程和规则,再动手写代码。把对账规则、分账比例、异常处理流程梳理得一清二楚之后,代码本身反而只占了整个项目不到一半的工作量。
如果有人要复刻这套系统,我的建议是:先拿一个渠道做试点,把最核心的抓取、对账、分账流程跑通,再逐步加其他渠道。一上来就想三四个渠道全部自动化,大概率会陷入同时调试多个平台的选择器困境。一步一步来,稳定性自然就有了。