首页 / 资讯中心 / 文章详情

用Playwright实现WorkBuddy积分自动领取:五个坑与完整方案

用Playwright实现WorkBuddy积分自动领取:五个坑与完整方案 ★ FEATURED ARTICLE
每天九点手机闹钟响起来的那一声曾经是我一天里最不想听到的声音。不为别的就为 WorkBuddy 里那笔每天需要手动领取的积分。你可能觉得领个积分能有多麻烦打开应用、找到入口、点一下按钮撑死三十秒。但三十秒乘以三百六十五天而且中间但凡有一天忘了连续签到周期就得清零重来。说实话我坚持了两个月之后终于决定不能再这么老实了得把这件事做成全自动。于是就有了这个WorkBuddy 积分自动领取的项目。折腾了一周多踩了五个比较隐蔽的坑才勉强跑通再花了三周把它打磨到基本不用管。这篇文章就是完整记录我为什么选浏览器自动化而不是抓接口第一版脚本是怎么写的五个坑各自是什么现象、什么根因、怎么排查、怎么解决以及最终部署到定时任务里的完整方案。如果你也想把类似的每天手动打卡变成每天自动完成这篇应该能帮你省下不少弯路。1. 为什么我非要折腾这个自动化先说清楚背景。WorkBuddy 是我们团队日常使用的一个协作平台里面有个积分体系每天登录后可以手动领取当日积分连续领取有额外奖励。说实话积分本身不算什么硬通货但连续领取的奖励系数是累乘的断签一天就得从头再来。这事最难的地方恰恰在这里它不重但天天不能忘。工作日还好周末和节假日一忙起来真的会连续三天想不起来然后某天突然一拍大腿完了断了。手动领积分这个动作我拆解过大概分四步打开入口、等待页面加载、找到领取按钮、确认到账。看起来简单但里面有个隐性成本它打断了你的工作流。九点多正写文档或者开会前准备材料突然被闹钟打断了领完之后再回到原任务注意力已经碎片化了。每天都来一次这种打断虽然只有几十秒但累计下来是非常影响心流的。让我决心自动化的另一个原因是我发现平台的每日领取在规则上是纯粹的重复动作没有任何需要人类判断的地方。既然行为本身是可确定的那就没有理由不把它交给脚本。这个项目本质上是个典型的无人值守日常任务自动化通过脚本定时登录、带状态地执行点击、对结果做校验、出错时留痕。适合做这类尝试的不限于是积分凡是那种每天重复、规则固定、有明确完成标志的操作都可以套这个思路。比如每日签到、每日报表下载、每日数据刷新、每日巡检任务。但我要先泼一盆冷水如果你只是想领积分又完全不懂代码那我建议先用平台自带的功能或者手机提醒而不是硬写脚本。自动化是有维护成本的尤其当页面改版、登录策略调整的时候脚本可能比手动还累。我的判断标准就一条这个动作如果坚持执行一年累计的时间成本是否超过我写脚本和维护脚本的成本我的答案是超过所以做。1.1 前置条件搞清楚你要自动化的流程细节动手之前我花了大概半小时把手动领取这个动作的完整链路记录下来。不是简单地记点按钮而是把每一步的页面状态、按钮文案、成功后的反馈都写下来。这一步非常关键因为后面写脚本的判断逻辑全靠这些细节。我的操作链路是这样的打开 WorkBuddy 首页 - 等待加载完成 - 点击顶部导航的积分中心 - 页面中有一个区域显示今日积分待领取/已领取状态 - 如果显示待领取点击领取今日积分按钮 - 按钮变为已领取且今日积分数字增加 - 关闭页面。其中最需要注意的有两点第一待领取和已领取是两个不同状态的文案脚本必须能区分否则你不知道应不应该执行领取动作。第二领取成功之后有一个明确的反馈比如按钮文案变化或出现提示这是判断是否已经领取成功的关键。如果只是点击完就走脚本很可能因为没等反馈而误判或者造成重复提交。把这些信息记录在一张表里后面写代码时会少很多困惑。1.2 选型为什么我选 Playwright 而不是抓接口或按键精灵方向定了之后接下来是整个项目里最需要想清楚的决定用什么技术方案来实现我当时列了三四个候选然后逐一排除。第一类是直接分析 WorkBuddy 的接口用脚本调 HTTP 请求来领取积分。这是最轻的方案速度最快资源占用最少也不依赖图形界面。但问题是它非常脆弱。只要平台的接口地址、参数签名、请求头校验有任何变动脚本立刻失效而且失效了你还不知道。另外登录态获取也是个麻烦事WorkBuddy 的登录流程带有动态 token 校验纯请求模拟等于把整个登录协议逆向一遍成本和风险都很高。自动化的核心目的是省心不是炫技所以我把这个方案划掉了。第二类是按键精灵/鼠标宏这类模拟键盘鼠标的工具。这类工具的优势是上手快录制一遍就能回放。但致命的问题是录制出来的脚本定位方式太脆它依赖鼠标在屏幕上的绝对坐标或相对窗口的相对坐标只要窗口位置变了、分辨率变了、页面布局稍微改动脚本就跑歪了。对付一个可能改版的网页这显然不够健壮。第三类是 Selenium老牌浏览器自动化框架思路成熟但安装驱动、处理浏览器版本匹配这类问题在反复折腾后容易让人失去耐心。我最后选的是 Playwright因为它在定位元素的方式上更贴近人怎么找按钮而非坐标在哪自带等待机制还支持持久化登录态这些特性正好对着我后面遇到的坑。2. 第一版脚本从看起来能跑到真的能跑方向定下来之后我心里很清楚一件事不要试图一次写完美先让脚本在手动环境里把整个流程跑通再考虑定时、异常、日志这些工程化问题。所以第一版的目标极其朴素打开浏览器登录 WorkBuddy找到按钮点击验证成功。至于无人值守、断点续跑、失败重试这些全都留给下一轮。安装依赖没什么好说的Python 环境里pip install playwright再执行playwright install chromium装浏览器内核这一步是新手很容易漏的。装完之后我先用一段最基础的脚本验证能不能打开一个正常的 Chromium 实例from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://workbuddy.example.com) page.wait_for_timeout(3000) browser.close()headlessFalse这个参数在调试阶段非常重要。我第一次调试时图省事直接用了无头模式结果页面打开后遇到一个需要人工滑动的验证组件我根本看不见它脚本卡在那里毫无反应日志里也没任何有效信息。后来老老实实把浏览器窗口调出来一眼就看到了问题所在。等流程完全稳定之后再切回无头模式继续观察。这是我的第一个经验自动化脚本的调试尽可能先带界面跑。跑通打开页面之后下一步是定位按钮。WorkBuddy 的按钮文本是领取今日积分最直观的写法是用文本定位page.get_by_text(领取今日积分).click()但这版跑了一天就暴露问题了后面细说。眼下先保证能点、能领、能验证。我的第一版完整逻辑差不多是下面这样打开页面 - 点击积分中心入口 - 检查页面是否出现待领取文案 - 如果出现点击领取今日积分 - 等待按钮文本变成已领取 - 记录日志。当时没做太多防御纯粹抱着能跑就行的心态以为第二天挂到定时任务里就万事大吉。后来的事实证明我太天真了。2.1 登录态的第一道坎自动化浏览器和我的浏览器不是同一个世界第一版脚本第一次运行时我遇到了一个哭笑不得的问题页面打开之后WorkBuddy 显示的是登录页而不是我平时直接打开时看到的已登录状态。原因其实很简单我平时用浏览器的时候登录态的 Cookie 是存在我日常浏览器 profile 里的但 Playwright 每次启动launch()创建的都是一个全新的独立浏览器上下文它跟我平时用的浏览器没有任何关系自然也就没有我的登录信息。面对这个问题最开始我图省事直接在脚本里写死微信扫码登录然后手动扫码。但这等于每天还要肉身守一次完全违背了自动化的初衷。正确的解法是让 Playwright 复用我平时使用的浏览器数据目录这样它启动时就会加载已经登录过的 Cookie 和本地存储。具体实现用的是launch_persistent_contextfrom playwright.sync_api import sync_playwright USER_DATA_DIR D:/playwright-chrome-profile with sync_playwright() as p: context p.chromium.launch_persistent_context( user_data_dirUSER_DATA_DIR, channelchrome, headlessFalse, viewport{width: 1280, height: 800}, ) page context.pages[0] if context.pages else context.new_page() page.goto(https://workbuddy.example.com) page.wait_for_timeout(5000) context.close()这个方案有几个好处首次运行时会弹出一个有界面的 Chrome我手动登录一次之后的运行它会直接带着已登录状态进入页面不需要再扫码。Cookie 过期除外。这一步做好之后脚本终于能以我的身份进入 WorkBuddy 了。但这也只是万里长征第一步因为很快就迎来了第一个真正的坑。3. 坑一登录态在第二天就失效了而且我没发现第一版脚本刚跑通的那个晚上我信心满满地设了每天早上九点的定时任务。第二天早上九点零三分我打开日志看到一行点击成功心里还挺高兴。结果打开 WorkBuddy 一看积分根本没到账。再仔细看日志我发现在点击成功之前还有一行我没注意到的记录等待领取按钮超时尝试备用定位器——随后程序走了备用分支点击了一个我以为是领取但实际上是重新登录入口的按钮。整件事一句话脚本以为自己领到了积分其实它执行的是一个错误动作。这背后的根因还是登录态。launch_persistent_context虽然复用了浏览器目录但 WorkBuddy 的登录态并不只是简单地存在于 Cookie 里它还会校验一些会话相关的本地存储信息。我平时用的浏览器每天都会保持活跃所以登录态一直在刷新。但 Playwright 用同一个数据目录启动的浏览器长时间没有人工活动服务端觉得会话不活跃悄悄把登录态清了。更尴尬的是这个清理动作不是立刻把页面踢回登录页而是让页面停留在加载中的状态某些按钮看起来还在但点击后会被路由到登录提示上。这个坑给我最重要的提醒是判断动作有没有成功不能只看我点了按钮要判断点完按钮之后目标状态有没有发生预期变化。这里的目标状态就是今日积分已领取。我后来在脚本里加了双保险点击之前读取当前积分数字点击之后再次读取积分数字只有数字增加且按钮文案变为已领取才算成功。任何一步落空脚本立刻终止并发送通知绝不假装无事发生。现象脚本显示点击成功但积分没到账页面被路由到登录提示。根因长时间无人工活跃会话被服务端清理脚本没有校验最终状态。处理点击后必须断言结果状态增加已登录/未登录的入口识别发现未登录直接发通知。3.1 用状态断言替代动作完成作为成功标准这个思路我后来想清楚了自动化脚本其实可以分成两层。第一层是动作层就是定位元素、点击、输入它只保证操作发生第二层是状态层就是检查页面是否进入了预期状态、数据是否发生了变化、返回值是否符合预期。只有两层同时成立才叫一次成功的执行。具体到我这个场景最可靠的状态信号有两个。第一个是按钮文案如果当前是待领取点击成功后应该变成已领取。第二个是积分数字点击前后积分总数会有对应增加。我把这两个信号都写到代码里并且用expect的轮询等待来代替固定sleep。sleep(3)的问题是网络慢的时候 3 秒根本不够网络快的时候又白白浪费时间。而下面这种写法会在指定时间内反复检查直到条件满足或超时from playwright.sync_api import expect # 点击领取按钮 page.get_by_role(button, name领取今日积分).click() # 等待按钮文案变为已领取最多等 10 秒 expect(page.get_by_role(button, name已领取)).to_be_visible(timeout10_000)只有这段断言通过了脚本才会继续往下走。如果超时说明状态异常按失败处理。整个脚本的执行路径从线性点完就完变成了每一步都带着验收标准稳定性明显上了一个台阶。4. 坑二按钮没丢但定位器失效了——动态 class 与文本陷阱登录态的问题解决了之后脚本稳了大概三天。第四天日志里蹦出一个新的报错TimeoutError: Timed out waiting for locator。我去手动打开页面一看按钮明明就在那里文案也没有变化为什么自动定位就找不到问题出在我用的定位方式上。第一版里我图简单写的是page.locator(button.claim-btn).click()其中claim-btn是我从浏览器开发者工具里看到的 class 名。但 WorkBuddy 前端打包时对样式类名做了混淆处理每次改版或者重新部署这些类名可能变。更常见的是同一个 class 会在多个元素上出现或者按钮外层有一层阴影 DOM导致标准定位器匹配不到。我再仔细检查发现这个按钮的 class 属性里其实带了一个带时间戳的后缀——前端每次部署都重新生成难怪第二天就失效。这让我下定决心换掉基于 class 的定位方式。Playwright 提供了更贴近人眼的定位方式get_by_role和get_by_text。前者按可访问性角色比如 button、按钮名称来定位后者按页面可见文本来定位。我改成get_by_role(button, name领取今日积分)之后就不再依赖前端 class 变化了。但这里也有个新陷阱name要求精确匹配。如果页面文案是领取 今日积分带空格或者按钮里套了一个图标导致文本被拆成多段精确匹配就会失败。所以我还加了一层兜底用正则匹配来容忍文案周围可能的空白和修饰符。page.get_by_role(button, namere.compile(r领取\s*今日积分)).click()这轮的教训我总结成一句话自动化的定位方式要模拟人怎么描述这个按钮而不是浏览器怎么渲染这个按钮。人会说右边那个写着领取今日积分的按钮但不会说class 是 claim-btn-xxx 的那个元素。定位器能换成语义化的就尽量换稳定性差好几倍。4.1 等待策略隐式等待、显式等待和强制等待怎么选在这轮排错里我还顺手处理了一个一直潜在的问题脚本里原来的多处page.wait_for_timeout(3000)都是临时拍的不是标准做法。严格来说Playwright 有自动等待机制执行点击之前会等元素稳定、可见、可交互。但这并不意味着你可以在任何场景下完全不用手动等待尤其是涉及异步刷新、接口返回后重新渲染的区域。我后来定了一个简单的等待策略页面跳转之后等待某个页面的特征元素出现。比如进入积分中心之后等待今日积分这个标题稳定可见。这一步是确认页面导航完成。点击动作交给 Playwright 自动等待只在极少数情况下增加expect(...).to_be_visible显式断言。绝不使用固定sleep等待大概率足够的时间因为网络状况、服务端响应速度波动太大固定等待要么慢要么不够。这套策略上线之后脚本的稳定性曲线持续好转。而之前用固定 sleep 的坏处在某个网络很差的早晨特别明显脚本醒来后页面加载非常慢它却在某个中间状态等了 3 秒就继续往下点结果点击失去目标整个流程直接失败。换成语义化定位加显式等待之后这类问题基本绝迹。5. 坑三重复执行导致的双重领取和状态污染第三个坑其实是我自己写代码的时候埋下的。最初的脚本没有考虑如果某天已经领取过了再跑一次会发生什么。某天我为了调试手动执行了三遍脚本。前两遍都正常第三遍开始出现一个奇怪的弹窗今日已完成领取请不要重复操作而且后续流程就卡住了。为什么前两遍正常、第三遍卡住因为第一遍领取成功之后页面按钮文案已经变成了已领取。我的脚本里虽然有判断当存在待领取字样时才点击的逻辑但判断依据写的是页面上存在待领取这个文本。问题在于WorkBuddy 在领取成功后会保留一条历史记录上面写着今日领取状态已完成页面里还有其他地方含有待领取字样的残留说明。文本匹配写得过于宽泛导致在已经领取的状态下脚本仍以为还没领跑去点击一个已经被禁用的按钮。这本质上是判断条件不精确造成的幂等失败。自动化的一个基本要求是幂等性同一任务无论跑一次还是跑一百次最终状态应该一致不能因为重复运行而产生副作用。我针对这个场景做了一个更精确的守卫page.goto(INTEGRAL_PAGE_URL) # 等待积分卡片区域渲染完成以积分标题作为基准 page.get_by_text(今日积分).wait_for(statevisible, timeout15000) # 读取按钮文案并去空白 button_text page.get_by_role(button).filter(has_text领取).first.inner_text().strip() if 已领取 in button_text: logger.info(今天已经领取过脚本退出) sys.exit(0) elif 待领取 in button_text: page.get_by_role(button, namere.compile(r领取\s*今日积分)).click() else: logger.error(无法识别的按钮状态: %s, button_text) sys.exit(1)我特意把识别状态提到执行点击之前并且限定在具体的按钮区域内读取文本而不是整页搜索。这样即使页面别处有待领取字样也不会干扰判断。从此之后不管定时任务因为什么原因多跑几次、手动调试跑几次都不会产生双重领取的错误。积分平台通常会从服务端做重复请求拦截但我不能把安全寄托在对方身上自己这端做好幂等才是真正可控的。5.1 日志与状态记录让每次运行都可以被事后复盘处理到这一步我发现脚本变得越来越有脑子了但还缺一样东西运行记录。一台无人值守的机器跑完就静悄悄走了第二天你面对的不是一条错误日志而是毫不相关的页面快照那会非常难排查。所以我给脚本加了两类输出。第一类是控制台和文件双写日志。用 Python 的logging模块配置一个WorkBuddyAutoClaim.log记录每次运行的时间、读取到的页面状态、执行的动作、结果断言是否通过、异常堆栈。第二类是用于事后复盘的状态快照。每次进入积分页面时我会截一张图保存到screenshots目录重点截取积分卡片区域。出问题时这些截图能快速告诉我是登录态失效、按钮文案异常、还是页面改版了。这套日志体系在后面排查第四个坑的时候帮了大忙。import logging logging.basicConfig( filenameWorkBuddyAutoClaim.log, levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, ) logger logging.getLogger(workbuddy) # 关键节点打日志 logger.info(开始执行积分领取任务) logger.info(当前按钮状态: %s, button_text) logger.info(点击成功等待状态断言) logger.info(断言通过今日积分已领取)不要小看这些日志。没有日志的自动化脚本就像没有黑匣子的飞机它能飞但出了事你根本不知道哪一步开始不对劲。6. 坑四跑得太勤反而被风控盯上——频率与随机扰动设计前四个坑都解决之后脚本又连续稳定跑了一周。本以为可以高枕无忧了结果第八天早上我的企业消息里弹出一条异常提醒WorkBuddy 检测到账号行为异常要求重新验证身份。我一看时间正好是脚本执行的那几分钟附近。第一反应是脚本哪里写错了打开了页面疯狂刷新但我检查日志和截图没有发现异常重试。那问题出在哪定睛一看日志我明白了。原来我在调试阶段为了反复验证稳定性把定时任务设成了每五分钟跑一次。周末忘了改回来于是这台机器从早上八点到中午每隔五分钟就登录一次、打开积分页面、模拟点击。在平台风控的视角里一个账号在短短几个小时内几十次打开同一个页面、执行同一个热区点击这显然不是人类行为。系统触发风控是必然的。这个坑的根因是我没有想清楚自动化和正常使用之间的关系。自动化脚本确实是在模拟人但它毕竟不是真人它没有思考停顿、没有鼠标轨迹、没有浏览上下文的自然变化。如果按高频率执行行为特征会非常明显。所以做这类日常任务的自动化频率设计必须克制。我的调整方案是每天只在固定时间执行一次执行前的 5-10 秒内随机偏移避免每天在同一秒触发执行过程中不额外注入随机延迟去模拟人类思考因为那既别扭又容易引入不稳定因素。相反我保持脚本的执行节奏稳定、快速、利落只做必要步骤不做多余访问。import random import time # 在 cron 已经固定到 09:00 的基础上再做秒级抖动 time.sleep(random.randint(0, 35))抖动时间通常选择 30 秒内就足够不需要拉太长否则任务可能拖到和平台后续刷新逻辑撞车。还有一个容易被忽略的细节脚本结束后立刻context.close()关闭浏览器不要让它残留在后台。无意义的后台驻留既浪费资源也可能被平台认为异常挂机。让脚本来也匆匆、去也匆匆反而更符合一个正常用户快速完成操作的习惯。6.1 验证码与滑块出现时脚本应该做什么风控升级的极端情况就是验证码。我在调试 Phase 曾经触发过一次滑块验证当时 Playwright 是能模拟拖拽滑块的但我试过一次之后果断放弃了这个思路。原因很简单一旦平台上了验证码说明它已经对当前环境和行为产生了不信任这时强行去破解验证码是在把一个小问题升级成账号安全问题。我的积分再多也不值得拿账号去赌。所以我在代码里做了明确的兜底如果页面检测到安全验证或滑块这类组件脚本立即停止保留截图发送通知让我人工介入。if page.get_by_text(安全验证).count() 0 or page.locator([class*slider]).count() 0: logger.error(检测到安全验证停止自动化等待人工处理) page.screenshot(pathfscreenshots/captcha_{datetime.now():%Y%m%d_%H%M%S}.png) sys.exit(2)这个策略可能比不上那些硬怼验证码的方案炫酷但它的好处在于风险可控。自动化要解决的是每天花三十秒的问题而不是引入账号被冻结的新问题。把事情做稳比把事情做绝重要得多。7. 坑五积分不是准点刷新的——本地时间和服务端时间的偏差最后一个坑很小但特别坑人因为它表面上根本不像脚本的问题反而像平台抽风。有几天脚本每天早上九点整执行点完按钮之后却显示未到领取时间请稍后再试。我最初怀疑是时区问题仔细核对了本地时间和服务器时间都没有异常。后来查 WorkBuddy 的接口文档里相关信息才发现它的积分刷新时间并不是严格按整点对齐的而是有一个约 30 秒的浮动窗口。也就是说我每天卡在 09:00:00 去点击有时候服务端还没来得及把新一日的积分状态翻出来于是落了个未到领取时间。这个问题的最优解不是把执行时间改到 09:01因为它每天的浮动窗口并不是固定偏移单纯改时间仍然有撞上的概率。稳妥的做法是九点开始执行时先读取页面显示的今日积分区域。如果显示的是待领取就点击如果是未到领取时间或今日未开放则等待 20 秒之后再次刷新页面重试最多重试 3 次。这样一来就算当天刷新有几十秒的延迟脚本也能在不打扰我的情况下自愈。for attempt in range(1, 4): page.goto(INTEGRAL_PAGE_URL) page.get_by_text(今日积分).wait_for(statevisible, timeout15000) status get_status_text(page) if status 待领取: click_and_verify(page) break elif 未到领取时间 in status: logger.info(第 %s 次重试服务端暂未刷新等待20秒, attempt) page.wait_for_timeout(20_000) continue else: logger.error(未知状态: %s, status) break通过这轮重试机制脚本的执行成功率从 92% 提到了接近满分。剩下的零星失败基本都是网络问题有日志和截图人工看一眼就能判断原因。之前那种明明每天在跑却偶尔不生效只能靠偶尔手动检查发现的情况算是彻底解决了。8. 完整方案清单代码骨架、定时任务与监控告警踩完五个坑之后我的 WorkBuddy 积分自动领取脚本终于从能跑进化到能一直跑。下面把最终方案完整列出来给想复现的朋友一个参考。整个项目结构就三个文件加一个截图目录main.py是主脚本config.json放路径和关键文案workbuddy.log是运行日志。定时任务我用的是 Windows 计划任务因为这套脚本就跑在我办公常用的那台 Windows 机器上如果你用的是 Linux 服务器cron的配置思路完全一样。8.1 最终脚本核心骨架import json import logging import random import re import sys import time from datetime import datetime from pathlib import Path from playwright.sync_api import sync_playwright # 读取配置 config json.loads(Path(config.json).read_text(encodingutf-8)) USER_DATA_DIR config[user_data_dir] INTEGRAL_PAGE_URL config[page_url] LOG_FILE config[log_file] # 日志配置 logging.basicConfig( filenameLOG_FILE, levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, ) logger logging.getLogger(workbuddy) def get_status_text(page): 读取积分卡片区域的状态文本 card page.get_by_text(今日积分).locator(..) return card.inner_text() def click_and_verify(page): button page.get_by_role(button, namere.compile(r领取\s*今日积分)) button.click() page.get_by_role(button, namere.compile(r已领取)).wait_for( statevisible, timeout10000 ) logger.info(领取成功已验证) def main(): time.sleep(random.randint(0, 35)) # 秒级抖动降低行为特征 with sync_playwright() as p: context p.chromium.launch_persistent_context( user_data_dirUSER_DATA_DIR, channelchrome, headlessTrue, ) page context.pages[0] if context.pages else context.new_page() logger.info(开始执行积分领取任务) for attempt in range(1, 4): page.goto(INTEGRAL_PAGE_URL) page.get_by_text(今日积分).wait_for(statevisible, timeout15000) if page.get_by_text(安全验证).count() 0: logger.error(检测到安全验证停止自动化待人工处理) page.screenshot( pathfscreenshots/verify_{attempt}_{datetime.now():%H%M%S}.png ) sys.exit(2) status get_status_text(page) logger.info(第 %s 次读取状态: %s, attempt, status) if 待领取 in status: click_and_verify(page) break elif 未到领取时间 in status: logger.info(服务端暂未刷新等待20秒后重试) page.wait_for_timeout(20_000) continue elif 已领取 in status: logger.info(今日已经领取过无需重复操作) break else: logger.error(未知状态停止执行) sys.exit(1) context.close() if __name__ __main__: main()这个版本基本就是我实际在用的版本只是去掉了一些具体业务路径细节。它已经涵盖了登录态复用、语义化定位、状态断言、幂等处理、异常终止、日志留痕、积分刷新补偿这几个关键点。还有一点要强调headlessTrue只在该脚本稳定运行之后才开启如果第一次部署建议先保持headlessFalse人工观察两次运行完全正常再切。8.2 定时任务与配置示例Windows 计划任务的配置要点有三个。第一操作里选启动程序程序填python.exe的绝对路径参数填main.py起始目录填脚本所在文件夹。第二触发器设为每天 09:00这个时间在脚本里已经做了秒级随机偏移所以不必在计划任务层再做复杂的间隔配置。第三在条件选项卡里取消勾选只有在计算机使用交流电源时才开始此任务这类限制否则笔记本合盖或者插拔电源的状态变化会导致任务被跳过。Linux 上对应一行 cron0 9 * * * cd /opt/workbuddy /usr/bin/python3 main.py workbuddy.log 21config.json的内容也很简单{ user_data_dir: D:/workbuddy-auto-profile, page_url: https://workbuddy.example.com/integral, log_file: workbuddy.log }8.3 监控与告警自动化不是跑完就不管最后一步是把无人值守变成无人值守但有事必报。我自己的做法是在脚本的失败路径里调用一个 Webhook 通知接口把错误类型和截图文件路径推到我的移动设备上。只有当脚本真正失败或者检测到验证码的时候才会收到消息平时完全静默。如果你的环境里没有 Webhook 这类设施退而求其次可以把失败日志写到固定的共享目录配合系统自带的通知机制也行。关键是做到异常必达而不是每天去翻日志确认一切正常。依赖这套监控脚本已经稳定运行了相当长一段时间。我每天早上的动作从打开 WorkBuddy 领积分变成了等通知、有通知才处理、没通知就正常干活。从时间成本上看每天省下的那三十秒微不足道但从心智负担上看省掉的是每天都要记得做这件事的那份牵挂。我后来还把这个思路用到了另一个每日报表同步的小任务上复用同一套 Playwright 骨架只改了页面定位和验证逻辑大概半小时就完成了。由此可见真正值钱的不是那行button.click()而是整个如何稳定地重复执行一件事的方法论——先确认状态再执行动作再验证结果最后留好日志和告警。把这四步做成闭环任何日常重复任务都可以放心交给脚本。有个细节我想最后再提一下写这类自动化脚本最忌讳的就是一上来就想着写得特别强大、覆盖所有异常。我的经验是先解决 95% 的正常路径再把那 5% 的异常一个一个用日志和截图兜住每兜住一个脚本的可靠程度就上一个台阶。这五个坑踩完之后我最大的感受是自动化的难点从来不在让程序帮你点一下按钮而在搞清楚程序什么时候该点、点错了怎么发现、发现了怎么让我知道。想明白这三点你的脚本才能真正从能用一天变成能用一年。
阅读完成 · 觉得有帮助?
咨询建站