delivery-tracking

delivery-tracking

热门

通过官方承运商接口,使用运单号跟踪CJ大韩通运和韩国邮政的包裹,并将工作流围绕一个承运商适配器进行组织,以便将来支持更多快递公司。

6445Star
736Fork
更新于 2026/7/22
SKILL.md
readonly只读
name
delivery-tracking
description

通过官方承运商接口,使用运单号跟踪CJ大韩通运和韩国邮政的包裹,并将工作流围绕一个承运商适配器进行组织,以便将来支持更多快递公司。

快递跟踪

技能功能

使用CJ大韩通运和韩国邮政的官方查询页面,通过运单号查询当前配送状态。

  • CJ大韩通运:使用官方配送查询页面暴露的JSON端点
  • 韩国邮政:使用官方配送查询页面使用的HTML端点
  • 结果以通用格式(快递公司/运单号/当前状态/最近事件)简要整理

使用场景

  • "帮我查一下CJ大韩通运的运单"
  • "韩国邮政的快递现在到哪了"
  • "帮我确认这个运单号是否已配送完成"
  • "请整理一下各快递公司的查询逻辑,方便以后添加更多"

不使用场景

  • 只有订单号而没有运单号的情况
  • 需要直接进行快递预约或退货受理的情况
  • 希望绕道使用非官方综合配送查询服务的情况

前提条件

  • 互联网连接
  • python3
  • curl
  • 可选:jq

输入

  • 快递公司标识符:cjepost
  • 运单号
    • CJ大韩通运:10位或12位数字
    • 韩国邮政:13位数字

承运商适配器规则

本技能将各快递公司的逻辑按承运商适配器单元划分。

添加新快递公司时,首先确定以下字段。

  • carrier id:例如 cjepost
  • validator:运单号的位数/模式
  • entrypoint:官方查询入口URL
  • transport:使用JSON API、HTML表单还是CLI
  • parser:从哪个字段或表格提取状态
  • status map:如何将各快递公司的原始状态代码映射为通用状态
  • retry policy:超时/重试规则

当前适配器如下:

承运商适配器 官方入口 传输方式 验证器 解析重点
cj https://www.cjlogistics.com/ko/tool/parcel/tracking 页面GET + tracking-detail POST JSON 10位或12位数字 parcelDetailResultMap.resultList
epost https://service.epost.go.kr/trace.RetrieveRegiPrclDeliv.postal?sid1= 表单POST HTML 13位数字 基本信息 table_col + 详细 processTable

工作流

0. 首先规范化输入

  • 将快递公司名称规范化为 cjepost 之一。
  • 去除运单号中的空格和 -
  • 如果位数验证先失败,则不发送查询。

1. CJ大韩通运:官方JSON流程

从官方入口页面读取 _csrf,并将其值随 tracking-detail POST一起发送。

  • 入口页面:https://www.cjlogistics.com/ko/tool/parcel/tracking
  • 详细端点:https://www.cjlogistics.com/ko/tool/parcel/tracking-detail
  • 必填字段:_csrfparamInvcNo

基本示例使用 curl 保持 _csrf 和cookie,Python仅用于JSON整理。

tmp_body="$(mktemp)"
tmp_cookie="$(mktemp)"
tmp_json="$(mktemp)"
invoice="1234567890"  # 官方页面占位符性质的smoke-test值

curl -sS -L -c "$tmp_cookie" \
  "https://www.cjlogistics.com/ko/tool/parcel/tracking" \
  -o "$tmp_body"

csrf="$(python3 - <<'PY' "$tmp_body"
import re
import sys

text = open(sys.argv[1], encoding="utf-8", errors="ignore").read()
print(re.search(r'name="_csrf" value="([^"]+)"', text).group(1))
PY
)"

curl -sS -L -b "$tmp_cookie" \
  -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
  --data-urlencode "_csrf=$csrf" \
  --data-urlencode "paramInvcNo=$invoice" \
  "https://www.cjlogistics.com/ko/tool/parcel/tracking-detail" \
  -o "$tmp_json"

python3 - <<'PY' "$tmp_json"
import json
import sys

payload = json.load(open(sys.argv[1], encoding="utf-8"))
events = payload["parcelDetailResultMap"]["resultList"]
if not events:
    raise SystemExit("查询结果为空。")

