通过官方承运商接口,使用运单号跟踪CJ大韩通运和韩国邮政的包裹,并将工作流围绕一个承运商适配器进行组织,以便将来支持更多快递公司。
快递跟踪
技能功能
使用CJ大韩通运和韩国邮政的官方查询页面,通过运单号查询当前配送状态。
- CJ大韩通运:使用官方配送查询页面暴露的JSON端点
- 韩国邮政:使用官方配送查询页面使用的HTML端点
- 结果以通用格式(快递公司/运单号/当前状态/最近事件)简要整理
使用场景
- "帮我查一下CJ大韩通运的运单"
- "韩国邮政的快递现在到哪了"
- "帮我确认这个运单号是否已配送完成"
- "请整理一下各快递公司的查询逻辑,方便以后添加更多"
不使用场景
- 只有订单号而没有运单号的情况
- 需要直接进行快递预约或退货受理的情况
- 希望绕道使用非官方综合配送查询服务的情况
前提条件
- 互联网连接
python3curl- 可选:
jq
输入
- 快递公司标识符:
cj或epost - 运单号
- CJ大韩通运:10位或12位数字
- 韩国邮政:13位数字
承运商适配器规则
本技能将各快递公司的逻辑按承运商适配器单元划分。
添加新快递公司时,首先确定以下字段。
carrier id:例如cj、epostvalidator:运单号的位数/模式entrypoint:官方查询入口URLtransport:使用JSON API、HTML表单还是CLIparser:从哪个字段或表格提取状态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. 首先规范化输入
- 将快递公司名称规范化为
cj或epost之一。 - 去除运单号中的空格和
-。 - 如果位数验证先失败,则不发送查询。
1. CJ大韩通运:官方JSON流程
从官方入口页面读取 _csrf,并将其值随 tracking-detail POST一起发送。
- 入口页面:
https://www.cjlogistics.com/ko/tool/parcel/tracking - 详细端点:
https://www.cjlogistics.com/ko/tool/parcel/tracking-detail - 必填字段:
_csrf、paramInvcNo
基本示例使用 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 也可能包含事件,因此优先查看详细事件数组。发布的示例按照通用结果模式(carrier、invoice、status、timestamp、location、event_count、recent_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相同的通用结果模式(carrier、invoice、status、timestamp、location、event_count、recent_events)仅保留配送状态所需的值,并去除事件地点中可能混杂的 TEL 号码片段,不直接显示收件人/详细备注原文。
3. 为人类规范化
不要直接粘贴响应原文,而是按照以下通用结果模式进行总结。
通用结果模式
carrier:快递公司标识符(cj或epost)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客户端连接时发生超时或重置
备注
- 本技能为查询型技能。
- 基本接口仅使用官方承运商端点。
- 添加其他快递公司时,以相同格式新增一个承运商适配器的方式进行扩展。






