☰
基于Playwright和API的接单平台全自动闭环实践
2026/10/6 14:29:16 网站建设 项目流程

去年到今年,我一直在猪八戒这种接单平台上接一些零散的单子。单子本身不难,真正让人崩溃的是那些固定流程:早上起来先刷一遍有没有新需求,看到合适的赶紧报价,报价完等客户回复,接了单又得手动下载素材,改完稿再上传交付,最后还要自己记一笔账,月底对账对到怀疑人生。后来我实在忍不了,花了一个周末把整套流程用 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 和龙虾节点 API

2.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(headless=False) context = browser.new_context() page = context.new_page() page.goto("https://your-platform.example.com/login") input("扫码登录完成后,按回车保存状态...") context.storage_state(path="state.json") browser.close()

之后每次启动脚本,直接加载这个状态文件:

context = browser.new_context(storage_state="state.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(f"div[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(timeout=5000) 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": f"Bearer {API_TOKEN}"}, json={ "event": "order.delivered", "order_id": order_id, "amount": amount, "delivery_url": delivery_url, "occurred_at": "2025-06-14T12:00:00+08:00" }, timeout=10, ) 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": f"Bearer {API_TOKEN}"}, timeout=10, ) 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": f"Bearer {API_TOKEN}"}, json={"amount": amount}, timeout=10, ) create_resp.raise_for_status() return {"duplicated": False, "data": create_resp.json()}

这个模式叫“先查后插”,虽然中间有极小的并发窗口可能重复创建,但对个人项目来说已经足够可靠。如果以后要做得更严谨,可以在 API 侧给order_id加唯一索引,直接在数据库层面挡住重复记录。

订单状态流转表我维护了一份:

状态含义触发动作
created订单创建接单成功
paid客户已付款轮询订单详情
delivered已交付上传交付物后回调
settled已结算客户验收后自动流转
disputed有争议客户发起争议,进入人工

状态表的好处是出了问题能一眼看出卡在哪一环。比如订单一直停在paid没到delivered,说明交付脚本执行失败了,而不是结算环节的问题。

4.3 自动交付与人工复核的配合

有些读者可能担心,交付环节全自动靠谱吗?我的回答是:不能全自动,必须留一个“冷静期”。

我的交付策略是三步走:

  1. Playwright 上传成果文件,填写交付说明。
  2. 调用龙虾 API 标记为“已交付待验收”。
  3. 等待 24 小时后如果没有客户异议,再把状态推为“已确认”,触发结算。

这个 24 小时冷静期看起来降低了自动化程度,实际上大大提升了安全性。因为部分客户会在交付后发现小问题要求修改,如果脚本直接把订单标记为终态,后续退款、争议的处理会非常被动。自动化不能只考虑顺风局,要提前想好逆风局怎么退。

交付说明我也不会千篇一律,而是根据任务类型选模板。比如定制开发类的交付说明,会附上运行命令、环境依赖和测试结果;设计类的交付说明,会附上源文件格式和预览图。模板放在本地 JSON 文件里,按任务标签匹配,改起来也方便。

5. 实操中遇到的坑与排查记录

5.1 元素找不到,选择器失效是最常见的坑

跑了两天之后,我发现脚本报错最多的问题不是登录过期,而是元素定位失败。原因很简单:平台的前端上线了新版,类名和页面结构全部改了。

排查这类问题我有几个固定步骤:

  1. 先用 Trace Viewer 回放失败现场,看报错瞬间页面元素长什么样。
  2. 把选择器从 class 改成文本定位,例如text=立即接单,稳定性高很多。
  3. 如果文本也会变,就加上 CSS 层级定位,从稳定的父容器往下找。
  4. 无论如何不要用完整的绝对路径,比如#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(timeout=5000) 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 的结算清单,几秒钟就完事。当然,中间也出过不少岔子,比如登录态半夜过期导致脚本空跑一晚上,再比如前端改版让选择器集体失效。每次踩坑我都习惯先想想是不是可以在流程设计上规避,而不是简单打补丁。最后再说一个小建议:如果你也打算做类似的接单自动化,第一版别追求全自动,先把“监控-提醒-人工操作”跑顺,等数据积累够了,再一步步把接单、交付、结算替换成自动动作,这样线上风险和开发成本都会小很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询