status_map = {
    "11": "商品接收",
    "21": "商品移动中",
    "41": "商品移动中",
    "42": "到达配送地",
    "44": "商品移动中",
    "82": "配送出发",
    "91": "配送完成",
}

latest = events[-1]
normalized_events = [
    {
        "timestamp": event.get("dTime"),
        "location": event.get("regBranNm"),
        "status_code": event.get("crgSt"),
        "status": status_map.get(event.get("crgSt"), event.get("scanNm") or "未知"),
    }
    for event in events
]
print(json.dumps({
    "carrier": "cj",
    "invoice": payload["parcelDetailResultMap"]["paramInvcNo"],
    "status_code": latest.get("crgSt"),
    "status": status_map.get(latest.get("crgSt"), latest.get("scanNm") or "未知"),
    "timestamp": latest.get("dTime"),
    "location": latest.get("regBranNm"),
    "event_count": len(events),
    "recent_events": normalized_events[-min(3, len(normalized_events)):],
}, ensure_ascii=False, indent=2))
PY

rm -f "$tmp_body" "$tmp_cookie" "$tmp_json"
CJ公开输出示例

以下值是2026-03-27基于实时smoke test(1234567890)确认的规范化结果。

{
  "carrier": "cj",
  "invoice": "1234567890",
  "status_code": "91",
  "status": "配送完成",
  "timestamp": "2026-03-21 12:22:13",
  "location": "京畿广州五浦",
  "event_count": 3,
  "recent_events": [
    {
      "timestamp": "2026-03-10 03:01:45",
      "location": "清原HUB",
      "status_code": "44",
      "status": "商品移动中"
    },
    {
      "timestamp": "2026-03-21 10:53:19",
      "location": "京畿广州五浦",
      "status_code": "82",
      "status": "配送出发"
    },
    {
      "timestamp": "2026-03-21 12:22:13",
      "location": "京畿广州五浦",
      "status_code": "91",
      "status": "配送完成"
    }
  ]
}

额外的smoke test也可以使用 000000000000

CJ响应中,即使 parcelResultMap.resultList 为空,parcelDetailResultMap.resultList 也可能包含事件,因此优先查看详细事件数组。发布的示例按照通用结果模式(carrierinvoicestatustimestamplocationevent_countrecent_events,可选 status_code)仅保留非识别字段,不直接显示可能包含负责人姓名和联系方式的 crgNm 原文。

2. 韩国邮政:官方HTML流程

韩国邮政的官方入口页面会再次向 trace.RetrieveDomRigiTraceList.comm POST sid1

  • 入口页面:https://service.epost.go.kr/trace.RetrieveRegiPrclDeliv.postal?sid1=
  • 实际查询端点:https://service.epost.go.kr/trace.RetrieveDomRigiTraceList.comm
  • 必填字段:sid1

韩国邮政使用 curl --http1.1 --tls-max 1.2 路径比本地Python HTTP客户端更稳定,因此以此组合作为基本示例。

tmp_html="$(mktemp)"
python3 - <<'PY' "$tmp_html"
import html
import json
import re
import subprocess
import sys

invoice = "1234567890123"  # 官方页面占位符性质的smoke-test值
output_path = sys.argv[1]

cmd = [
    "curl",
    "--http1.1",
    "--tls-max",
    "1.2",
    "--silent",
    "--show-error",
    "--location",
    "--retry",
    "3",
    "--retry-all-errors",
    "--retry-delay",
    "1",
    "--max-time",
    "30",
    "-o",
    output_path,
    "-d",
    f"sid1={invoice}",
    "https://service.epost.go.kr/trace.RetrieveDomRigiTraceList.comm",
]
subprocess.run(cmd, check=True)

page = open(output_path, encoding="utf-8", errors="ignore").read()

summary = re.search(
    r"<th scope=\"row\">(?P<tracking>[^<]+)</th>.*?"
    r"<td>(?P<sender>.*?)</td>.*?"
    r"<td>(?P<receiver>.*?)</td>.*?"
    r"<td>(?P<delivered_to>.*?)</td>.*?"
    r"<td>(?P<kind>.*?)</td>.*?"
    r"<td>(?P<result>.*?)</td>",
    page,
    re.S,
)
if not summary:
    raise SystemExit("未找到基本信息表格。")

