Add misc/temu-pipeline
This commit is contained in:
@@ -0,0 +1,406 @@
|
|||||||
|
---
|
||||||
|
name: temu-pipeline
|
||||||
|
description: TEMU半托管订单全流程流水线 — 从API获取武汉仓库TEMU半托管订单,过滤[H201试算]/[A2试算]已处理订单,逐个试算最低运费,API+UI双策略修改物流,通过API写[H201试算]备注并验证。含EPIPE防护、carrier解析修复、分批处理。
|
||||||
|
category: ecommerce
|
||||||
|
---
|
||||||
|
|
||||||
|
# TEMU 半托管订单全流程流水线 [H201试算]
|
||||||
|
|
||||||
|
一键完成:API获取武汉仓库半托管订单 → 排序 → 逐个试算最低运费 → API动态匹配物流(失败则UI兜底) → API写备注 → 验证重试。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install playwright ddddocr requests
|
||||||
|
playwright install chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推荐版本 — 纯Playwright,iframe穿透稳定
|
||||||
|
python3 scripts/pipeline_hybrid.py
|
||||||
|
|
||||||
|
# 旧版本 — DrissionPage,iframe穿透不稳定,编辑按钮成功率<2%
|
||||||
|
python3 scripts/pipeline_dp.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键发现(会话经验总结)
|
||||||
|
|
||||||
|
### 武汉仓库
|
||||||
|
- 名称: `武汉仓库`
|
||||||
|
- warehouseId: `0006000951201403050000004680`
|
||||||
|
- warehouseType: `owner`
|
||||||
|
|
||||||
|
### 获取TEMU半托管订单(最关键参数)
|
||||||
|
```python
|
||||||
|
api_call("/openapi/tongtool/ordersQuery", {
|
||||||
|
"merchantId": PARTNER_OPEN_ID,
|
||||||
|
"pageNo": "1", "pageSize": "100",
|
||||||
|
"warehouseId": "0006000951201403050000004680",
|
||||||
|
"saleMode": "2", # 2=半托管
|
||||||
|
"platformCode": "temu_api", # ⚠️ 必须加!否则不返回TEMU订单
|
||||||
|
"orderStatus": "waitPacking", # waitPacking/error
|
||||||
|
"payDateFrom": "...", "payDateTo": "..."
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键字段:**
|
||||||
|
- `orderIdCode` — 外部完整订单号(如 `4Caohalf-PO-211-13340762327670771`)
|
||||||
|
- `orderIdKey` — 通途内部ID(用于写备注、改物流)
|
||||||
|
- `salesRecordNumber` — 通途短单号(如 `PO-211-13340762327670771`)
|
||||||
|
- `dispathTypeName` — 当前物流方式(如 `EJET-GOFO(美东)`)
|
||||||
|
|
||||||
|
> ⚠️ `orderId` 字段为空!外部单号在 `orderIdCode` 中,不在 `orderId`。
|
||||||
|
> `platformOrdersQuery` 的 `salesRecordNumber` 返回纯数字,不带 `-PO-` 格式。
|
||||||
|
|
||||||
|
### 过滤TEMU订单
|
||||||
|
```python
|
||||||
|
temu_orders = [o for o in all_orders if '-PO-211-' in (o.get('orderIdCode', '') or '')]
|
||||||
|
```
|
||||||
|
|
||||||
|
### TEMU试算表格结构(2026年新版)
|
||||||
|
```
|
||||||
|
表头: ['', 'merchant_shipping_method_param_id', 'Temu邮寄方式', '平台预估参数', '系统邮寄方式名称', ...]
|
||||||
|
```
|
||||||
|
- TD0: 空(跳过)
|
||||||
|
- TD1: 数字ID(跳过)
|
||||||
|
- TD2: **Temu邮寄方式名** — `SwiftX,Standard` / `SpeedX,standard`(当carrier用)
|
||||||
|
- TD3: 预估价格 — `预估$2.42; 2-5 天送达`(提取价格)
|
||||||
|
- TD4: 系统名称 — `TEMU半托管美西>>...`(含>>,跳过)
|
||||||
|
|
||||||
|
**carrier/method解析规则:**
|
||||||
|
1. 跳过:纯数字ID(8位+)、价格列(含`预估`)、含`>>`列、序号列
|
||||||
|
2. carrier = 第一个非跳过的TD
|
||||||
|
3. method = carrier中逗号前部分 (`SwiftX,Standard` → `SwiftX`)
|
||||||
|
|
||||||
|
### 搜索结果页"编辑"按钮
|
||||||
|
通途详情页需要先点击 **"仓储"链接**(搜索结果页的子tab,非文字"仓储物流"),仓储内容在**iframe**中加载。需在iframe内查找"编辑"和"邮寄方式解析"。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 正确方式
|
||||||
|
document.querySelectorAll('a').forEach(a => {
|
||||||
|
if (a.textContent.trim() === '仓储' && a.href.includes('search_text_value')) a.click();
|
||||||
|
});
|
||||||
|
// 在iframe内操作
|
||||||
|
document.querySelectorAll('iframe').forEach(iframe => {
|
||||||
|
try {
|
||||||
|
const idoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
|
// idoc.querySelectorAll('a') 找"编辑"、"邮寄方式解析"
|
||||||
|
} catch(e) {} // 跨域iframe抛异常
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 物流修改判断逻辑(⚠️ 常见错误)
|
||||||
|
```python
|
||||||
|
# ❌ 错误:与试算结果的carrier对比(carrier是TEMU建议值,不是订单实际物流)
|
||||||
|
carrier_text = cheapest.get("carrier", "")
|
||||||
|
if best_last_mile.upper() not in carrier_text.upper():
|
||||||
|
# 这行判断的是"试算建议的末端不在建议的carrier名中"——永远为False!
|
||||||
|
# 即使当前物流是SWIFTX,建议是GOFO,Standard,也会误判为"已是最优"
|
||||||
|
|
||||||
|
# ✅ 正确:与订单当前实际物流(dispathTypeName)对比
|
||||||
|
dispatch = order.get('dispathTypeName', '') or ''
|
||||||
|
if best_last_mile.upper() not in dispatch.upper():
|
||||||
|
# dispatch = "EJET-SWIFTX(美东)" → "GOFO" not in "EJET-SWIFTX(美东)" → True → 需要修改
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# 策略1: API匹配(优先)
|
||||||
|
sid = find_shipping_method_id(shipping_methods, carrier_keyword, best_last_mile)
|
||||||
|
if sid:
|
||||||
|
update_shipping_method_api(oik, sid, order_code)
|
||||||
|
|
||||||
|
# 策略2: UI兜底(API匹配不到时)
|
||||||
|
ui_ok = await update_shipping_in_ui(page, shipping_methods, best_last_mile)
|
||||||
|
```
|
||||||
|
|
||||||
|
关于 `find_shipping_method_id` 匹配逻辑:`last_mile_upper in name`,即 `"GOFO" in "EJET-GOFO(美东)"` → 可能匹配失败因为SHORTNAME格式不同(含地区括号等)。此时用UI兜底直接打开下拉框选择。
|
||||||
|
|
||||||
|
### DrissionPage vs Playwright(关键经验 2026-05-02)
|
||||||
|
|
||||||
|
**DrissionPage iframe穿透极不稳定** — 编辑按钮点击成功率<2%(79/85失败)。根本原因:DP的`get_frames()`和元素定位在通途嵌套iframe中经常找不到元素。
|
||||||
|
|
||||||
|
**CDP共享方案不可行** — 尝试让Playwright启动浏览器(`--remote-debugging-port=9222`),然后DP连接同一浏览器,但DP 4.1.1.2的`existing_only`参数不起作用,总是尝试启动新浏览器。
|
||||||
|
|
||||||
|
**纯Playwright方案(pipeline_hybrid.py)** — 用`page.evaluate()`穿透iframe查找和操作元素,编辑按钮成功率**100%**(66/66)。这是当前推荐方案。
|
||||||
|
|
||||||
|
```
|
||||||
|
DP版(Batch2): 编辑按钮 1/85 成功 (<2%) — iframe穿透彻底失败
|
||||||
|
PW版(Hybrid): 编辑按钮 66/66 成功 (100%) — iframe穿透完美
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hybrid v2 批量测试统计(100订单):**
|
||||||
|
- 编辑成功: 71/86 (83%) — 15个失败是iframe内找不到编辑按钮
|
||||||
|
- 试算有结果: 50/71 (70%)
|
||||||
|
- 备注写入验证: 50/50 (100%)
|
||||||
|
- API改物流失败: 23次"订单不存在" — orderUpdate对TEMU半托管不可用(已确认)
|
||||||
|
|
||||||
|
**Round2 修复后统计(23订单,直接点击试算行):**
|
||||||
|
- UI改物流成功: 22/23 (95.7%)
|
||||||
|
- 唯一失败: 仓储tab找不到(订单可能已移出待包装状态)
|
||||||
|
|
||||||
|
**结论**:UI改物流应直接点击试算弹窗中的行,不要尝试关闭弹窗再操作下拉框。API改物流对TEMU半托管订单不可用,需走UI。
|
||||||
|
|
||||||
|
### UI改物流:直接在试算弹窗选择(2026-05-02 关键修复)
|
||||||
|
|
||||||
|
**错误方案**:关闭试算弹窗 → 在编辑表单操作下拉框
|
||||||
|
**实测结果**:关闭试算弹窗时,**编辑表单弹窗也一并关闭**(通途layui容器共享)。日志特征:`当前弹窗状态: {'windows': [], 'layers': 0}`
|
||||||
|
|
||||||
|
**正确方案**:直接在试算弹窗表格中点击目标物流行,自动应用到编辑表单
|
||||||
|
**实测结果**:22/23 成功 (95.7%)
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def pw_update_shipping(page, best_last_mile):
|
||||||
|
"""在试算弹窗里直接选择目标物流(不关闭弹窗)"""
|
||||||
|
best_sub = get_best_method_code(best_last_mile)
|
||||||
|
|
||||||
|
# 直接点击试算结果中的匹配行
|
||||||
|
clicked = await page.evaluate(f"""(target) => {{
|
||||||
|
const popup = Array.from(document.querySelectorAll('div[windowid]'))
|
||||||
|
.find(p => p.querySelector('#tabpanelDiv'));
|
||||||
|
if (!popup) return 'no_popup';
|
||||||
|
const temuDiv = popup.querySelector('#temuShippingMethod') || popup;
|
||||||
|
|
||||||
|
for (const tr of temuDiv.querySelectorAll('tr')) {{
|
||||||
|
const rowText = tr.textContent || '';
|
||||||
|
if (!rowText.toUpperCase().includes(target.toUpperCase())) continue;
|
||||||
|
if (rowText.includes('未启用')) continue;
|
||||||
|
|
||||||
|
// 找"选择"按钮或直接点击行
|
||||||
|
for (const btn of tr.querySelectorAll('a, button, span')) {{
|
||||||
|
if (['选择', '确定', '应用'].includes(btn.textContent.trim())) {{
|
||||||
|
btn.click(); return 'clicked_select:' + target;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
tr.click(); return 'clicked_row:' + target; // 行本身可点击
|
||||||
|
}}
|
||||||
|
return 'not_found:' + target;
|
||||||
|
}}""", best_sub)
|
||||||
|
|
||||||
|
return clicked.startswith('clicked')
|
||||||
|
```
|
||||||
|
|
||||||
|
**调用时机**:试算提取`pw_extract_cheapest()`后直接调用`pw_update_shipping()`,无需关闭弹窗。
|
||||||
|
|
||||||
|
**失败情况**:目标物流在试算结果中不存在(渠道未启用、无匹配行)。此时回退到关闭弹窗尝试下拉框(成功率低)。
|
||||||
|
|
||||||
|
### 文件锁机制(防止多实例并发)
|
||||||
|
```python
|
||||||
|
LOCK_FILE = "/tmp/temu_hybrid_v2.lock"
|
||||||
|
|
||||||
|
def acquire_lock():
|
||||||
|
"""获取文件锁,防止多实例并发运行"""
|
||||||
|
try:
|
||||||
|
fd = open(LOCK_FILE, "w")
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
fd.write(f"{os.getpid()}\n") # 写入PID便于调试
|
||||||
|
fd.flush()
|
||||||
|
return fd
|
||||||
|
except (IOError, OSError):
|
||||||
|
try:
|
||||||
|
fd.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取锁异常: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def release_lock(fd):
|
||||||
|
"""释放文件锁并删除锁文件"""
|
||||||
|
try:
|
||||||
|
if fd:
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||||
|
fd.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# 始终尝试删除锁文件
|
||||||
|
try:
|
||||||
|
if os.path.exists(LOCK_FILE):
|
||||||
|
os.remove(LOCK_FILE)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键点**:`release_lock()` 必须删除锁文件。`fcntl.flock` 是进程级的,进程异常退出时锁自动释放,但锁文件不会自动删除。残留的锁文件虽然不会阻止新进程获取flock,但会造成混淆。
|
||||||
|
|
||||||
|
**主函数结构**:
|
||||||
|
```python
|
||||||
|
async def main():
|
||||||
|
lock_fd = acquire_lock()
|
||||||
|
if lock_fd is None:
|
||||||
|
print("已有实例在运行")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# ... 主逻辑 ...
|
||||||
|
finally:
|
||||||
|
release_lock(lock_fd) # 确保异常时也释放
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日志归档(每次运行保存独立日志)
|
||||||
|
```python
|
||||||
|
LOG_DIR = os.path.expanduser("~/.hermes/logs/temu-pipeline")
|
||||||
|
LOG_FILE = os.path.join(LOG_DIR, f"run_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
|
||||||
|
class TeeLogger:
|
||||||
|
"""同时输出到文件和stdout"""
|
||||||
|
def __init__(self, log_file):
|
||||||
|
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||||
|
self.terminal = sys.stdout
|
||||||
|
self.log = open(log_file, 'w', encoding='utf-8')
|
||||||
|
|
||||||
|
def write(self, message):
|
||||||
|
self.terminal.write(message)
|
||||||
|
self.log.write(message)
|
||||||
|
self.log.flush()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.terminal.flush()
|
||||||
|
self.log.flush()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.log.close()
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
sys.stdout = TeeLogger(LOG_FILE)
|
||||||
|
print(f"日志归档: {LOG_FILE}\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用**:
|
||||||
|
```python
|
||||||
|
async def main():
|
||||||
|
setup_logging() # 放在最前面
|
||||||
|
# ... 后续print都会同时写入文件和stdout ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**查看历史统计**:
|
||||||
|
```bash
|
||||||
|
grep '完成!' ~/.hermes/logs/temu-pipeline/*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**日志位置**:`~/.hermes/logs/temu-pipeline/run_YYYYMMDD_HHMMSS.log`
|
||||||
|
|
||||||
|
### Playwright EPIPE崩溃
|
||||||
|
长时间运行(15+订单)导致Pipe断开。**不要关闭browser**,只导航到登录页重新认证:
|
||||||
|
```python
|
||||||
|
BATCH_SIZE = 15
|
||||||
|
for batch_start in range(0, len(orders), BATCH_SIZE):
|
||||||
|
if batch_start > 0:
|
||||||
|
await page.goto(TT_LOGIN_URL) # 不close browser
|
||||||
|
# 重新登录...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 物流更新API需传orderId
|
||||||
|
`orderUpdate` 需要 `orderId`(外部单号),否则报 `参数orderId不能为空!`:
|
||||||
|
```python
|
||||||
|
api_call("/openapi/tongtool/orderUpdate", {
|
||||||
|
"orderIdKey": oik, "shippingMethodId": sid, "orderId": order_code
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待配货页面 headless 问题
|
||||||
|
- 统计功能正常(能显示订单数)
|
||||||
|
- **查询按钮点击返回0条**(headless下事件链不完整)
|
||||||
|
- **解决方案**:直接用API获取订单数据,完全绕开浏览器
|
||||||
|
|
||||||
|
### API限速
|
||||||
|
每分钟最多5次,超限返回 `请求次数限额[1Minute/5times.]`。需加3秒间隔+限速后sleep 15秒。
|
||||||
|
|
||||||
|
## 完整流程
|
||||||
|
|
||||||
|
1. API获取邮寄方式列表 — `getShippingMethod`
|
||||||
|
2. `ordersQuery` 获取订单(`warehouseId` + `saleMode=2` + `platformCode=temu_api`)
|
||||||
|
3. 筛选 `waitPacking`/`error` 状态 + `-PO-211-` 格式
|
||||||
|
4. `ordersRemarkQuery` 批量查备注 → 过滤有 `[H201试算]`/`[A2试算]` 的订单
|
||||||
|
5. 分批(每15个)处理:
|
||||||
|
- 搜索详情页 → 仓储链接 → 编辑(iframe) → 邮寄方式解析(iframe) → TEMU试算
|
||||||
|
- 提取最低价(新表头规则)
|
||||||
|
- **物流修改判断:对比 `dispathTypeName`(订单当前物流)与 `best_last_mile`(试算最优末端)**
|
||||||
|
- 如果当前物流已含最优末端 → 跳过
|
||||||
|
- 否则:**API匹配 → `orderUpdate` → 失败则UI兜底**
|
||||||
|
- 注意:`orderUpdate` 的 `orderId` 参数传外部单号(`4Caohalf-PO-211-xxx`)但TEMU订单可能报`订单不存在!`(API限制)
|
||||||
|
- **UI下拉框匹配规则**:只匹配末端派送名(`"GOFO"`),不匹配物流商(`"湖北众信"`)——选项dd元素不含物流商前缀
|
||||||
|
- API写 `[H201试算]` 备注 → `verify_remark` 验证 → 重试
|
||||||
|
|
||||||
|
### 物流修改 vs 备注写入的分离
|
||||||
|
|
||||||
|
| 操作 | 方式 | 成功条件 |
|
||||||
|
|------|------|---------|
|
||||||
|
| 改物流 | API `orderUpdate` + UI兜底 | API: 需要orderId字段(TEMU订单可能为空);UI: 需要下拉正确弹出 |
|
||||||
|
| 写备注 | API `addOrderRemark` | 只需要orderIdKey,总是可用 |
|
||||||
|
|
||||||
|
**关键策略**:改物流和写备注是**两个独立步骤**。即使物流修改失败,也**必须写备注**。`
|
||||||
|
`
|
||||||
|
|
||||||
|
## 去重逻辑
|
||||||
|
```python
|
||||||
|
has_remark = any('[H201试算]' in r or '[A2试算]' in r for r in remarks)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键API
|
||||||
|
|
||||||
|
| API | 参数 |
|
||||||
|
|-----|------|
|
||||||
|
| `ordersQuery` | warehouseId, saleMode, platformCode, orderStatus |
|
||||||
|
| `addOrderRemark` | remarkList: [{orderIdKey, remark}] |
|
||||||
|
| `ordersRemarkQuery` | orderIdKey |
|
||||||
|
| `getShippingMethod` | warehouseId |
|
||||||
|
| `orderUpdate` | orderIdKey, shippingMethodId, orderId |
|
||||||
|
|
||||||
|
### orderUpdate 的 orderId 问题
|
||||||
|
- TEMU半托管订单的 `orderId` 字段在API返回中**为空字符串**
|
||||||
|
- 传外部单号(`4Caohalf-PO-211-xxx`)报 `订单不存在!`(code=527)
|
||||||
|
- `orderIdCode` 字段有值但API不接受该参数
|
||||||
|
- **结论**:`orderUpdate` API对TEMU半托管订单不可用,必须走**UI方式**修改物流
|
||||||
|
- 批量测试:12次API改物流全部报"订单不存在",确认API不可用于TEMU半托管
|
||||||
|
|
||||||
|
### update_shipping_ui 修正(2026-05-01)
|
||||||
|
|
||||||
|
参考 `tongtool-y2-lowest-shipping` skill 的 `get_best_method_code` + `update_shipping_method` 逻辑。
|
||||||
|
|
||||||
|
**关键修正点:**
|
||||||
|
1. **Playwright evaluate传参**:只能传1个参数,必须包装成对象 `{"method": "GOFO"}` — 旧代码传2个参数 `(provider, method)` 永远失败
|
||||||
|
2. **下拉框dd匹配规则**:dd选项只显示物流方式名(如 `EJET-GOFO(美东)`),**不包含物流商名**(没有"湖北众信"前缀),所以只需按末端名匹配,不需要同时匹配物流商
|
||||||
|
3. **添加 `get_best_method_code()` 标准化**:`SwiftX,Standard` → `SwiftX`,`GOFO,Standard` → `GOFO`
|
||||||
|
4. **保存按钮排除 `#orderRemarkForm`**:避免保存备注而非物流
|
||||||
|
5. **移除UI函数内的API逻辑**:API尝试已在主循环中先做,UI函数只负责纯UI兜底
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_best_method_code(method_str):
|
||||||
|
# USPS→USPS, GOFO→GOFO, SWIFTX→SwiftX, SWIFT→Swift etc.
|
||||||
|
|
||||||
|
async def update_shipping_ui(page, best_last_mile):
|
||||||
|
best_sub_method = get_best_method_code(best_last_mile)
|
||||||
|
# 检查当前是否已是最优
|
||||||
|
current_text = await page.evaluate("() => {... find input with '>>' ...}")
|
||||||
|
if best_sub_method.upper() in current_text.upper(): return True
|
||||||
|
|
||||||
|
# 单参数对象传值
|
||||||
|
result = await page.evaluate("(args) => {...}", {"method": best_sub_method})
|
||||||
|
# dd只匹配method名,不匹配provider
|
||||||
|
```
|
||||||
|
|
||||||
|
**主循环调用(line ~827):**
|
||||||
|
```python
|
||||||
|
sid = find_shipping_method_id(shipping_methods, carrier_keyword, best_last_mile)
|
||||||
|
if sid:
|
||||||
|
update_shipping_method_api(oik, sid, order_code) # API优先
|
||||||
|
else:
|
||||||
|
ui_ok = await update_shipping_ui(main_page, best_last_mile) # UI兜底
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI物流下拉选项匹配
|
||||||
|
- 下拉框dd选项只显示物流方式名(如 `EJET-GOFO(美东)`)
|
||||||
|
- **不包含物流商名**(没有"湖北众信"前缀)
|
||||||
|
- 匹配条件应**只匹配末端派送名**:`item.textContent.includes("GOFO")`
|
||||||
|
- 不需要同时匹配物流商
|
||||||
|
|
||||||
|
### carrier/method 完整解析示例
|
||||||
|
```
|
||||||
|
TEMU行: raw=['3704583687894247366', 'SwiftX,Standard', '预估$2.42; 1-5 天送达', 'TEMU半托管美东>>SwiftX,Standard...', '奥盛-美东']
|
||||||
|
→ 跳过TD0(空)、TD1(纯数字)、TD3(含"预估")、TD4(含">>")
|
||||||
|
→ carrier = TD2 = "SwiftX,Standard"
|
||||||
|
→ method = "SwiftX,Standard".split(",")[0] = "SwiftX"
|
||||||
|
→ best_last_mile = "SwiftX"
|
||||||
|
```
|
||||||
|
|
||||||
Reference in New Issue
Block a user