深圳船信息查询(中外运开舱表 + 长荣订舱追踪 + 蛇口招商ePort + 易物流盐田 156yt.cn)
🚪 0 号铁律:需要登录的渠道,先查凭证,缺失必须先向用户索取(禁止直接跑查询)
中外运(腾讯文档)和长荣订舱无需登录、直接跑;但盐田(156yt)和蛇口(招商 ePort)必须账号密码。 对这两个渠道,第一次执行查询前,必须先跑凭证检查,凭证没配齐就先停下来问用户要账号密码,绝不允许直接跑 query_vessel.py / shekou_* 然后碰上报错。
每个需要登录的查询任务,开局第一步:
PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe"
# 自动定位本技能 scripts 目录:用户级 + 工作区级技能目录都找一遍,装成任何文件夹名都能命中(勿写死目录名)
SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)"
"$PY" "$SK/setup_credentials.py" --check yantian # 盐田任务用
"$PY" "$SK/setup_credentials.py" --check shekou # 蛇口任务用
"$PY" "$SK/setup_credentials.py" --check all # 或一次查两个
- 退出码 0(
READY:...) → 凭证已配齐,继续正常查询。 - 退出码 2(
MISSING:...) → 立即停止,不要执行任何查询脚本,用一句话向用户明确索取,例如:
> "查询盐田/蛇口船期需要该平台的登录账号和密码(技能不内置)。请提供 账号和密码,我会保存到本地,之后无需再提供。"
拿到账号+密码后,立刻运行setup_credentials.py(交互式直接回车输入,或--yantian-user/--yantian-pass、--shekou-user/--shekou-pass)保存,再继续查询。 - 账号和密码都要问(脱敏发布版没有任何默认账号,不能假设用户名)。
- 严禁:不检查就直接跑查询脚本;报错后只把脚本错误丢给用户;猜测或留空账号密码;把账号密码写进 SKILL.md/日志/记忆。
- 用户在对话中直接给出账号或密码时,当场保存到对应
secrets/*.json,不要用完即弃。
> 中外运、长荣两个渠道免登录,不受此条约束。
⚠️ 渠道路由铁律:指定渠道,禁止跨渠道补查
四类来源各管一段,用户已明确指定渠道时,只跑该渠道对应的脚本,绝不要拿到结果后再去别的渠道"顺手补查":
- 用户给长荣订舱单号 / Evergreen / Booking No.(如
@skill 长荣订舱单号 149607870869)→ 只跑evergreen_booking.py。即使查到了船名航次,也不要再拿船名去中外运表、蛇口、盐田跨查。长荣订舱本身已输出船名航次+结关日期+开航日,即为该渠道的完整答案。 - 用户给船名问"开舱/开舱日期/LTD/开船/中外运/外运/Sinotrans" → 只跑
sinotrans_schedule.py。 - 用户问蛇口 / SCT / CCT / 招商 ePort / eport.cmp1872 的航次/ETB/ETD/船代 → 只跑
shekou_*(CDP)。 - 用户问盐田 / 156yt / 易物流盐田 的航次/闸口/ETB/ETD/船代 → 只跑
query_vessel.py。
判定原则:以用户措辞里的渠道关键词为准(订舱单号→长荣;开舱/开船/LTD→中外运;蛇口/招商→蛇口;盐田/156yt→盐田)。命中哪个渠道就只交付哪个渠道的结果。
只有在用户没有指定渠道、或明确要求"多渠道/全部/顺便看看其他港口时间"时,才允许跨渠道聚合。 跨查会拖慢响应、引入无关渠道的空结果(如长荣订舱的船在中外运表里查不到),属冗余动作。
若单一渠道查无结果,直接如实告知"该渠道无记录",不要自动换渠道兜底;可在结尾一句话提示"如需 XX 渠道的靠离泊时间可再查",由用户决定,不擅自执行。
🔑 凭证初始化(盐田 + 蛇口,首次必做一次,之后自动读取)
盐田(156yt.cn)和蛇口招商 ePort 都要账号密码。两个平台的凭证统一保存在技能目录本地凭证文件,用户只需在首次使用(或密码变更)时提供一次,之后所有查询/重登脚本自动读取,无需每次再问。
- 凭证文件(本地文件、勿提交/外传/写入记忆或文档正文/截图):
secrets/yantian.json→{"user":"<盐田账号>","pass":"..."}(盐田 156yt.cn,账号由使用方提供)secrets/shekou.json→{"user":"<蛇口账号>","pass":"..."}(蛇口招商 ePort,账号由使用方提供)- 两个平台的账号、密码均不内置、不随技能分发,使用方首次运行
setup_credentials.py时填入自己的账号。 - 初始化/更新凭证(推荐,交互式不回显密码):
bash PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe" # 自动定位本技能 scripts 目录:用户级 + 工作区级技能目录都找一遍,装成任何文件夹名都能命中(勿写死目录名) SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)" "$PY" "$SK/setup_credentials.py" # 交互式,依次提示输入盐田、蛇口密码 "$PY" "$SK/setup_credentials.py" --status # 查看两个平台是否已配置(不打印密码)
也可参数式:setup_credentials.py --yantian-pass 'xxx' --shekou-pass 'yyy'(用户名用--yantian-user/--shekou-user覆盖)。 - Agent 行为约定:
1. 首次需要登录某平台、且对应凭证文件缺失密码时(setup_credentials.py --status显示"未配置"),主动向用户索取该平台密码,拿到后立即用setup_credentials.py保存,再继续查询。不要猜密码。
2. 用户在对话里直接给了密码(如"盐田密码是 xxx"),当场保存到对应凭证文件(跑 setup 脚本或写 secrets json),不要只在当次命令里用完就丢。
3. 凭证优先级:命令行参数 > 环境变量(盐田YT_USER/YT_PASSWORD、蛇口SHEKOU_PASS)> 本地凭证文件。凭证文件是默认长期来源。
4. 密码属敏感信息,绝不写入 SKILL.md、日志、记忆文件或回复正文;只落secrets/*.json。
中外运 Sinotrans 开舱/船期表(腾讯文档在线表格,纯 HTTP 无需登录)
中外运(Sinotrans)各航线开舱时间 / LTD 开船日期维护在腾讯文档在线表格:
https://docs.qq.com/sheet/DZG5YdGVxUHN6SkJE,含 6 个标签页:000002=美加航线、000003=欧地航线、000004=亚太航线、000005=拉非航线、000006=东南亚航线(000001=驳船操作时间,格式不同,已跳过)。脚本一次拉取全部 5 个开舱航线标签页、跨表匹配,按船名(+航次)查,自动命中该船所在航线,输出 船名航次、航线航次(SVVD)、POL 港口、LTD/ETD 开船日期、开舱日期、所属航线表。
脚本 scripts/sinotrans_schedule.py 仅用 Python 标准库(urllib + zlib + struct),匿名读取、无需登录、无需浏览器、无验证码。表格以腾讯文档自定义 protobuf 二进制下发,脚本内部解码(船名/航次/POL 为字符串池索引,日期为 Excel 序列号 double)。列位置不写死:按表头关键词 SVVD 自动定位航次列,船名=SVVD-1、开船日期=SVVD+1、正常开舱=SVVD+2、提前开舱=SVVD+3,POL 按表头"POL"定位,兼容各航线不同布局。
PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe"
# 自动定位本技能 scripts 目录:用户级 + 工作区级技能目录都找一遍,装成任何文件夹名都能命中(勿写死目录名)
SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)"
"$PY" "$SK/sinotrans_schedule.py" --ship "OOCL VALENCIA" # 跨全部航线模糊匹配船名
"$PY" "$SK/sinotrans_schedule.py" --ship "ALS VESTA" # 同名多航次会全部列出
"$PY" "$SK/sinotrans_schedule.py" --ship "EVER META" --voy 005E # 船名+航次
"$PY" "$SK/sinotrans_schedule.py" --ship "CMA CGM DIGNITY" --json
"$PY" "$SK/sinotrans_schedule.py" --list # 列出全部航线全部船期(~730 条)
"$PY" "$SK/sinotrans_schedule.py" --ship "OOCL SPAIN" --refresh # 强制重新下载
缓存与自动刷新(业务约定):全部航线结果缓存到 scripts/../cache/sinotrans_schedule.json。
- 本地缓存存在且未超过 12 小时(半天) → 直接读缓存,不重新下载。
- 按船名查不到、且缓存已超过半天 → 脚本自动重新下载最新表格后再匹配一次。
- --refresh 可随时强制重下(会拉 5 个标签页)。
输出字段:船名航次(如 OOCL VALENCIA 011W)、航线航次/SVVD(如 AEU1-TPJ-011 W)、港口 POL(YANTIAN/SHEKOU/NANSHA 等)、LTD 开船日期、开舱日期(正常开舱优先,该航线只有提前开舱列时取提前开舱),多航次/多港口命中会逐条列出。
工作原理(失效时排障):
1. 数据接口 GET https://docs.qq.com/dop-api/opendoc?id=DZG5YdGVxUHN6SkJE&tab=<tab>&...&callback=clientVarsCallback(必须带 Referer: https://docs.qq.com/sheet/...?tab=<tab>,否则 401),返回 JSONP,剥 clientVarsCallback(...) 得 JSON。逐 tab 拉取(先拉一个拿 header 里的全部标签页名)。
2. 路径:clientVars.collab_client_vars.initialAttributedText.text[0].block_datas[0].related_sheet 是 base64+zlib 的 protobuf。
3. protobuf 结构(腾讯 smartsheet):sheet → 网格消息(field5 第3个) → field19;field19 内 field5 同时含字符串池(重复 field1 子消息,其 field1=文本)与日期值(重复 field3 子消息,内嵌 field1 = fixed64 double,为 Excel 日期序列号,基准 1899-12-30);field19 的 field6 是单元格记录(field1=行、field2=列、field3=单元格值)。
4. 单元格值:cell field1=类型(2=日期,4/6=文本);文本索引在 cell 的 field2/field4 嵌套消息 field1(指向字符串池);日期单元格本身不含数值,日期型单元格(type=2)按网格存储顺序与 field5 里 field3 日期 double 一一对应(按顺序分配)。
5. 列布局因航线而异:欧地线船名在 SVVD 左侧第 1 列、开船日期在 SVVD 右侧第 1 列;美加/亚太/拉非/东南亚表头为 POL | Line | Vessel&Voyage 船名 | SVVD | ETD 开船日期 | Origin CY open 正常开舱 | Early CY open 提前开舱。统一规律:船名=SVVD列-1、开船日期=SVVD列+1、正常开舱=+2、提前开舱=+3,故按表头定位 SVVD 列即可通用。数据行判定:开船日期列必须是合法 YYYY-MM-DD(自动滤掉顶部说明行/表头/BLANK 行)。POL 有合并单元格,缺省沿用上一行港口。
- 若腾讯改 protobuf 结构导致某 tab 解码为空:脚本会跳过该 tab 并在 stderr 提示,不影响其它航线;用 --list 看各航线条数,接口 401 多为缺 Referer。
用途
在易物流盐田平台(https://www.156yt.cn,盐田国际集装箱码头公共信息平台)按船名查询船期,输出:航次(码头航次)、船名、闸口、ETB(预计停靠/靠泊)、ETD(预计离港)、船代。
凭据
- 凭证已纳入技能统一管理,见上文 「🔑 凭证初始化」:保存在
secrets/yantian.json(用户名、密码由使用方首次初始化填入)。 - 首次使用若
setup_credentials.py --status显示盐田"未配置",向用户索取密码并立即用setup_credentials.py保存;之后query_vessel.py自动读取,无需再传。 - 密码属敏感信息,禁止写入本技能文件、日志或记忆;凭证优先级:
--password> 环境变量YT_PASSWORD>secrets/yantian.json。
快速使用(优先方式:直接跑脚本)
脚本 scripts/query_vessel.py 仅依赖 Python 标准库(urllib),无需浏览器、无需第三方包,是本技能的首选执行方式。凭证已配置后,无需再传 --user/--password:
PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe"
SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)"
"$PY" "$SK/query_vessel.py" --ship "APL MERLION" # 自动读 secrets/yantian.json
# 多艘船(英文逗号分隔)
"$PY" "$SK/query_vessel.py" --ship "APL MERLION, MSC XXX"
# JSON 输出(便于后续处理)
"$PY" "$SK/query_vessel.py" --ship "APL MERLION" --json
# 指定起始日期(yyyymmdd,默认取当天往前约一年)
"$PY" "$SK/query_vessel.py" --ship "APL MERLION" --etb 20260101
# 也可临时覆盖:--user <盐田账号> --password '<密码>',或环境变量 YT_USER / YT_PASSWORD
文本输出格式(每船一行):
航次:<航次> | 船名:<船名> | 闸口:<闸口> | ETB:<YYYY-MM-DD> | ETD:<YYYY-MM-DD> | 船代:<船代>
无记录的船名会返回 船代:无记录,不要当作报错。
Python 路径(Windows 托管环境)
使用托管 Python:C:\Users\Administrator\.workbuddy\binaries\python\versions\3.13.12\python.exe。
返回给用户的结果格式
以表格呈现,列:航次 | 船名 | 闸口 | ETB 时间 | ETD 时间 | 船代。多艘船合并成一张表;无记录的船名单独标注"无记录"。
工作原理(脚本失效时的排障/手动复现)
站点为 GBK 编码的 JSP 老系统,登录与查询接口如下(详见 references/api_notes.md):
- 建立会话:先 GET
https://www.156yt.cn/与https://www.156yt.cn/passport/拿到JSESSIONID(必须,否则登录接口返回空)。 - 登录:POST
/passport/login!verify,表单j_username/j_password(明文,无验证码)。成功返回 JSON{"result":1,"redirectUrl":".../member/index.action?loginVerifyCode=..."};失败返回result:2+loginMessege:"用户名或密码错误"。 - 从
redirectUrl解析loginVerifyCode,GET 该地址建立会员会话。 - 查询:先 GET
pqs_revision/pages/jsp/voyQuery.jsp?loginVerifyCode=<code>,再 POSTpqs_revision/pages/jsp/voyQuery.jsp?modify=query&loginVerifyCode=<code>,表单字段etb_time(起始日期 yyyymmdd)、ship_name(船名)、voyage_code(航次,留空)、Submit1=查询。 - 响应为 GBK 编码,用 GBK 解码后解析结果表格。列顺序:码头航次 | 船名 | 闸口 | 预计停靠ETB | 预计离港ETD | 船代。
关键坑(务必记住)
- 不要给请求加
Accept头:本站对 Accept 头敏感,携带后登录/查询接口会返回空响应体(HTTP 200 但 body 长度 0)。只设 User-Agent。 - 顶层浏览器导航到
voyQuery.jsp会被重定向到/publicInfoService/index.action;脚本用同源 HTTP 会话直接 POST 接口可绕过。若只能用浏览器(agent-browser),需在已登录页面上下文用fetch(...,{credentials:'include'})调接口,并用new TextDecoder('gbk')解码。 - 结果页 GBK 编码,直接 UTF-8 解码会乱码。
- 浏览器自动化环境(Windows)备选方案见
references/api_notes.md。
蛇口港(招商ePort eport.cmp1872.com)—— CDP 全自动(登录含验证码,已跑通)
蛇口/招商局港口平台(账号由使用方自有,运行时经 setup_credentials.py 配置;登录有文字点选验证码)是 Vue + iView + vxe-table 的 qiankun 微前端。已用 Node 直连 Edge CDP WebSocket 实现全程自动(含验证码点选),无需 agent-browser、无需人工。
标准查询流程(两步,日常照此执行)
NODE="C:/Users/Administrator/.workbuddy/binaries/node/versions/22.22.2-2/node.exe"
# 自动定位本技能 scripts 目录:用户级 + 工作区级技能目录都找一遍,装成任何文件夹名都能命中(勿写死目录名)
SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)"
# 1) 确保常驻 Edge 在跑——以后台长驻任务方式运行守护脚本(它拉起 Edge 并常驻持有;
# 若端口已开,再跑一次会立即退出码 0 表示复用)。切勿前台运行(会阻塞)或依赖 detached 子进程(会被沙箱回收)。
# → 用后台任务执行: CDP_PORT=9223 "$NODE" "$SK/shekou_browser.mjs"
# 2) 查船(文本 / --json 全字段);可加第二个位置参数=出口航次精确查
CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "ALS VESTA" # 不带航次:默认系统带出的最新一班
CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "ALS VESTA" 055N # 带航次:精确查在港/更早航次(推荐)
CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "ALS VESTA" 055N --json
> 航次参数重要(2026-09 修复):招商 ePort 船期查询表单有「航次 OutboundVoy」下拉,选中船名后会自动带成最新一班(如 ALS VESTA → 056N)。若只按船名查,点查询会被这个默认航次过滤,只返回最新一班、查不到在港/更早的航次(如 055N)。用户给了具体航次(如"055N")时务必把航次作为第二个参数传入,脚本会在选船后覆盖 formConfig.data.OutboundVoy(选船后、点查询前各写一次防异步回填冲掉)。不传航次则保留默认最新一班并在输出里提示。
> 注意:OutboundVoy 清空为 ""/null/delete 都无法表示"全部航次"(空串和 null 会被后端当过滤条件返回 0 条,delete reactive 键会打断 vxe 绑定导致不发请求)——要查全部航次就逐航次传参查询。
- 固定登录 profile:
browser-profile/(技能目录下,跨会话保留 cookie,已瘦身 ~267MB,勿删/勿归档 Cache)。shekou_browser.mjs用它 +--remote-debugging-port=9223拉起 Edge;脚本本身常驻守护 Edge(Edge 作为其子进程随它存活)。 - 登录态失效时——全自动重登(2026-09-03 实测跑通,
shekou_relogin.mjs+shekou_cap.mjs,全部裸 CDP 无 agent-browser):shekou_query.mjs检测到跳登录页会立即以退出码 4 输出NEED_LOGIN。随后按下面 4 步走(验证码文字点选由 agent 读网格图完成):
1. 密码来源(优先级):prepare第 2 参 > 环境变量SHEKOU_PASS> 技能目录secrets/shekou.json({"user":"<蛇口账号>","pass":"..."},本地文件、勿提交/外传)。配置一次后失效可自动重登。
2.CDP_PORT=9223 "$NODE" "$SK/shekou_relogin.mjs" prepare→ 导航登录页、切"账号登录"、native setter 填账号密码、勾协议、点登录、等验证码弹出,输出{captcha:true, tip:"请依次点击【X、Y】"}。若{captcha:false, loggedIn:true}表示无验证码直接成功。
3.CDP_PORT=9223 "$NODE" "$SK/shekou_cap.mjs" grid→ 用 canvas 给验证码图片区叠加页面绝对坐标网格,输出cache/captcha_grid.png(红黄线 +x###/y###标签)。agent 读这张图,按 tip 找两个目标字中心——图上 x/y 标签数字就是页面坐标,直接读出,无需任何换算。
4.CDP_PORT=9223 "$NODE" "$SK/shekou_cap.mjs" click <x1> <y1> <x2> <y2>→ 裸 CDP 依次点(入参即上一步读出的页面坐标)。输出{loggedIn:true, url:"...#/main/home"}即成功(cookie 已写回 profile)。退出码 3 = 验证失败/新题:跑"$NODE" "$SK/shekou_cap.mjs" refresh换新题 → 回第 3 步重试。 shekou_cap.mjs tip看当前要点哪两个字;shekou_relogin.mjs status查登录态(0=已登录/2=未登录)。- 验证码关键坑(实测,务必照做):
- 待点的字画在背景
<img>(.verify-img-panel img,约 500×300,原点约 x210/y287)上,不是 DOM 元素——只能从图读位置。 - 截图必须截纯图片区(
grid命令已处理)。截整个.verifybox(含顶部白边+底部提示条,约 503×383,原点 209/261)会让图中字产生约 (1,50px) 偏移、坐标全错。 - 网格图法零换算:网格标注的是页面绝对坐标,读字中心直接喂
click。肉眼估截图像素再 /2 有系统性偏差、必偏(曾反复失败的根因)。 - 取点必须落在图片区、取字形几何中心(2026-09-04 实测教训):图片区 y 范围约 y287(顶部红线)~ y587,任何目标字的点击 y 都不得小于 287——把上方字读成 y≈175/298 之类是错的(点到图片区外/天上,整题判错自动换题)。读点时让目标字的几何中心对齐最近的红网格交叉:横向 x 用红线列(210/260/310…,列间距 50,可内插到 25/35/410 等),纵向 y 用黄线行(287/337/387/437/487/537/587,行间距 50)。两个字都必须点准,一个偏了整题失败、刷新换新题。先看 tip 里的目标字,再在图里按颜色+字形找同名的那个;图中总有一个不同字(颜色可能相同,如红「气」vs 目标红「个」)是干扰,只点 tip 点名的字。 连续 2 次失败先
sleep 75避限流再 refresh 换题,别猛点。 - 点击必须裸 CDP
Input.dispatchMouseEvent三连(mouseMoved/Pressed/Released);点对一个字面板会出现绿色.point-area序号标记,两个都对自动跳#/main/home。 - 刷新按钮
.verifybox-refresh(右上角约 (681,274));关闭.verifybox-close别点错。
- 待点的字画在背景
- 连接挂死排障:
shekou_query.mjs报"未捕获 VesselSchedule 响应"或请求 PENDING = qiankun 网关在程序化导航后挂起、该源连接池耗尽(登录成功后首次查询偶尔会遇到)。正解:结束守护后台任务并taskkill /F /T /IM msedge.exe,以后台任务重跑shekou_browser.mjs——登录 cookie 已持久化到 profile,重启后免登录直接落在 VesselSchedule 页,等 ~12s 微应用挂载后再查即可恢复(2026-09-03 实测)。 - 限流排障(2026-09-03 实测):短时间内密集登录+查询(如排障反复跑)会触发服务端限流。两种表现:① 请求 PENDING;② 表单填写完全正确(船名/航次/日期都对)但结果"暂无数据 / 共 0 条"——后者是船名自动联想接口被限流、候选没回填、VesselId 没绑上所致,不是真的没船期。处理:静置约 60–80s 不发任何请求,再做一次干净查询即恢复(实测静置 80s 后 055N 正常返回)。排障时避免连续猛跑查询/登录脚本。
脚本:scripts/shekou_browser.mjs(启动/复用常驻 Edge)、scripts/shekou_cdp.mjs(CDP 通用 eval/shot/click/insert/nav)、scripts/shekou_relogin.mjs(登录失效重登:prepare/status,填表提交到弹验证码)、scripts/shekou_cap.mjs(验证码处理:grid 出坐标网格图 / click 按页面坐标点字 / refresh 换题 / tip)、scripts/shekou_login.mjs(旧版填表+提交,保留备用)、scripts/shekou_query.mjs(一键船期查询,失效退出码 4)。细节见 references/api_notes.md。
登录(仅登录态失效时;正常固定 profile 免登录)——照上面"全自动重登"4 步即可,核心是 shekou_cap.mjs grid 网格图 + 页面坐标直点。 铁律:裸 CDP Input.dispatchMouseEvent 三连点击(agent-browser 的 mouse 会判错);填表用 native setter + input 事件(勿逐字 keyEvent);协议点真实 input[type=checkbox](点 label 不触发)。
已验证的直达路由(登录后直接导航,别点工作台菜单):
- 船期查询(航次/ETB/ETD,用户要的):https://wk-eport.cmp1872.com/scct-query/VesselSchedule
- 船舶静态档案(非船期):https://wk-eport.cmp1872.com/scct-query/VesselParticular
- 子应用全部路由可读 __vue__.$router.options.routes(NGAgentInfo=船代查询、ServiceInfo=航线查询等)。
登录后取船期(一键脚本 scripts/shekou_query.mjs,已多轮连跑验证稳定):
CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "ALS VESTA" # 文本输出
CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "ALS VESTA" --json # 全字段 JSON
脚本内部做了这几件事(全是踩坑后的正解,别改回旧法):
1. 附加到已开页面,绝不 Page.reload/同 URL Page.navigate。浏览器启动时已自然打开 VesselSchedule;qiankun 微前端一旦程序化整页重载,网关会在首个业务请求上永久挂起并泄漏连接,几次后该源连接池耗尽,连原生 fetch 都 PENDING(症状:GVesselNameInfoKING 请求发出但永不返回)。仅当当前页根本不在 VesselSchedule 路由时才导航一次。轮询等 vesselscheduleindex+SertchInput 组件挂载。
2. 选船用组件方法,别模拟输入框:在组件树里找 SertchInput(船名联想组件,源码里就这么拼),设 search.inputValue=<船名大写> + showTbale=true,直接调 search.findList1()(发 GVesselNameInfoKING);轮询 search.gridOptions.data 候选回填后调 search.cellClick({row:hit})(完整匹配优先)。cellClick 会再发 GVesselVoyage?VesselName=<船名> 自动带出航次(SCT→056N/056NI)并设好 VesselId。裸 Input.insertText/dispatchKeyEvent 在该框不可靠。
3. 日期原地改属性,不要整体替换对象:vxeTableOptions.formConfig.data.StartDate/EndDate 是 {val,rule,formValue} 结构,只改 obj.val/obj.formValue(宽范围 2026-05-01 ~ 2026-12-31)。整体 d.StartDate={...} 会打断 vxe 表单绑定,导致 ParamsAll 里 StartDate 变空 {}。
4. 触发查询用真实 CDP 点击「查询」按钮(动态定位 button.vxe-button 文本"查询"的中心坐标,约 (420,137)),不要调 wrap.proxyQuery() 或 grid.commitProxy('query')——它们读 vxe 内部 formData(grid.formData),程序化改的日期不在其中,会因日期为空而不发请求。真实点击走 vxe 表单 submit,能收集到 DatePicker 的值并发出带完整参数的 GET。
5. 响应用 CDP Network 域抓(Network.enable + requestWillBeSent 匹配 /api/VesselSchedule + loadingFinished 后 Network.getResponseBody)。查询走 fetch 不是 XHR,XHR hook 拦不到!
6. 字段映射(InnerList[0],勿靠 vxe 虚拟滚动 DOM):航次=invoynbr(进口)/outvoynbr(出口);ETB=ETB;ETD=ETD;船代=Inagent(进口)/Outagent(出口);TerminalCode=港区、ShipId=船代码、LINEID=船公司、ServiceId=航线、IMO、ETADate/POB。
7. 裸 fetch 直连接口即使复制网关头(Authorization/timestamp/ct-app-version/c)仍返回 code:551(网关 per-request 签名),不要尝试逆向,让页面组件发请求即可。
排障:若脚本报"未捕获 VesselSchedule 响应"或 GVesselNameInfoKING 一直 PENDING,基本是连接池被之前的失败/探测拖挂了——taskkill /F /T /PID <edge主进程>(或 /IM msedge.exe /T)后用同一 --user-data-dir profile 重启 Edge(cookie 保留,无需重新登录/验证码),再跑即可。iView/vxe 组件上 DOM 原生 .click() 不触发 Vue handler,一律走 CDP Input.dispatchMouseEvent。
长荣海运 Evergreen 订舱单号追踪(纯 HTTP,无需浏览器/无验证码)
按订舱单号(Booking No.,如 149607870869)在长荣官网货柜动态查询,输出船名航次、结关(截关)日期。页面 https://www.evergreen-shipping.cn/servlet/TDB1_CargoTracking.do 。脚本 scripts/evergreen_booking.py 仅用 Python 标准库(urllib + cookie),直接跑:
PY="C:/Users/Administrator/.workbuddy/binaries/python/versions/3.13.12/python.exe"
SK="$(find "$HOME/.workbuddy/skills" "$PWD/.workbuddy/skills" -maxdepth 3 -name sinotrans_schedule.py 2>/dev/null | head -1 | xargs -r dirname)"
"$PY" "$SK/evergreen_booking.py" --bkno 149607870869
"$PY" "$SK/evergreen_booking.py" --bkno 149607870869 --json
工作原理(表单是普通 POST,无验证码拦截):
1. GET 查询页建立会话(失败不致命)。
2. POST 同 URL,表单:TYPE=BK、bkno=<订舱号>、BL=/CNTR=/NO= 留空(订舱号走 BK 分支;提单号 TYPE=BL+BL=,箱号 TYPE=CNTR+CNTR=)。订舱号合法长度 12/9/8 位。
3. 解析返回 HTML:船名航次在顶部表"船名 航次"后的 <td>(注意该页 <th> 误用 </td> 闭合,正则用 </t[dh]>);结关日期在"装货港"行的"截关日期"列(单元格顺序:装货港名、VGM硬拷贝、VGM(EDI)、截关、预计到达、预计开航、预计装船、签发)。日期形如 SEP-07-2026,脚本转成 2026-09-07。
- 偶发
SSL: UNEXPECTED_EOF/ 连接重置:脚本已内置重试(3 次),网络抖动时再跑一次即可。 - 返回还含装货港/开航日/订舱状态等参考字段(
--json全量)。
资源
scripts/sinotrans_schedule.py— 中外运:腾讯文档开舱/船期表,一次拉取美加/欧地/亚太/拉非/东南亚全部 5 条开舱航线、跨表匹配,按船名(+航次)查 航线表/船名航次/SVVD/POL/开船日期/开舱日期(标准库匿名 HTTP + protobuf 解码,无需登录/浏览器;按表头 SVVD 自动定位列;缓存 12h,查不到且超半天自动重下)。用法python sinotrans_schedule.py --ship "ALS VESTA" [--voy 054N] [--json] [--list] [--refresh]。cache/sinotrans_schedule.json— 中外运全航线表格本地缓存(自动生成,含 fetched_at / 全部船期行);可删,删后下次自动重下。scripts/evergreen_booking.py— 长荣:按订舱单号查船名航次 + 结关日期(标准库 HTTP POST,无验证码)。用法python evergreen_booking.py --bkno 149607870869 [--json]。scripts/query_vessel.py— 盐田:登录 + 查询 + 解析一体化脚本(标准库,直接运行)。scripts/shekou_browser.mjs— 蛇口:启动/守护常驻调试 Edge(固定browser-profile/登录 profile,端口 9223;以后台常驻任务运行)。日常第一步。scripts/shekou_cdp.mjs— 蛇口:Node 直连 Edge CDP(eval/shot/click/insert/nav/reload)。scripts/shekou_login.mjs— 蛇口:自动填账号密码+勾协议+提交登录(旧版,已由 relogin+cap 取代,保留备用)。scripts/shekou_relogin.mjs— 蛇口:登录失效重登。prepare(读 secrets/shekou.json 密码→填表提交→弹验证码,输出 tip)、status(查登录态,退出码 0/2)。scripts/shekou_cap.mjs— 蛇口:验证码处理。grid(canvas 给验证码图片区叠加页面坐标网格出 cache/captcha_grid.png)、click <x1> <y1> <x2> <y2>(按页面坐标裸 CDP 点字)、refresh(换一题)、tip(看提示)。scripts/shekou_query.mjs— 蛇口:一键船期查询(附加已登录页面→组件法选船→覆盖航次 OutboundVoy→原地改日期→真实点查询按钮→Network 域抓响应)。用法CDP_PORT=9223 "$NODE" "$SK/shekou_query.mjs" "<船名>" [航次] [--json]。选船后航次会自动带成最新一班,要查在港/更早航次务必传第二个航次参数(如 055N)。 登录失效时退出码 4 +NEED_LOGIN。secrets/shekou.json— 蛇口 ePort 登录凭证{"user":"<蛇口账号>","pass":"..."}(本地文件,勿提交/外传/写入记忆或文档正文;relogin 自动读取)。secrets/yantian.json— 盐田 156yt 登录凭证{"user":"<盐田账号>","pass":"..."}(本地文件,勿提交/外传;query_vessel.py 自动读取)。scripts/setup_credentials.py— 凭证初始化/保存(盐田+蛇口)。交互式python setup_credentials.py(密码不回显)、--status查看配置状态、参数式--yantian-pass/--shekou-pass/--yantian-user/--shekou-user。首次缺密码时向用户索取后用它保存,之后查询自动读取。browser-profile/— 蛇口 ePort 固定 Edge profile(含登录 cookie,跨会话免登录;已去除缓存,~267MB)。勿删;勿把运行后新产生的 Cache 目录归档。references/api_notes.md— 盐田接口(§2) + 蛇口 CDP 全自动登录/验证码/船期(§3) + 长荣订舱追踪(§4) + 中外运腾讯文档 protobuf 解码与全航线列定位(§5)。