ARTICLE DETAIL

资讯详情

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

基于Playwright和API的接单平台全自动闭环实践

基于Playwright和API的接单平台全自动闭环实践 去年到今年我一直在猪八戒这种接单平台上接一些零散的单子。单子本身不难真正让人崩溃的是那些固定流程早上起来先刷一遍有没有新需求看到合适的赶紧报价报价完等客户回复接了单又得手动下载素材改完稿再上传交付最后还要自己记一笔账月底对账对到怀疑人生。后来我实在忍不了花了一个周末把整套流程用 Playwright 串了起来再用一个第三方任务回调 API也就是标题里说的龙虾节点 API来负责状态回传和结算对账最终跑通了一个从“发现需求”到“结算入账”的全自动闭环。这个项目不是什么高并发、高可用的平台系统就是给接单的个体户自己做的一个生产力工具。如果你也在接单平台上消耗大量时间做重复操作或者想把自己的工作流自动化这篇文章应该对你有用。下面我会按需求拆解、技术选型、核心实现、排查技巧的顺序来写尽量把踩过的坑和能直接复用的代码都保留下来。1. 先想清楚这个自动化项目的边界在哪里1.1 表面的需求是“少点几次鼠标”实际的需求是“把流程做成状态机”我一开始的想法很简单写个脚本代替我盯着需求列表有新单子就提醒我。但真动手之后发现单是“提醒”还不够。接单后的动作链很长每个环节都散落在不同界面里自动化的收益远远不止省几次点击。我把自己的日常工作流拆了一张表环节手动操作内容重复频率找单刷新需求大厅筛选预算和标签每天十几次报价填写报价金额、周期、留言每天三五次接单确认接单下载附件沟通需求每单一次执行本地处理文件可能用到第三方工具每单一次交付上传成果写交付说明每单一次结算确认到账记录订单金额、平台抽佣每单一次对账月底拉账单核对收入每月一次真正耗时的其实不是“点鼠标”本身而是这些动作之间的状态切换。比如我不知道客户有没有付款就要反复刷新订单详情页不确定有没有提交成功就反复打开交付记录。手动操作时这些事很琐碎但落到代码里就非常清晰每个订单就是一个状态机从created到paid再到delivered最后到settled。自动化的第一步不是写脚本而是把整个流程抽象成状态流转。所以我给这个项目定了四层结构采集层Playwright 打开浏览器定时抓取任务列表和订单状态。决策层根据过滤器判断哪些任务值得接哪些订单需要执行交付。执行层调用本地工具完成任务或者自动上传交付物。结算层把交付完成的事件推送给龙虾节点 API由它创建结算记录、同步对账数据。这套结构的好处是每一层都能独立测试。采集层挂了不影响结算层结算接口改了我也不用重新跑浏览器。1.2 “接单-交付-结算”三段分别用什么手段实现明确了边界后每个环节用什么工具就很好选了。接单段交给 Playwright。因为接单平台基本是动态渲染页面用 Requests 直接拿不到渲染后的 DOM而 Playwright 启动的是一个真实浏览器能执行 JavaScript能点击按钮能上传文件行为上跟真人操作最接近。交付段由 Python 完成一部分Playwright 完成另一部分。文件重命名、格式转换、压缩打包这些用本地 Python 脚本处理安全可控上传交付物、填写交付说明、发送站内信这些必须操作页面的动作交给 Playwright。结算段交给龙虾节点 API。这个 API 在我的项目里承担的是业务状态中枢不是直接抓数据的爬虫接口。它有事件回调、状态持久化和对账查询三个能力。当浏览器这边把订单标记为已交付我就调用这个 API 创建一条交付事件之后 API 会根据我配置的规则把订单状态推到已结算然后我再定期拉取结算列表跟平台账单核对。这样浏览器自动化部分和账目部分就彻底解耦了脚本崩了也不会影响已经生成的结算记录。2. 技术选型为什么是 Playwright 和龙虾节点 API2.1 Playwright 比 Requests 和 Selenium 更适合这个场景不少朋友问过我爬这种平台为什么不用 requests 直接调接口或者用更成熟的 Selenium。我先说说对比结果再解释为什么最后选了 Playwright。对比维度RequestsSeleniumPlaywright环境依赖无浏览器依赖需要对应浏览器驱动自带浏览器内核安装命令动态页面支持不支持需找接口支持支持等待机制手写轮询显式等待较繁琐自带等待策略定位时自动等待运行速度最快较慢比 Selenium 快支持异步并发调试体验无页面截图、录制一般自带 Trace Viewer可回放操作稳定性依赖接口参数驱动版本容易不匹配API 设计统一维护成本低我在决定之前也试过直接用 Requests 抓接口。问题在于平台页面的很多数据是前端异步加载的个别接口头里带签名参数手工模拟了一部分但稍微改个版本就失效维护成本太高。Selenium 的问题则是老牌但笨重元素等待和浏览器驱动管理都比较繁琐写起来不够干净。Playwright 最打动我的三点内置等待机制。click()之前它会把自动等待元素可点击不用自己写time.sleep()。有 Trace Viewer。脚本跑挂了能把整个操作过程录下来复盘时能看到每一步的 DOM 快照。一个 API 走天下。Python、Node.js、Java 都有同样风格的库以后换语言写也不用重学。当然Playwright 也不是银弹。它本质还是驱动一个真实浏览器资源占用比 Requests 高并发跑太多实例容易把机器拖垮。我的做法是一次最多开两个浏览器实例一个跑任务监控一个跑交付操作再多了就排队。2.2 龙虾节点 API 在这个项目里的真实角色很多爬虫脚本的问题在于只解决了“拿数据”这一步后续的业务动作还是靠人手工衔接。我这次特意把结算部分单独拿出来用龙虾节点 API 来做状态中枢。简单说这个 API 帮我承担了三件事事件回调。当 Playwright 完成交付动作后我用一条 HTTP 请求把这个订单的交付事件推给 API内容包括订单号、金额、交付链接。API 那边收到事件后会按我设定好的规则流转状态。状态持久化。平台页面上的订单状态只能实时看关了页面就没了。龙虾 API 会把每次状态变更记录在云端我随时能查某个订单当前到哪一步了。对账输出。每天定时拉取一次“已结算”清单和平台后台账单做比对发现金额对不上就报警。以前月结对到眼睛疼的活现在一条命令就出结果。有一点容易误解这里的“节点”指的是业务流程里的节点也就是我在云端配置的一个服务端点和任何网络通道、代理服务都没有关系。它的本质就是一个跑在云端、开放 HTTP 接口的业务服务我用 Token 鉴权访问。2.3 环境搭建与最小项目结构整个开发环境非常普通Python 3.10 Playwright 1.4x。安装步骤也很简单python -m venv .venv source .venv/bin/activate pip install playwright playwright install chromium如果网络环境下载浏览器内核比较慢可以把playwright install换成带--with-deps的版本它会一并把系统依赖装好省得启动时报缺库。项目的目录结构我保持了最小化auto_order/ ├── main.py # 入口调度各模块 ├── browser.py # Playwright 浏览器生命周期 ├── collector.py # 任务列表采集与筛选 ├── executor.py # 接单和交付动作 ├── billing.py # 龙虾节点 API 对接 ├── config.yaml # 过滤规则、接口地址、Token └── state.json # 登录状态存储我刻意没有用复杂的框架因为脚本的核心逻辑是“顺序执行 异常兜底”用模块文件比用类继承更好理解。config.yaml 里只放非敏感配置Token 和账号密码用环境变量注入避免代码泄露。3. 自动接单的核心实现细节3.1 用 storage_state 保持登录状态避免每次扫码接单平台最烦的就是登录态过期。以前手动操作的时候一个月要重新扫码登录好几次脚本自动化之后这个问题更明显总不能每次跑脚本都让人工扫码。Playwright 提供了一个非常方便的能力storage_state。你可以先把浏览器以有头模式打开人工完成扫码登录然后把整个上下文状态保存到本地 JSON 文件。核心代码长这样from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) context browser.new_context() page context.new_page() page.goto(https://your-platform.example.com/login) input(扫码登录完成后按回车保存状态...) context.storage_state(pathstate.json) browser.close()之后每次启动脚本直接加载这个状态文件context browser.new_context(storage_statestate.json) page context.new_page()storage_state会保存 cookies、localStorage 和 IndexedDB等于把整个登录态快照下来比单独复制 cookie 要完整得多。这个方案在大部分网站上都能复现我后面接别的平台也是用这套代码只改登录地址就行。要注意的一点是登录态通常有有效期可能三天、可能一周过期后请求会在页面上弹回登录页。所以流程里要加一个检测逻辑看到跳转到登录页就立刻触发提醒让操作人回来扫码重登一次。3.2 任务列表的抓取与条件筛选登录后就是抓列表。Playwright 里最常用的定位方式是query_selector_all配合等待page.goto(https://your-platform.example.com/task/list) page.wait_for_selector(.task-item) task_list page.query_selector_all(.task-item) for task in task_list: title task.query_selector(.task-title).inner_text() price_text task.query_selector(.task-price).inner_text() tags [t.inner_text() for t in task.query_selector_all(.tag)] # 判断是否符合接单条件这里有个细节列表页是分页加载的直接遍历一页往往不够。我一般会在拿到一页数据后先判断有没有“下一页”按钮有就继续点没有就退出。筛选逻辑是纯 Python 实现的最简单直观。我会在 config.yaml 里维护三张表filters: min_price: 300 max_price: 5000 include_tags: [Python, 爬虫, 自动化] exclude_words: [急聘, 长期合作, 价格面议] exclude_categories: [平面设计, 配音]每次拿到任务标题和标签后先做关键词排除再做价格区间过滤全部通过才进入待接单队列。这套配置化设计非常实用不同时期的接单策略不同我只改 YAML不改逻辑代码。然后把筛选后的任务展示到一个本地队列也就是一个 JSON 数组文件。脚本每天运行前先读这个队列避免重启后把已经处理过的任务再处理一遍。3.3 自动接单时的防重复与状态确认自动接单是整个项目里最需要谨慎的环节。我的原则是对于平台明确区分“报价”和“直接接单”两种模式能直接接的不做多余动作必须报价的只提交一次报价不做恶意刷单。接单部分我用了一个很关键的检查逻辑在执行点击动作之前先判断任务当前状态。只有状态是“可接/待报价”的任务才允许点击已经接过的任务直接用任务 ID 去重跳过。accepted_ids set() def try_accept(page, order_id: str) - bool: if order_id in accepted_ids: return False # 找到对应任务卡片 card page.locator(fdiv[data-order-id{order_id}]) if card.count() 0: return False # 点击接单按钮前先确认状态文本 status card.locator(.status-text).inner_text() if 已接单 in status or 已报价 in status: accepted_ids.add(order_id) return False card.locator(button:has-text(接单)).click() # 等待弹窗出现再确认而不是直接点击 dialog page.locator(.dialog-confirm) dialog.wait_for(timeout5000) dialog.locator(button:has-text(确认)).click() accepted_ids.add(order_id) return True这里我特意没用page.click()而是先定位按钮再等待弹窗原因很简单接单平台经常有二次确认弹窗如果直接点按钮不处理弹窗动作大概率失败。弹窗的等待时间我设置为 5 秒超过就说明页面没有响应此时应该放弃并记录异常而不是继续盲点。还有一点非常重要每次成功接单后一定要等待页面跳转或出现成功提示再处理下一条。否则浏览器操作过快平台风控很容易盯上你。我希望脚本跑得慢一点而不是跑得猛一点。4. 结算闭环怎么用 API 串起来4.1 回调接口的设计思路接单、交付都自动化了最后一步结算如果还靠人工记账闭环就不完整。所以我在交付完成后会主动向龙虾节点 API 推一条交付事件。接口定义我设计得比较简单import requests API_BASE https://lobster-api.example.com/v1 API_TOKEN your_token_here def report_delivery(order_id: str, amount: float, delivery_url: str): resp requests.post( f{API_BASE}/events, headers{Authorization: fBearer {API_TOKEN}}, json{ event: order.delivered, order_id: order_id, amount: amount, delivery_url: delivery_url, occurred_at: 2025-06-14T12:00:0008:00 }, timeout10, ) resp.raise_for_status() return resp.json()推送事件是结算链路的触发器。API 收到后会做两件事先把订单状态从delivered流转到settled再生成一条结算明细包含订单号、金额、时间和平台。这样我不用在本地维护账本任何一台机器上都能查到所有结算记录。我特意把时间字段也传过去而且用 ISO 8601 格式带时区主要是为了避免服务器时区不一致导致对账偏差。踩过一次坑之后我就老实了时间相关的数据永远用标准时间格式传绝不让双方各自格式化。4.2 用唯一订单号保证结算幂等避免重复入账自动化的流程跑久了什么怪事都能遇到。最典型的就是网络超时Playwright 那边交付其实已经成功了但回调请求超时脚本重新上报了一次。如果 API 不做幂等处理同一笔单子就会被结算两次。解决思路很直接用订单号做唯一约束。结算接口在创建结算单之前先查一下这个订单号是否已经存在存在就直接返回已有的结算单不再重复创建。def settle_order(order_id: str, amount: float): # 第一步查询是否已存在结算单 query_resp requests.get( f{API_BASE}/orders/{order_id}/settlement, headers{Authorization: fBearer {API_TOKEN}}, timeout10, ) if query_resp.status_code 200 and query_resp.json().get(settled): return {duplicated: True, data: query_resp.json()} # 第二步不存在则创建 create_resp requests.post( f{API_BASE}/orders/{order_id}/settle, headers{Authorization: fBearer {API_TOKEN}}, json{amount: amount}, timeout10, ) create_resp.raise_for_status() return {duplicated: False, data: create_resp.json()}这个模式叫“先查后插”虽然中间有极小的并发窗口可能重复创建但对个人项目来说已经足够可靠。如果以后要做得更严谨可以在 API 侧给order_id加唯一索引直接在数据库层面挡住重复记录。订单状态流转表我维护了一份状态含义触发动作created订单创建接单成功paid客户已付款轮询订单详情delivered已交付上传交付物后回调settled已结算客户验收后自动流转disputed有争议客户发起争议进入人工状态表的好处是出了问题能一眼看出卡在哪一环。比如订单一直停在paid没到delivered说明交付脚本执行失败了而不是结算环节的问题。4.3 自动交付与人工复核的配合有些读者可能担心交付环节全自动靠谱吗我的回答是不能全自动必须留一个“冷静期”。我的交付策略是三步走Playwright 上传成果文件填写交付说明。调用龙虾 API 标记为“已交付待验收”。等待 24 小时后如果没有客户异议再把状态推为“已确认”触发结算。这个 24 小时冷静期看起来降低了自动化程度实际上大大提升了安全性。因为部分客户会在交付后发现小问题要求修改如果脚本直接把订单标记为终态后续退款、争议的处理会非常被动。自动化不能只考虑顺风局要提前想好逆风局怎么退。交付说明我也不会千篇一律而是根据任务类型选模板。比如定制开发类的交付说明会附上运行命令、环境依赖和测试结果设计类的交付说明会附上源文件格式和预览图。模板放在本地 JSON 文件里按任务标签匹配改起来也方便。5. 实操中遇到的坑与排查记录5.1 元素找不到选择器失效是最常见的坑跑了两天之后我发现脚本报错最多的问题不是登录过期而是元素定位失败。原因很简单平台的前端上线了新版类名和页面结构全部改了。排查这类问题我有几个固定步骤先用 Trace Viewer 回放失败现场看报错瞬间页面元素长什么样。把选择器从 class 改成文本定位例如text立即接单稳定性高很多。如果文本也会变就加上 CSS 层级定位从稳定的父容器往下找。无论如何不要用完整的绝对路径比如#root div div div button稍微改一个层级就废了。文本定位的代码很简单page.locator(button, has_text立即接单).click()5.2 验证码与平台风控自动化操作接单平台本身就容易触发风控。我的经验是一旦页面弹验证码就让脚本立刻停下来不要试着自动识别或绕过弹通知让人工来处理。这里没有捷径可走。验证码的识别模型再准本质是和一个持续升级的安全系统对抗个人开发者不可能一直跟进。与其把时间耗在这上面不如老老实实接受偶尔的人工介入。我还给脚本加了一套“频率控制”每次接单和交付操作之间至少间隔 15 秒每天处理单量上限设为 20 单。虽然速度慢但连续跑了一个月没有被限制登录整体收益远大于那些跑两天就死掉的激进方案。5.3 网络超时和偶发连接失败浏览器自动化有个很现实的问题goto()偶尔会因为网络波动直接抛超时异常。解决办法有两个第一个是设置全局默认超时我一般设 30 秒page.set_default_timeout(30000)第二个是给关键步骤加重试。比如点击接单按钮后如果确认弹窗没有出现就重试点击一次最多三次for attempt in range(3): try: card.locator(button:has-text(接单)).click() dialog page.locator(.dialog-confirm) dialog.wait_for(timeout5000) break except Exception: if attempt 2: raise page.wait_for_timeout(2000)注意重试要等 2 秒再执行不能让脚本在浏览器里疯狂点同一个按钮那样只会加重页面卡顿甚至触发风控。5.4 常见问题速查表症状可能原因处理优先级页面加载到登录页storage_state 过期高需要重新扫码登录元素定位超时前端改版/异步加载慢中检查选择器接单按钮点击无反应有弹窗遮挡/状态不可接中确认页面状态回调接口超时网络问题/API 服务不可用高重试上报结算金额对不上重复结算/回调丢失高查订单号去重脚本无故崩溃浏览器进程残留低启动前清理旧进程6. 接入这套系统前必须想清楚的合规边界6.1 自动化可以但不要踩平台规则红线写这个项目的初衷是为了省时间不是为了钻平台空子。接单平台对自动报价、自动刷单通常都有明确限制尤其是高频自动化操作很容易被判定为恶意行为。我的做法是只做低频率的辅助操作同时保留人工确认决策的环节绝不把“无脑批量接单”做成一键刷单工具。如果你所在的接单平台提供官方 API 或者招募类服务应该优先使用官方渠道接入而不是用浏览器自动化硬怼。自动化解决的是重复劳动问题不应该变成和平台规则对抗的工具。动手前先想清楚这一点整个项目的技术选择都会不一样。6.2 数据采集只取必要字段不碰敏感信息任务列表抓取时我只保存任务标题、价格、标签、发布人昵称这些业务字段。不采集用户的手机号、微信号、身份证等隐私信息也不需要采集这些才能完成业务。理由很朴素拿到的数据越少出事时风险越小。本地存储做好权限控制配置文件里不写明文密码和 Token。我在config.yaml里保存的是过滤规则账号密码和 API Token 一律走环境变量防止代码仓库泄露。6.3 这套系统的后续扩展方向闭环跑通之后我发现它的架构可以平移到很多场景多平台接单助理把 Playwright 采集和龙虾 API 回调抽出来加一个平台适配层就能对接多个接单平台。订单看板龙虾 API 已经保存了所有订单状态可以再做一个小仪表盘按金额、状态、客户维度汇总。定时执行与异常告警用系统自带的定时任务去跑脚本异常时通过推送服务把错误信息发到手机上。后续每加一个功能我都会先回到状态流转这张表上看一看确认新功能是在补全状态机还是在给某个流程打补丁。只要状态流转清晰这个系统就能一直稳定跑下去。这套系统我前前后后跑了快三个月最大的体会是自动化最值钱的地方不是省了“点击”那一下而是把每个订单的来龙去脉全记下来了。以前最怕月底对账一笔一笔翻聊天记录和订单详情现在直接拉 API 的结算清单几秒钟就完事。当然中间也出过不少岔子比如登录态半夜过期导致脚本空跑一晚上再比如前端改版让选择器集体失效。每次踩坑我都习惯先想想是不是可以在流程设计上规避而不是简单打补丁。最后再说一个小建议如果你也打算做类似的接单自动化第一版别追求全自动先把“监控-提醒-人工操作”跑顺等数据积累够了再一步步把接单、交付、结算替换成自动动作这样线上风险和开发成本都会小很多。
返回列表