ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Playwright与节点API构建接单任务自动化闭环

用Playwright与节点API构建接单任务自动化闭环 你可能也经历过这种状态手上一堆外包任务从需求确认到交付回款全靠在几个网页之间来回切换复制粘贴、改文件名、发通知忙一下午发现真正干活的时间没多少。我最近用 Playwright 搭了一套自动化爬虫流程把外部依赖统一收敛到一个节点 API 网关把接单后的需求拉取、任务流转、交付回填和对账提醒整条链路跑通了。这篇文章就把整个落地过程拆开讲一遍包括技术选型、核心代码、踩坑记录以及我明确不碰的自动化边界。先说清楚一个前提我这里说的“全自动接单结算”不是去抢单、刷单也不是绕过平台支付流程。自动化的对象是任务受理之后的重复性工作比如新需求提醒、需求字段提取、交付文件归档、账单登记。涉及报价确认、验收、资金支付的动作我全部保留人工判断。如果你期望学的是靠脚本去薅平台羊毛、刷好评、批量抢单那这篇帮不了你那种玩法迟早出事而且出事就是大事。1. 内容整体设计与思路拆解1.1 从“手动接单”到“自动闭环”的流程重构我以前在威客平台接私活流程看上去不复杂但架不住单子一多就乱套。每天早上一睁眼要先刷一遍消息列表看有没有新需求点进去之后把需求复制下来整理字段排进自己的待办做完之后又要回传交付信息手动登记回款。这套流程里真正需要动脑子的部分可能只占三成剩下七成全是“看到页面 - 复制内容 - 填到另一个地方”的机械操作。手动流程的痛点很明显第一刷新频率太低容易漏单刷新太频繁又浪费时间第二不同项目的需求字段格式不统一人工整理容易漏项第三交付和回款记录散落在各个聊天记录里月底对账要翻半天第四人的注意力有限同时处理五个任务时很容易把A客户的交付文件发到B客户那边。所以这个闭环的核心思路不是“做一个万能爬虫”而是把原有业务流程拆成小步骤找出哪些步骤是规则固定的、重复度高的然后交给自动化。重构后的流程大概是定时巡检任务列表发现新需求就抓取详情结构化写入数据库任务进入处理阶段后通过状态机跟踪进度交付完成后自动归档文件、发送通知、登记账单信息。整个链路里每个环节的输入和输出都清晰可见出问题也能快速定位。1.2 为什么这个闭环能跑通模块拆解与数据流我把整个系统拆成了四个模块。采集模块负责和浏览器页面打交道核心工具就是 Playwright因为要面对大量 JS 动态渲染的页面requests 直接拿 HTML 根本拿不全。数据模块负责存储和去重我用了一张简单的任务表加上状态字段和唯一索引避免重复入库。能力模块是一个统一的节点 API 网关把企业微信通知、短信、云文档写入这些外部能力收敛成 HTTP 接口。调度模块负责把前面几个模块串起来用定时任务触发采集用消息队列做异步处理。这四个模块之间不是嵌套调用而是通过数据和消息解耦。采集模块只负责“把页面上的字段变成结构化数据”不关心数据后面怎么用能力模块只负责“把一条通知发出去”不关心这条通知是从哪个流程来的。这样设计的好处是任何一个模块想换成替代方案都不需要动其他模块。比如今天我用 Playwright明天想换成别的自动化框架只要采集模块输出的数据结构不变后面完全不用改。这个结构有点像餐厅的流水线有人负责切菜有人负责炒菜有人负责出餐各管一段中间用单据传递信息。如果切菜的人请假了换一个切菜手法不同的人也一样能顶上因为下游只关心切好之后装进盘子里的食材长什么样。2. 核心细节解析与实操要点2.1 为什么选 Playwright 而不是 requests、Scrapy 或 Selenium总有人问Python 爬虫不是用 requests 和 Scrapy 就行了吗我之前也是 requests 和 Scrapy 的忠实用户但这次项目里有很多页面是登录后才能看到的并且数据是前端异步加载出来的直接请求 URL 拿到的 HTML 里没有任务内容得分析 XHR 请求、找接口、复现参数这套流程下来工作量一点不小而且接口参数稍微加密一下就要花大量时间去逆向。Scrapy 的优势在于分布式和规模化抓取但对于我这个场景来说任务量没有大到需要分布式反而需要频繁处理“登录、点击、翻页、上传、下载”这类浏览器交互。用 Scrapy 处理这些交互得搭配 Playwright 或 Splash 中间件链路变长调试也麻烦。Selenium 倒是能处理交互动但 WebDriver 版本要和浏览器版本严格对应装环境的时候很容易出幺蛾子执行速度和稳定性也稍差一些。Playwright 在这几个方案里比较均衡。它有自动等待机制元素没出现的时候会自动等不用写一大堆显式等待选择器引擎很强大支持 text、CSS、XPath 混合定位还支持多标签页操作、网络请求拦截、Trace Viewer 回放调试体验比 Selenium 好太多。如果你想快速把操作录制下来变成脚本它内置的 codegen 命令可以直接录制浏览器操作并生成代码起点会低很多。如果你面对的场景是接口明确、能直接拿到 JSON 数据那用 requests 做轻量采集完全够没必要上 Playwright。我的经验是页面操作和接口调用不要混在一起。能用接口解决的优先用接口只有需要真实浏览器去渲染、点击、验证的时候才把 Playwright 放上去。2.2 节点 API 在这个体系里解决什么问题项目里会用到一堆外部能力给客户发通知、把交付清单写入在线表格、定时给自己推提醒消息。这些服务各有各的地址、鉴权方式和限流策略如果我在爬虫脚本里分别写各个服务的 SDK代码会变得很臃肿调试任何一个环节都要去翻对应的文档而且每次换服务商都得改业务代码想想都头疼。我采用的方案是加一个统一的节点 API 网关所有外部能力都在网关层做适配。业务侧只面对一个网关地址发送不同的事件类型网关负责路由、鉴权、重试和限流。在代码里我把它称为节点 API因为它承担的是“节点调度”的角色把不同的外部服务像节点一样串起来。这样做的好处是账号凭据不会散落在爬虫业务代码里都集中在网关环境变量中日志也能在网关侧统一记录方便排查某条消息到底发没发出去更重要的是业务逻辑和外部能力做到了解耦。后来我把其中一个通知服务商换掉了只改了网关里的适配逻辑爬虫代码一行没动。很多人会把“节点 API”理解成网络代理节点那通常是另一个话题和业务 API 网关不是一回事。这里我只讨论能力聚合的场景。2.3 登录态、等待策略与页面操作的关键细节做登录类页面自动化最容易踩的坑就是每次启动脚本都重新登录。重新登录不仅慢而且频繁触发验证码的概率会明显增加。我采取的方案是使用 Playwright 的 storage_state也就是把登录后的 cookie 和 localStorage 保存到本地文件下次启动直接加载。这样脚本可以保持登录态运行很久直到平台主动踢掉登录。等待策略也很关键。新手最容易写time.sleep(3)但固定等待在慢网络下会超时在快网络下又浪费大把时间。Playwright 的wait_for_selector和expect是我用得最多的它们会根据元素实际状态来等待比如“列表加载完成”“某个按钮可见”“某个请求返回”。只要合理设置 timeout脚本稳定性会明显提升。页面定位我建议优先用locator不要再用旧式的query_selector那一套。locator的可读性更好也支持自动等待。比如page.locator(.task-item).first一眼就知道在定位什么。如果你要遍历一组元素用all()拿到列表再逐个处理比在页面上下文里倒腾 XPath 舒服很多。登录态保持和等待策略都不涉及破解行为只是减少重复操作和等待时间。如果平台本身要求人工验证你该看还是得看不要想着靠脚本强行绕过那是另一条不归路。3. 实操过程与核心环节实现3.1 第一版 Demo登录、拉单、通知我先写了一个最简版本跑通流程目标是启动浏览器 - 加载登录态 - 打开任务列表页 - 抓取前几个任务标题和链接 - 通过节点 API 发一条通知到工作群。import asyncio import httpx from playwright.async_api import async_playwright NODE_API_URL https://your-node-api.example.com/api/webhook async def notify_node_api(payload: dict): async with httpx.AsyncClient() as client: resp await client.post(NODE_API_URL, jsonpayload, timeout10) print(notify status:, resp.status_code, payload) async def process_tasks(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) context await browser.new_context(storage_statestate.json) page await context.new_page() await page.goto(https://your-platform.example.com/dashboard) try: await page.wait_for_selector(.task-item, timeout15000) except Exception: print(任务列表加载超时可能需要人工登录) await browser.close() return task_items await page.locator(.task-item).all() for item in task_items[:5]: title await item.locator(.task-title).inner_text() url await item.locator(a).get_attribute(href) await notify_node_api({ event: new_task, title: title, url: url, }) await context.storage_state(pathstate.json) await browser.close() asyncio.run(process_tasks())这段代码里有一个细节容易被忽略在每次跑完之后我重新保存了一次storage_state。原因是登录态可能在运行过程中被平台刷新比如某些平台会周期性续期 token如果只保存最初的登录态时间长了还是会失效。定期把最新状态写回文件能最大限度延长免登录时间。超时处理也值得注意。wait_for_selector超时之后我选择直接关浏览器并打印提示而不是继续往下执行。为什么这么处理因为任务列表都加载不出来后面再抓也是空数据不如停下来观察是不是登录态失效了等人工介入后再继续。3.2 把“接口调用”和“页面操作”串成一个闭环第一版 Demo 只是单向的通知还谈不上闭环。我接着加入了一个简单的任务状态机把“受理”“处理中”“已完成”“已通知”“已归档”这几个阶段串起来。后端维护一个状态字段每次流转都校验是否合法避免任务被重复处理。from enum import Enum class TaskState(Enum): RECEIVED received PROCESSING processing COMPLETED completed NOTIFIED notified ARCHIVED archived ALLOWED_TRANSITIONS { TaskState.RECEIVED: {TaskState.PROCESSING, TaskState.ARCHIVED}, TaskState.PROCESSING: {TaskState.COMPLETED}, TaskState.COMPLETED: {TaskState.NOTIFIED}, TaskState.NOTIFIED: {TaskState.ARCHIVED}, } def transit(task_id: str, current: TaskState, new_state: TaskState): if new_state not in ALLOWED_TRANSITIONS[current]: raise ValueError(f非法状态流转: {current.name} - {new_state.name}) # 更新数据库记录 print(ftask {task_id}: {current.name} - {new_state.name}) return new_state状态机在这个闭环里解决了一个很实际的问题重复通知。以前我做过一个没有状态管理的版本定时任务每次发现页面上的任务还在列表里就往群里发一遍通知结果客户问了好几遍“你怎么同一个需求发三次”。加上状态机之后只有在RECEIVED状态才能发通知发完立即切到PROCESSING后面即使采集模块再次扫到同一条任务也会被状态校验挡住。同时我引入了一个简单的幂等判断。每个任务在数据库里有唯一约束即使消费程序处理消息时重复触发了也不会插入重复记录只会更新原记录的状态。这个机制对“全自动”来说非常重要因为定时任务和消息队列都可能出现重试场景没有幂等设计很容易把数据搞乱。3.3 日志、重试与状态机的加固闭环跑通之后我还要考虑稳定性的问题。第一版脚本是跑一次就完事但当它变成一个每天自动执行的生产脚本就必须有日志、重试和异常处理。我用的是 logging 加 loguru 这一套组合。每个任务分配一个 request_id从采集模块开始一直到节点 API 通知所有日志都带着这个 id 打出来。这样如果某个任务在通知环节失败了我在日志里搜一下 request_id就能看到它经过了哪些阶段挂在了哪一步。重试机制我用的是指数退避策略。比如调用节点 API 超时先等 2 秒重试再等 4 秒再等 8 秒最多重试 3 次。重试无效的任务会进入死信队列等待人工查看。死信队列非常重要它保证了系统不会无限重试消耗资源又能避免任务悄无声息地丢失。Playwright 自带的 Trace Viewer 我也是强烈推荐。在跑关键流程时开启 tracing把整个操作录下来出现异常时保存成一个 trace 文件然后用浏览器打开回放界面可以清楚看到脚本每一步做了什么页面当时显示了什么定位问题特别快。这比我以前靠 print 日志猜问题高效太多了。4. 常见问题与排查技巧实录4.1 高频问题速查表我整理了一份实际运行中经常遇到的问题以及对应的排查思路这里直接放出来。症状可能原因解决思路页面元素定位不到选择器过期或页面改版用page.wait_for_selector再定位打开 Trace Viewer 回放看实际 DOM登录态失效storage_state 过期服务端踢掉登录捕获失效特征页发送“需要人工登录”告警不要硬跑任务列表加载超时网络慢或平台接口异常增大 timeout增加重试超时后进入死信队列接口调用超时节点 API 网关后端不稳定设置 retry 指数退避超时重试 3 次后告警重复通知没有状态管理或消费幂等引入状态机数据库加唯一约束通知函数做幂等浏览器内存占用越来越高context 或 page 未关闭每次任务结束显式close()定期重启进程Playwright 安装失败浏览器下载被中断或网络问题使用playwright install chromium重试指定浏览器版本有一个隐藏比较深的问题就是平台页面偶尔会弹出“异常流量提示”之类的中间页。以前我只看当前 URL 是不是目标 URL忽略了页面内容可能已经被替换。后来我加了一步抓取任务列表前先判断页面上有没有“输入验证码”“异常访问”这类关键词一旦出现就立刻停止批量操作等待人工处理。这一步救了我很多次。4.2 稳定性维护从“能跑”到“一直能跑”脚本能跑通和能稳定跑三个月完全是两回事。我在这段时间里做的最重要的一件事就是给所有外部调用设置合理超时。没有超时的代码一旦某个服务挂起整个调度队列都会被堵住后续任务全部积压。另外控制请求频率也很重要。定时任务不要设计成每隔几十秒扫一次页面这对自己的服务器和对方平台都不友好。我在调度时把巡检间隔设定为 5 到 10 分钟并且加入随机抖动让执行时间不那么规律。这样既能满足及时性也避免形成过于规律的请求特征。浏览器实例管理上我坚持“用完即关”的原则。每个任务处理完context 和 page 都显式关闭避免内存泄漏。如果任务量特别大可以考虑进程级隔离跑完一个任务就重启一个 worker。5. 合规边界与防护者视角自动化的底线5.1 哪些自动化动作不能碰技术本身是中性的但使用方式有边界。我在这个项目里给自己定了几条红线这里也分享给同样在做自动化的朋友。第一不碰验证码对抗。滑块、点选、行为验证这些机制的存在就是为了区分人和机器。你花大力气去研究绕过已经脱离“自动化工具”的范畴变成了一场对抗性攻击。即使短期跑通了平台升级风控之后你的脚本也会瞬间失效前期投入全部白费。第二不碰未授权数据采集。业务需要拿到的数据应该是你的账号本来就能看到的数据。如果你去爬取其他用户的联系方式、交易记录或者把平台上的公开数据批量抓下来用于转售和营销这已经不是技术问题而是合规问题。第三不碰自动化抢单和刷单。我理解做威客的人都希望第一时间看到新订单但“第一时间看到”和“用脚本瞬间抢下”是不同的。前者是提醒后者是破坏平台和其他接单者的公平性。这种自动化一旦被认定被封号只是起步严重的甚至会被追责。网上经常有人问小红书、抖音、淘宝这类平台怎么爬我的态度都是一样的个人学习研究可以理解放进生产项目里收益有限风险极大。5.2 如果你是被爬的一方怎么防护我平时也会写一些 Web 服务站在后端开发的角度爬虫防护其实不复杂核心就是“增加批量操作的摩擦”。比如说对未登录的接口做频率限制对同一个 IP 的并发数做限制页面关键操作加二次验证接口返回中埋一些动态 token让纯静态抓取拿不到稳定数据。前端防源码查看的手段也很多代码混淆、动态加载、请求加密都是常规操作。但这通常是对“防止简单抓取”有效真正有决心的人还是会尝试绕过。所以最好的防护思路不是让页面完全无法抓而是让自动化行为的成本高于人工操作的成本并且做好监控告警一旦发现异常流量特征能快速封禁。理解了防爬方的思路之后你会发现做爬虫自动化的正确姿势就是反过来尊重频率限制不频繁请求只拿自己有权访问的数据遇到验证机制就停下来走人工流程。双方互相尊重这套系统才能跑得久。5.3 后续可以扩展的方向这个闭环跑通之后我还有几个可以继续扩展的方向。一个是接入更丰富的人工审批流比如通过企业微信、飞书机器人推送待确认的任务详情在聊天里点一下按钮就能完成人工确认再自动流转到下一环节。这能把人工判断融入到自动化流程里而不是挡住流程。另一个方向是结合大模型做需求结构化。现在很多需求描述是自然语言长短不一格式混乱。可以尝试把采集到的原始文本发给大模型接口让它提取出交付时间、预算范围、技术要求这些字段再写入任务表。这个过程是在做信息整理不涉及任何平台对抗价值很直接。这些能力同样可以通过节点 API 网关来统一封装。这套框架还能复用到自动化测试里。Playwright 本来就是优秀的自动化测试工具配置好之后跑批处理任务和跑测试用例其实是同一套思路。项目盘点的时候这套东西既保住了接单流程又能当作测试基建一举两得。跑通这套自动化之后我最大的感受是它最有价值的时刻不是看着脚本刷刷刷跑完的那点爽感而是第二天早上看到账单已经自动归好类、未完成任务清单已经列好时的那种踏实。别一上来就想着把整个环节全部自动化挑一个重复程度最高的环节先跑通等它稳定了再慢慢扩。真做下来你就会发现最难的往往不是技术而是克制住什么都想自动化的冲动。
返回列表