ECS 自动化爬取 Cookie 服务搭建

ECS 自动化爬取 Cookie 服务搭建
彖渊子一、想法背景
前面做的自动化跟踪表,数据源都是目标网站的业务系统。业务系统是单点登录(SSO)的,登录态(SESSION cookie)隔一阵就过期。原来的链路是:WPS 多维表触发 → Coze 工作流 → 用我的账号密码去抓业务系统数据。一旦 cookie 失效,整条工作流就废了,我得手动去浏览器登一次再把新 cookie 塞回去,非常麻烦,而且账号密码硬编码在节点里也不安全。
于是我换了个思路:单独弄一台云服务器,让它 7×24 小时开着真实浏览器,自动把各基地的登录态维持好,把 cookie 缓存起来。 Coze / WPS 工作流不再自己登录,而是直接来这台服务器”借”现成的 cookie。谁要数据谁就带 token 来取,登录态过期由服务器在后台默默续期。这样登录这件最容易断的事就被”外包”给了常驻服务,自动化链路彻底闭环。
二、项目介绍
本项目部署在云服务器上,并且利用 FastAPI + Playwright + 真实 Chromium,模拟人工完成目标网站 IAM 登录 → 进入应用门户 → 点对应业务系统应用触发 SSO → 提取目标业务域(如 tms.example.com)的会话 cookie,按基地缓存,对外提供 HTTP 接口供 Coze / WPS 工作流调用。
简单说,它干三件事:
- 自动登录多个目标网站基地
每个基地在sites.json里配专属账号,统一以超级账号作回退,确保一定能拿到 cookie。 - cookie 按基地缓存+后台续期
客户端来取时秒回,过期前在后台悄悄重新登录,调用方不会被卡在 16~42 秒的登录里。 - 运单截图落盘返回直链
给定调度单号,自动进业务系统截”运输单元明细”图,存成 PNG 并返回图片 URL,WPS 直接用=IMAGE()写进单元格。
为什么不用“接口逆向”而要用真实浏览器?因为业务系统的 SSO 跳转、风控验证码、应用门户是重前端逻辑,逆向成本极高且极易随版本坏。真实浏览器登录是最稳的,代价是服务器要多花一点内存跑 Chromium(2C2G 够用,靠信号量限制并发)。
三、技术原理
(一)登录态的本质
业务系统的权限来自目标业务域(如 tms.example.com)下的 SESSION / JSESSIONID 等 cookie。只要这个 cookie 有效,拿着它去请求业务数据接口(如 业务数据接口.vue)就能拿到运单数据。所以”爬cookie”的本质就是:用一个能登录的浏览器,把登录成功后浏览器里的这些 cookie 读出来。
(二)为什么需要”常驻 + 缓存”
真实浏览器登录一次要 16~42 秒(冷启动),而且 cookie 会过期。如果每次工作流都现登,Coze 的网关 30 秒就超时掐断了(后面”趟坑”会讲这个坑)。所以服务把 cookie 缓存在内存里,客户端来取时立刻返回旧 cookie,过期由后台线程重新登录换新,调用方完全无感。
(三)截图服务的长轮询
截图比取 cookie 更慢(要进业务系统页面渲染)。为了不被网关超时,设计成:单次调用耗时硬封顶在 wait 秒(默认 8 秒)。
- 命中缓存(15 分钟内截过)→ 毫秒级返回
status:done+image_url - 热态(约 5 秒)→ wait 内完成,一次调用直接拿到 URL
- 冷态(16~42 秒)→ 到点返回
status:pending+job_id,调用方再带job_id轮询
这样任何一次 HTTP 调用耗时都 ≤ wait,网关永远不可能再 504。
四、搭建步骤
下面以一台云服务器为例(公网 IP 用 1.2.3.4 代指,2C2G / 40G ESSD,Ubuntu)。其它云同理。
(一)云服务器准备(装 Docker)
先 SSH 上去:
1 | ssh root@1.2.3.4 |
连不上就先去云厂商控制台「防火墙 / 安全组」放行 22 端口。连上后装 Docker:
1 | # 1. 更新系统包 |
⚠️ 第 3 步后断开 SSH 重连一次让 docker 组生效,再验证第 4 步。
(二)上传项目代码
把本地的 cookie-server/ 目录打包传到服务器:
1 | # 本机电脑执行(不是服务器上) |
回到服务器解压:
1 | cd /root |
💡 如果服务器拉
python:3.11-slim基础镜像很慢(国内常事),可以先在本机docker build成镜像再docker save成 tar 上传离线加载(deploy_offline_image.sh就是干这个的)。本项目 Dockerfile 首行是FROM python:3.11-slim,国内服务器注意:云厂商容器镜像仓库的library/公共命名空间需要授权,直接 build 可能报insufficient_scope,改成可达的镜像源(如docker.m.daocloud.io/library/python:3.11-slim)即可。
(三)配置 .env 与 sites.json
1. 生成强随机 Token
1 | python3 -c "import secrets; print('COOKIE_API_TOKEN=' + secrets.token_urlsafe(32))" |
把输出复制下来,这是生产 token,不要短、不要复用。
2. 编辑 .env
1 | cd /root/cookie-server |
改成(token 换成上一步生成的):
1 | # 生产环境 token(强随机值) |
Ctrl+O → 回车 → Ctrl+X 保存。
3. sites.json(基地与账号)
sites.json 已填好真实账号,千万别提交进 Git(含明文密码)。每个基地配专属账号,另设超级账号作回退:
1 | { |
字段含义:
| 字段 | 说明 |
|---|---|
key |
基地标识(Coze 可填,也可直接填中文 name) |
name |
人类可读名(Coze 输入值就填这个,如 基地A) |
username/password |
IAM 专属账号(来自账号本) |
fallback_username/fallback_password |
回退账号(超级账号),主账号失败时自动用 |
tms_app |
应用门户里要点的业务系统应用代码,是区分基地的关键 |
sso_url |
(可选)固定 SSO 地址,跳过应用发现(售后用) |
select_account |
(可选)IAM 账号选择页要选的子账号(售后用) |
基地靠内部应用门户里的业务系统应用代码区分,不是中文名。售后应用比较特殊:SSO 会先弹账号选择页,所以单独用
sso_url+select_account处理。
(四)构建并启动
1 | cd /root/cookie-server |
看到 Uvicorn running on http://0.0.0.0:8000 就成功了。Ctrl+C 退出日志,服务后台继续跑。
这里我的cookie还服务器自动截图项目。
(五)放行端口 + 外部验证
去云厂商控制台「防火墙 / 安全组」加规则:协议 TCP、端口 8000、策略允许、授权对象 0.0.0.0/0(后续建议收紧到 Coze 出口 IP)。
服务器内部先验证:
1 | curl "http://localhost:8000/health" |
本机电脑验证公网可达:
1 | curl "http://1.2.3.4:8000/health" |
(六)Coze 工作流接线
节点 1(HTTP 请求 · 取 cookie)
- 方法:GET
- URL:
http://1.2.3.4:8000/cookie/{{基地名}}?token=你的TOKEN{{基地名}}就是基地A这种(支持中文名,也兼容 key 如base_a)
- 取返回 JSON 的
cookie字段作为变量auth_cookie
节点 2(HTTP 请求 · 取数据)
- 方法:POST
- URL:
https://tms.example.com:8445/业务数据接口.vue - Headers:
Cookie: {{auth_cookie}},Content-Type: application/json - Body:单号 + 请求签名参数(沿用已有算法)
- 若返回 401/空,可在节点 1 加
&refresh=1强制重登重试
(七)WPS 多维表 / 自动化接线
WPS(金山文档)自动化里,我用一个工作流:事件触发(表格改了某行)→ 执行 AirScript 脚本 → 发送 HTTP 请求。其中”发送 HTTP 请求”节点去调 Coze 工作流。
⚠️ 关键坑:WPS 自动化不能直接出网到 api.coze.cn,需要走金山文档的出站代理(后面趟坑第 5 条详述)。另外 AirScript 写 =IMAGE() 时,要把服务器返回的图片直链(形如 http://1.2.3.4:8000/img/xxx.png)写进去,而不是把整段 JSON 或 Coze 调试页当图片——否则单元格会 #VALUE!。
AirScript数据回写脚本参考
1 | function colLetter(n) { |
(八)开机自启(重要)
确认 docker-compose.yml 里是:
1 | services: |
并让 Docker 开机自启:
1 | systemctl enable docker |
⚠️ 更新代码请
docker compose up -d --build或docker compose restart,**千万别用docker compose down**——down会删掉容器,而restart: always/unless-stopped只能重启”存在的”容器,删了就再也自愈不了(趟坑第 3 条)。
五、报错分析与趟坑记录
这部分是本项目踩过的真坑,按”现象 → 原因 → 解决”记录,省得以后再掉。
1. Coze 返回 522 Timeout(来自 ByteProxy)
现象:工作流偶发 522 Timeout,尤其容器刚重启、要重新登录那次。
原因:实测单次接口耗时 —— 容器冷启动 16~42 秒,浏览器热态约 5 秒。Coze 的出站代理(ByteProxy)超时阈值远小于 42 秒,碰上冷启动那一次就被掐断。服务器其实没挂,/screenshot/{order} 一直 200。
解决:改成”长轮询 + 封顶”。单次 HTTP 调用耗时被 wait 硬卡(默认 8 秒),到点没好就返回 status:pending + job_id,调用方再轮询。另提供极简接口 /shot/url?order=...&wait=20 直接返回一行纯文本 URL,WPS 取值最省事。图片直链 /img/{filename} 无需 token。
2. WPS 写入 #VALUE! 且 AirScript 报 Unexpected token 'const'
现象:脚本跑通了,但 WPS 单元格写进 #VALUE!;有时直接报 Unexpected token 'const'。
原因有两个:
- 一是脚本把 Coze 的
debug_url(一个coze.cn调试页)误当成图片 URL,写成了=IMAGE("https://www.coze.cn/work_flow?..."),自然是非法图片 →#VALUE!。 - 二是 WPS 的 JS 引擎解析不了我新加的复杂正则(转义斜杠
/与|交替、\.、?量词混用),把下一行的const判成意外 token。
解决:
- 把脚本里所有复杂正则改成零正则(用
indexOf/split/charCodeAt)。 - 取图 URL 时排除
coze.cn链接,只认含/img/、/shot、.png这些图 URL,且必须是服务器1.2.3.4的直链。 - 在
main()顶部加上游 Coze 错误码拦截:body.code != null 且 != 0/200就早返回清晰诊断,不再误报”未拿到单号”。 - 加
const的 TDZ 保护(变量先声明再日志)等。
经验:往受限环境(网页终端 / WPS 编辑器)投放代码,新增逻辑尽量用
indexOf/split代替复杂正则,避免多行表达式。
3. 容器被 docker compose down 删掉,再也不自愈
现象:某天所有工作流突然 521(服务器没监听 :8000)。上服务器一看 docker ps -a 空空如也,curl 127.0.0.1:8000/health 无响应。
原因:容器是被 docker compose down 清掉的。restart: always 只负责”容器存在时崩溃重启”,删了就无能为力。而且这台机没有开机自启 docker 的 systemd 配置,重启后连容器都没有。
解决:
- 拉起:
cd /root/cookie-server && docker compose up -d(镜像还在,秒起)。 - 防复发:compose 改
restart: unless-stopped;systemctl enable docker保证 docker 守护进程开机自启;**日常更新用restart/stop,绝不用down**。 - 顺手清理服务器上其它不相关的项目,只留 cookie-server + 截图服务 + 基础设施,降低 OOM 互相挤占。
4. 售后登录失败(验证码 OCR + Playwright 异步报错)
现象:/health 显示 shouhou:false,/refresh/shouhou?wait=1 报 登录未拿到会话 cookie;日志里两种错误交替:
1 | [验证码] OCR 结束: 'p2um' / 'emg' / 'vplm' ... # 全是垃圾 |
原因:
- 验证码 OCR 全错。目标网站的验证码是 4 字符彩色 + 大量干扰线,
login.py的_recognize_captcha先试ddddocr再退pytesseract。如果ddddocr没装好,就退化到 tesseract,基本无解。 - 更诡异的是第二句:源码里完全没有 asyncio,全是同步
def接口,理论上sync_playwright()不该报”在 asyncio 循环里”。高度怀疑容器里跑的代码 ≠ 宿主机/root/cookie-server的代码(镜像是别处 build 的 tar 上传的,可能带一版 async 登录)。
解决(诊断脚本):写了一个一次性诊断 diag_login_root_cause.txt,做四件事:
- 容器
/appvs 宿主机/root/cookie-server逐文件 md5 对比(找DIFF) - 检查
ddddocr/pytesseract/ tesseract 二进制是否可用 - 抓一张真实验证码双引擎识别,并打印 base64 供人工核对
- 触发一次真实登录打全日志
拿到 md5 表就能确认是不是”容器代码滞后”:是的话 docker cp 宿主机 *.py 进容器再重启即可;否则就是 OCR 引擎没装对,进容器 pip install ddddocr 并让识别失败有明确日志。
补充判断:镜像
image_created比所有源码 mtime 都新,重建镜像不改变任何行为,所以这条路不用急着 build;先比对代码再决定。
5. WPS 工作流集体报 context deadline exceeded
现象:所有 WPS 自动化工作流都失败在”发送 HTTP 请求”节点:
1 | 请求失败,状态码:500,响应体: do request err: Post "https://120.92.111.228:19100": context deadline exceeded |
运行耗时 30 / 32 秒(正好顶到节点 30 秒上限)。
原因(实测定位):120.92.111.228:19100 不是我的服务器,它是金山文档(WPS)自己的出站代理 kdocs_proxy(返回 {"kdocs_proxy_err_msg":...})。链路是:WPS 节点 → POST 该代理 {url: api.coze.cn...} → 代理转发到 Coze 工作流。这个代理有主机名白名单,实测 api.coze.cn / 1.2.3.4 / www.baidu.com 全部 not in whitelists。白名单里把 api.coze.cn 移除了 → 经代理的 WPS→Coze 调用整体不通,卡到 WPS 30 秒超时 → 全挂。
解决:
- 找公司 space 管理员 / IT,把
api.coze.cn加回代理白名单,工作流立刻恢复。 - 或者改方案,让”发送 HTTP 请求”节点原生直连
https://api.coze.cn/v1/workflow/run(不填代理),前提是 WPS 能直出外网。 - 顺带提醒:WPS 节点只有 30 秒上限,而服务器冷启动截图要 16~42 秒,所以即便代理修好,冷启动那几次仍可能超时。正确姿势是”提交任务立刻拿 job_id → 第二步轮询”,或把
wait压到 8 秒以内。
六、改进设想
- 验证码彻底自动化。
ddddocr离线打包进镜像 + 识别失败时人工兜底(把验证码图推到钉钉/邮箱让人填一次),把”风控验证码”这唯一会断的点也闭环。 - 安全加固。加 HTTPS 反代(Caddy + Let’s Encrypt)+ Coze 出口 IP 白名单,避免 token 明文传输、避免被扫。
- 监控告警。cookie 连续续期失败时主动推钉钉告警,而不是等工作流挂了才发现。
- 多机容灾。目前 2C2G 单机,Chromium + 并发登录有 OOM 风险(内存曾掉到 288MB 逼近暂停阈值)。后续可拆 cookie 服务与截图服务,或加 Swap / 限制并发。
- 对外开放成标准接口。截图直链 + cookie 接口已经很通用,后续可以把更多业务表单(在途确认、要求到达时间等)都接进来,让跟踪表所有字段全自动闭环。
如果搭建过程遇到上面没提到的坑,欢迎在评论区交流,看到会回。




