def clean(raw: str) -> str:
    text = re.sub(r"<[^>]+>", " ", raw)
    return " ".join(html.unescape(text).split())

def clean_location(raw: str) -> str:
    text = clean(raw)
    return re.sub(r"\s*(TEL\s*:?\s*)?\d{2,4}[.\-]\d{3,4}[.\-]\d{4}", "", text).strip()

events = re.findall(
    r"<tr>\s*<td>(\d{4}\.\d{2}\.\d{2})</td>\s*"
    r"<td>(\d{2}:\d{2})</td>\s*"
    r"<td>(.*?)</td>\s*"
    r"<td>\s*<span class=\"evtnm\">(.*?)</span>(.*?)</td>\s*</tr>",
    page,
    re.S,
)

normalized_events = [
    {
        "timestamp": f"{day} {time_}",
        "location": clean_location(location),
        "status": clean(status),
    }
    for day, time_, location, status, _detail in events
]

latest_event = normalized_events[-1] if normalized_events else None

print(json.dumps({
    "carrier": "epost",
    "invoice": clean(summary.group("tracking")),
    "status": clean(summary.group("result")),
    "timestamp": latest_event["timestamp"] if latest_event else None,
    "location": latest_event["location"] if latest_event else None,
    "event_count": len(normalized_events),
    "recent_events": normalized_events[-min(3, len(normalized_events)):],
}, ensure_ascii=False, indent=2))
PY
rm -f "$tmp_html"
韩国邮政公开输出示例

以下值是2026-03-27基于实时smoke test(1234567890123)确认的规范化结果。

{
  "carrier": "epost",
  "invoice": "1234567890123",
  "status": "配送完成",
  "timestamp": "2025.12.04 15:13",
  "location": "济州邮件集中局",
  "event_count": 2,
  "recent_events": [
    {
      "timestamp": "2025.12.04 15:13",
      "location": "济州邮件集中局",
      "status": "配送准备"
    },
    {
      "timestamp": "2025.12.04 15:13",
      "location": "济州邮件集中局",
      "status": "配送完成"
    }
  ]
}

韩国邮政基本信息表格使用 등기번호(挂号编号)、보내는 분/접수일자(寄件人/受理日期)、받는 분(收件人)、수령인/배달일자(收件人/配送日期)、취급구분(处理分类)、배달결과(配送结果)的顺序,详细事件则读取 processTable 下的 날짜 / 시간 / 발생국 / 처리현황(日期/时间/发生地/处理状态)行。发布的示例按照与CJ相同的通用结果模式(carrierinvoicestatustimestamplocationevent_countrecent_events)仅保留配送状态所需的值,并去除事件地点中可能混杂的 TEL 号码片段,不直接显示收件人/详细备注原文。

3. 为人类规范化

不要直接粘贴响应原文,而是按照以下通用结果模式进行总结。

通用结果模式
  • carrier:快递公司标识符(cjepost
  • invoice:规范化后的运单号
  • status:当前配送状态
  • timestamp:最后事件时间
  • location:最后事件地点
  • event_count:总事件数
  • recent_events:最近最多3个事件列表
  • status_code:仅在需要时保留的原始状态代码(当前仅在CJ示例中使用)

4. 重试和回退策略

  • 如果位数错误,立即停止并重新获取正确格式。
  • CJ在重新获取 _csrf 后再尝试一次。
  • 韩国邮政保持 curl --retry 3 --retry-all-errors --retry-delay 1
  • 不绕道使用其他快递公司。

完成条件

  • 快递公司和运单号已正确识别
  • 当前状态和最近事件已整理完毕
  • 能够说明使用了哪个官方接口
  • 为将来扩展其他快递公司时,需要添加哪些承运商适配器字段已明确

失败模式

  • CJ:_csrf 提取失败或 tracking-detail 响应模式变更
  • CJ:运单号长度不是10位或12位
  • 韩国邮政:sid1 不是13位
  • 韩国邮政:HTML标记变更导致表格提取规则失效
  • 韩国邮政:使用非 curl 客户端连接时发生超时或重置

备注

  • 本技能为查询型技能。
  • 基本接口仅使用官方承运商端点。
  • 添加其他快递公司时,以相同格式新增一个承运商适配器的方式进行扩展。