← 返回 Skill 列表
extension
分类: 数据与分析无需 API Key

AOE瀚忠空运价格提取

AOE 瀚忠空运价格提取规则。当用户提供 AOE(瀚忠)空运价格 Excel 文件(命名含 "AOE" 和日期)时使用。触发词:AOE、瀚忠、瀚忠空运、GARY。

person作者: user_17da570ahubcommunity

AOE 瀚忠空运价格提取 Skill

提取 AOE 瀚忠空运多航司运价表 → 24列标准输出 → 上传 WPS。

⚠️ 铁律:仅提取用户最新提交文件,禁止复制历史数据

  • 禁止查看/引用/复制任何历史脚本中的 price/data 数组
  • 禁止从历史输出文件直接读取数据
  • 禁止硬编码历史文件路径作为源文件
  • 每次提取必须从用户当前提交文件全新读取
  • 有就是有,没有就是没有:不得用历史数据填补缺失值

一、核心参数

| 参数 | 值 | |------|-----| | 源文件 | 用户提交(命名 AOE XXMAY GARY.xlsx) | | 格式 | Excel .xlsx,多 Sheet(12个价格Sheet) | | 起始港 | 多港口:PVG/HGH/HAK/NKG/CGO/KMG | | 价格编号 | aoe_{sheet}_{carrier}_001 | | 来源 | AOE | | 货型 | 1:1(默认)+ 子行货型(1:300/1:500等) | | 引擎 | pandas + openpyxl(GF需要 openpyxl 检测填充色) |


二、Sheet 清单与特征

| Sheet | 航司 | 格式特征 | 特殊处理 | |-------|------|---------|---------| | EY PVG | EY | 简单, +250KG列 | +250→300KG槽位, PVG起运 | | EY SZX | EY | 同上 | SZX起运 | | EY EHU | EY | 同上 | EHU起运 | | 印度专线 | CX/AI/NH/GI/JD/O3/RH | 主行+子行, 多货型 | 合并单元格, 全角冒号, 货型子行 | | ~~AI~~ | ~~AI~~ | ~~副本版已移除~~ | - | | 6E印度散货 | 6E | 简单 | 生效日期=05-29 | | CX CGK | CX | 主行+子行 | 合并单元格 | | 南非CXEYVN | CX/VN | 主行+子行 | 卡车行跳过 | | D0中东 欧洲 | D0 | 双段式:中东直价+Zone表 | 🚨 新增,col1=目的港,同行多比率共价,Zone表跳过 | | VN | VN | 多目的地 | 生效日期=01-Jun | | HU | HU | 副本版格式:区域+航线+卡车Zone | 🚨 列映射变更,col0=区域,col1=航线,col4=目的站 | | ~~GF~~ | ~~GF~~ | ~~副本版已移除~~ | - | | ~~UA~~ | ~~UA~~ | ~~副本版已移除~~ | 0608恢复 | | ~~QF~~ | ~~QF~~ | ~~副本版已移除~~ | 0608恢复 | | ~~KTM~~ | ~~KTM~~ | ~~副本版已移除~~ | 0608恢复 | | GF | GF | 多目的地,黑底跳过 | 0608恢复,openpyxl检测 | | ~~C6~~ | ~~C6~~ | ~~副本版已移除~~ | 0608新增OSR HGH | | ~~LH~~ | ~~LH~~ | ~~副本版已移除~~ | 0608新增FRA PVG | | ~~CX RUH 停飞中~~ | ~~CX~~ | ~~副本版已移除~~ | 0608恢复,停飞仍提取 | | ~~D0美国~~ | - | ~~图片无法OCR~~ | 0608跳过 |


三、关键规则

3.1 合并单元格价格传播(⚠️ 高频坑)

印度专线、CX CGK、南非 等Sheet中,子行的300/500/1000KG常为合并单元格,只有col8(300KG)有值。

# 子行价格读取后统一处理
if p300 and not p500: p500 = p300
if p500 and not p1000: p1000 = p500

3.2 全角冒号检测

印度专线 Sheet 中货型列存在全角冒号:1:260、1::500。

def is_ratio_str(val):
    s = str(val).strip() if pd.notna(val) else ''
    return bool(re.match(r'^1[::]+(\d+)', s))

所有 startswith('1:') 判断必须替换为 is_ratio_str()。

3.3 货型子行处理

印度专线中,主行下可能有「散货」「带电托盘」「1M以下矮托盘」等货型子行——这些行的col4有货型描述,col5为/或Q100起...(非1:XXX)。

处理逻辑:

  1. col4有货型描述 + col6-10有价格 → 提取为1:1行
  2. 更新 current_cargo 供后续子行继承
  3. st_type 从 col4 解析

3.4 散托类型(多表述)

def st_type_from_cargo(s):
    has_tuo = '托' in s
    has_san = '散' in s
    if has_tuo and has_san: return '散货/托盘'
    if has_tuo: return '托盘'        # 含 矮托盘/超高/带电托盘
    if has_san: return '散货'        # 含 带电散货
    return '散货/托盘'

常见货型表述:

  • 托/散 / 托盘/散货 → 散货/托盘
  • 托盘 / 托/超高 → 托盘
  • 散货 / 1:300散货 → 散货
  • 带电托盘 → 托盘
  • 1M高度以下矮托盘 → 托盘

3.5 黑底单元格过滤(GF专有)

GF Sheet 使用 openpyxl 读取,价格区域 col C-G 有 patternType='solid' 的行整行跳过。

c45_cell = ws.cell(row=row, column=3)
fill = c45_cell.fill
if fill and fill.patternType == 'solid':
    continue  # 黑底,不提取

3.6 HU 卡车/飞机Zone + 托盘列(⚠️ 托盘增量不提取)

HU Sheet 中 托盘 列(col12=col M)为合并单元格(如 M8:M14 包含 BRU主行+6卡车区)。但 HU 的托盘增量不提取,散托增量统一留空。

中转区有两种格式:

  • 卡车N区:公路转运,col4 列出目的站
  • 飞机N区:空运转运,col4 列出目的站(结构完全相同,必须同时提取)
# 必须同时处理卡车和飞机区
is_truck = routing.startswith('卡车') or routing.startswith('飞机')

# HU 所有行:散托增量留空
'散托增量': ''

3.7 目的地继承

印度专线中,同一目的地下多个航司行(col0为空)→ 继承 current_dest:

if dest:
    current_dest = dest
# 后续无dest的航司行使用 current_dest

3.8 QF 双区块

QF Sheet 有两个独立格式块(SYD + JFK/ORD/DFW),通过 'AOE报价' / 'valid to' 行切换生效日期。

3.9 EY +250KG 特殊列

EY Sheet 价格列为 +100/+250/+500/+1000(无+300)。+250KG 值填入标准 300KG 列。

全局规则

  • 频次为空时默认填"Daily":频次列如果为空则填入 Daily(0727全局规则)
  • 分泡列不提取:分泡列始终留空,不提取任何分泡数据(0720全局规则)

四、提取流程

1. 确认源文件路径(命名含 AOE + 日期)
2. pandas 读取各 Sheet 结构
3. 逐 Sheet 调用对应提取函数
4. GF 使用 openpyxl 检测填充色
5. 应用合并单元格传播 + 货型判断
6. 生成 24 列 DataFrame
7. validate_columns() 验证
8. 保存 → 自动上传 WPS → 删除临时文件

五、脚本模板

当前脚本:/Users/hantao/WorkBuddy/oone/extract_aoe_0525.py 源文件:0525/AOE 25MAY GARY.xlsx 输出:aoe_0525_output.xlsx(临时,上传后删除)

新批次时需更新:

  • file_path 指向新源文件
  • 各Sheet 生效日期
  • 新增/停飞 Sheet 的处理

六、常见坑

坑1:合并单元格价格(印度/CGK/南非)

子行 300/500/1000KG 合并为单值,只有 col8 有数据。 → 修复:if p300 and not p500: p500 = p300; if p500 and not p1000: p1000 = p500

坑2:全角冒号(印度专线)

1:260 / 1::500 导致 startswith('1:') 失败。 → 修复:用 is_ratio_str() 正则 ^1[::]+(\d+)

坑3:货型子行跳过(印度专线)

「散货」「带电托盘」等行 col5 为 / 不是 1:XXX,被跳过。 → 修复:检测 col4 有货型描述 + col6-10 有价格 → 提取为 1:1 行

坑4:黑底单元格误提取(GF)

GF 部分价格单元格黑色填充表示废弃。 → 修复:用 openpyxl 读取,patternType=='solid' → 跳过整行

坑5:HU 托盘增量不提取

HU Sheet 的托盘列有 +2/+1 合并单元格,但不提取散托增量,统一留空。

坑6:目的地跨行继承(印度专线)

DEL块下 NH/AI 行的 col0 为空,dest 丢失。 → 修复:if dest: current_dest = dest + 记录用 current_dest

坑7:子行 100KG 错位

子行记录中 '100KG': p45 应为 p100。 → 修复:改为 '100KG': p100

坑8:GF openpyxl 警告

DrawingML support is incomplete 不影响数据读取,可忽略。

坑9:D0中东欧洲 多比率同行共用价格(0603新增)

D0 Sheet 的 RATIO col 可能含多级比率(如 1:100;1:300),同行所有比率共用同一价格。 ❌ 错误:尝试从下一行读价格 → BAH 1:300 取了 R7 的 38 ✅ 正确:同行多比率全部使用该行价格 → BAH 1:100 和 1:300 都是 37

坑10:D0 Zone参考表误提取(0603新增)

D0 Sheet R23+ 是 Zone 参考表(Region/PVG/DEST/add-on),col0='Europe'/'Africa'。 ✅ 在循环中检测到 col0 in ('Europe','Africa','Region','ME','General Cargo') → break 终止

坑11:D0 目的港解析(0603新增)

目的港在 col1(非 col0),多目的港用 / 或 , 分隔(如 BAH/AUH/DWC/SHJ)。 排除 PVG(3字母但为起运港)和含 to/+ 的 add-on 字符串。

坑15:EY PVG/SZX/EHU 起始行错误(0603修正)

EY 三个 Sheet 数据起始行均为 R6(df.iloc[5]),不是 R10(df.iloc[9])。 ❌ 错误:range(9, len(df)) → 跳过前3~4行有效数据(AMM/AUH/BAH 等) ✅ 正确:range(5, len(df)) → 从 R6 开始

坑16:EY 三个 Sheet 生效日期不同(0603修正)

  • EY PVG:R3=AOE报价6.1 报价6.1-6.15 → 2026-06-01 00:00:00
  • EY SZX:R3=AOE报价 6.8-6.14 → 2026-06-08 00:00:00(与PVG/EHU不同!)
  • EY EHU:R3=AOE报价 6.1 → 2026-06-01 00:00:00 每次提取必须先读 R3 内容确认生效日期,不能用 datetime.now() 代替。

坑12:HU 列映射差异(0603新增)

副本版 HU Sheet 格式与 0525 不同:

  • col0=区域描述(加拿大区域/美东区域...)
  • col1=航线(PEK-YYZ),起运港从航线首段提取
  • col4=目的站 / 卡车区列表(用逗号分隔多个3字码)
  • col6-10=价格(+45KG/+100KG/+300KG/+500KG/+1000KG)
  • 直飞行 routing='直飞',卡车子行 routing='卡车N区'

坑13:D0 散托增量(0603新增)

D0 备注 托盘+N → 解析为散货+增量N。

def parse_d0_increment(remark_str):
    m = re.search(r'托盘\+(\d+(?:\.\d+)?)', remark_str)
    if m: return m.group(1)
    m = re.search(r'\+(\d+(?:\.\d+)?)', remark_str)
    if m and float(m.group(1)) <= 20: return m.group(1)
    return ''

坑17:EY 三个 Sheet 频次处理(0603新增)

  • EY PVG/SZX:表格无明确频次标注 → 全部默认 Daily,不能留空
  • EY EHU:频次记录在第一个价格行(R6)的备注列,如 *ETD/EHU: D1467(货机)
    • 解析规则:从第一行备注中 re.search(r'D(\d+)', first_remark) 提取,整个Sheet统一使用
    • 当前值:D1467(后续批次可能变化,每次需读 R6 备注确认)
    • 如未匹配到则回退为 Daily
first_row = df.iloc[5]  # R6是第一个数据行
first_remark = safe_str(first_row[6])  # col6 = 备注列
m = re.search(r'D(\d+)', first_remark)
sheet_freq = 'D' + m.group(1) if m else 'Daily'

坑14:印度专线「航司」空值跳过(0603修正)

印度专线中 JIA/CCU 行 MIN 值特殊(MIN 1.7/KG OR 3900/CNY),col2='CX' 有航司但 col0 有其他目的港码。 ✅ 修正:if 'MIN' in min_val.upper() or 'KG' in min_val.upper(): i += 1; continue 在主行提取中已覆盖

坑18:印度专线频次清洗 + 生效日期 + ratio双列 + cargo传播(0603修正)

18a. 生效日期读取:

  • 印度专线 R7 'AOE报价6.2调整' → 正则提取 → 2026-06-02
  • 6E印度散货 R1 'AOE报价05.29 valid from 01.Jun' → 正则提取 → 2026-06-01
  • 禁止使用 datetime.now()

18b. 频次清洗函数 clean_freq():

  • 删除 via 及之后内容(re.sub(r'\bvia\b.*$', '', s))
  • 删除所有中文字符
  • 删除非频率残留词(转卡车/直达/直飞/服务/窄体等)
  • 搜索 \bdaily\b(全字符串)→ 'Daily'
  • 🆕 纯中文输入默认 Daily:清理后为空但原始含中文(如"窄体续程")→ 'Daily'
    • 触发例:6E BOM~GOX 共15行 col6='窄体续程',col7无频次信息
  • 搜索所有 \bD([\d\-\/]+) 模式合并去重 → D{unique_digits}
    • D4-5 D5-6 → 合并 → D456
    • D3/5 → D35
    • D3/4/5/7 VIA HKG → D3457
  • parse_freq(val, remark='') 双参数:先试 val,失败则从 remark fallback
    • 6E DEL: col6='窄体续程'→空,remark='PVG-CCU DAILY 直达'→Daily

18c. col4 ratio 检测(印度专线子行格式不一致):

  • 标准子行:col4(货型)空,col5(MIN)是 ratio → 原逻辑
  • 变异:col4 是 ratio(如 1:300),col5 空 → 需额外 is_ratio_str(col4_val) 分支
    • 触发例:GI CGO-BLR R71 ['', '', '', '', '1:300', '', '', '21.5', ...]

18d. cargo+ratio 合并更新(O3散货/托盘分类错误):

  • col4='散货'(cargo变更)+ col5='1:167'(ratio)→ 需先 current_cargo=col4_val 再处理 ratio
    • 触发例:O3 CGO-BLR R78 ['', '', '', '', '散货', '1:167', '', '17.5', ...]
    • 之前:用旧的 current_cargo(限1米以下矮托盘)→ 散托类型=托盘 ✗
    • 修正:更新后 → 散托类型=散货 ✓
# 子行4路分支处理顺序:
# A) col4是ratio, col5非ratio → ratio在货型列
# B) col4是cargo, col5是ratio → 先更新cargo再ratio
# C) col5是ratio, col4空 → 标准ratio子行
# D) col4是cargo, col5非ratio → 纯cargo变更

坑19:CX CGK 生效日期 + 频次默认Daily(0603修正)

19a. 生效日期:

  • R7 'AOE报价6.1' → 正则 (\d{1,2})\.(\d{1,2}) → 2026-06-01
  • 禁止使用 datetime.now()

19b. 频次 VIA HKG → Daily:

  • R10 routing='VIA HKG' → clean_freq 删除 via 后剩 'HKG' → 非 Daily 非 D-pattern → 空
  • parse_freq() 最终 fallback:无识别频率 → 默认 'Daily'

19c. parse_freq() 末尾通用规则(全局生效):

def parse_freq(val, remark=''):
    freq = clean_freq(val)
    if freq: return freq
    if remark:
        freq = clean_freq(remark)
        if freq: return freq
    return 'Daily'  # 🆕 无识别频率 → 默认 Daily
  • 影响范围:所有 Sheet 中频次列为空/纯中文/VIA 等情况均回退为 Daily
  • 例外:EY EHU 单独提取 D1467(不在 routing 列,在 remark),不受影响

坑20:D0 中东 欧洲 生效日期 + ratio减价跳过 + 欧洲段目的地继承(0603修正)

20a. 生效日期:

  • R4 col6 'Valid to 04.Jun' → 2026-06-04(统一整个Sheet)
  • 禁止使用 datetime.now()

20b. ratio 含 -1/kg → 不展开:

  • R6 ratio_text = '1:100 -1/kg 55分;1:300 -1/kg。' → 这些是减价备注,不是真正的比重段
  • 检测 ratio_text and '-1/kg' not in ratio_text 跳过,不生成 1:100/1:300 额外行
  • BAH/AUH/DWC/SHJ 等中东目的地只有 1:1 主行(各1条)

20c. 欧洲段 R12-R16 目的地解析:

  • R12 col1 = 'to LEJ+1/kg ,WAW+3/kg+add on' → 用 re.findall(r'\b([A-Z]{3})\b', col1) 提取 → ['LEJ', 'WAW']
  • R13-R16 col1 为空 → 继承 prev_dests(上一行目的地)
  • 每个比重(1:80/120/167/250/500)LEJ 和 WAW 各生成一条 → 共 10 条
  • 散托类型: col7 含"托盘"→散货,否则→散货/托盘
    • 1:80/120/167 col7='托盘+2' → inc=2, 散托=散货
    • 1:250/500 col7='' → inc=空, 散托=散货/托盘

20d. 频次从路线头提取:

  • R4 col1 = 'PVG-BAH-MES 带电不接 D1234' → re.search(r'D(\d+)', ...) → D1234
  • 所有目的港统一使用

20e. 目的地排除列表:

# 只排除纯转运点(MES, BRU),BAH 是目的地不能排除
if len(part) == 3 and part.isalpha() and part not in ('PVG', 'MES', 'BRU'):

坑21:HU 「飞机N区」被跳过(0603修正)

HU Sheet 中转区有两种:卡车N区(公路转运)和 飞机N区(空运转运)。两者结构完全相同,都是 col4 列出目的站列表。

原代码只匹配 is_truck = routing.startswith('卡车'),飞机N区 不匹配任何分支,直接被跳过。

  • 影响行:R74-R76(飞机1/2/3区,拉美)、R204-R207(飞机1/2/3/4区,中东)、R213-R215、R219-R220、R225-R226、R255、R265、R287、R303-R304、R306-R307
  • 以 PEK-HKT 为例:R287 飞机1区 含 RUH/SHJ/BAH 等 21 个中东目的地,全部丢失

✅ 修复:

# fuben 版(副本AOE 2JUN EVA.xlsx,col3=routing, col4=dest)
is_truck = routing.startswith('卡车') or routing.startswith('飞机')

# 0601 版(AOE 2JUN EVA.xlsx,col3=routing, col4=cargo)
if (routing.startswith('卡车') or routing.startswith('飞机')) and cargo and cargo != 'NA':

坑22:extract_dest *号/括号/非字符污染(0608修正)

通用 extract_dest() 函数缺陷导致 GF/UA 等 Sheet 产出伪目的地:

| 问题 | 输入示例 | 错误产出 | 原因 | |------|---------|---------|------| | 末尾星号 | DEN* / ORD* | 跳过(len=4≠3) | 星号计入长度 | | 括号备注 | BAH (must go +5/kg) | KG) 伪码 | / 号分割伪造 | | 非字母码 | Zone 5-6 → 5-6 | 非目的地码 | 无 alpha 检查 |

✅ 修复(三步清洗):

def extract_dest(val):
    if not val: return []
    val = val.strip().upper()
    val = re.sub(r'\*+$', '', val)              # ① 去末尾星号
    val = re.sub(r'\s*\([^)]*\)', '', val)      # ② 去括号备注
    # ③ 所有分隔路径统一加 .isalpha()
    if ',' in val:
        return [d.strip() for d in val.split(',') if len(d.strip())==3 and d.strip().isalpha()]
    if '/' in val:
        return [d.strip() for d in val.split('/') if len(d.strip())==3 and d.strip().isalpha()]
    parts = val.split()
    if len(parts) > 1:
        return [p.strip() for p in parts if len(p.strip())==3 and p.strip().isalpha()]
    return [val] if len(val)==3 and val.isalpha() else []

影响范围:0525/0608 脚本的 GF、UA 提取器(均已修复)。


七、🛡️ 防坑经验总结(0603 批次积累)

以下经验适用于所有价格提取数据源,不限于 AOE。

经验1:频次处理三阶段流水线 ✅

无论哪个 Sheet,频次处理统一走三阶段:

raw_value → clean_freq() → parse_freq(val, remark='') → 最终值

| 阶段 | 函数 | 职责 | |------|------|------| | 清理 | clean_freq(val) | 删除 via/VIA + 地名、中文、多余空格;提取 Daily 或 D-pattern | | 解析 | parse_freq(val, remark='') | 先试 val → 为空则从 remark fallback → 为空则返回 'Daily' | | 默认 | 最末 fallback | 所有 Sheet 统一:无识别频率 → 'Daily'(不再输出空) |

关键细节:

  • D 模式合并去重:D4-5 D5-6 → D456,D3/5 → D35
  • 纯中文("窄体续程")→ clean_freq 返回空 → parse_freq 返回 'Daily'
  • VIA 路由("VIA HKG")→ clean_freq 过滤后空 → 'Daily'
  • EY EHU 例外:频次从 remark 单独提取(D1467),不走通用流水线

经验2:生效日期严禁 datetime.now() ✅

所有 Sheet 的生效日期必须从源文件提取,严禁用 datetime.now()。

提取优先级:

  1. 表格中明确标注(Valid to 04.Jun → 2026-06-04)
  2. R3/R4/R7 等表头行含日期字符串(AOE报价6.1 → 2026-06-01)
  3. 都不行 → 从文件名或用户提供的信息推断,绝不用 datetime.now()

原因:0603 当天是 6 月 3 日,但 D0 报价 valid to 6 月 4 日、印度专线 6 月 2 日调整 —— 系统时间与报价生效日无关。

经验3:子行处理「谁来都一样」分支模式 ✅

子行(无航线名的数据行)格式变化多端,必须按优先级多路检测:

# 子行4路分支(按检测顺序):
# A) col4 是 ratio, col5 不是 → ratio 在货型列(如 GI BLR 1:300)
# B) col4 是 cargo, col5 是 ratio → 先更新 cargo 再处理 ratio(如 O3 BLR 散货 1:167)
# C) col5 是 ratio, col4 空 → 标准 ratio 子行
# D) col4 是 cargo, col5 不是 → 纯 cargo 变更

核心教训:不能假设子行某列一定是什么。即使是「比重列」,也可能被填在货型列;即使是「货型列」,也可能同时含 cargo + 下一列 ratio。

经验4:ratio 含减价/加价标记不展开 ✅

检测 ratio 文本是否含 -1/kg、+1/kg(加减价备注):

if '-1/kg' in ratio_text:
    pass  # 不展开为比重行

这些只是对主行价格的修正说明,不是真正的比重段。

经验5:中转区/路由区「同类全覆盖」✅

同一 Sheet 中功能相同的路由类型必须全部匹配:

  • HU:卡车N区 + 飞机N区 → is_truck = routing.startswith('卡车') or routing.startswith('飞机')
  • 教训:写 startswith('卡车') 时必须问自己「有没有其他等价类型?」

经验6:多目的地「空继承」模式 ✅

连续多行的 col1/路由列为空时,继承上一行的目的地:

if not col1_val:
    col1_val = prev_dest  # 继承上一行
else:
    prev_dest = col1_val  # 更新继承源

D0 欧洲段 R13-R16 均为空,依赖此机制正确生成 LEJ/WAW 各比重行。

经验7:排除列表 = 纯转运点,不排除目的地 ✅

路线头 PVG-BAH-MES 解析目的地时:

# ❌ 错误:把 BAH 也排除了(BAH 是目的地)
exclude = {'PVG', 'MES', 'BRU', 'BAH'}
# ✅ 正确:只排除纯转运点
exclude = {'PVG', 'MES', 'BRU'}

规则:三字码在路线头中间且不作为独立目的站出现 → 转运点。

经验8:合并单元格增量「跨行传播」✅

合并单元格中的托盘+N 备注要传播到所有合并子行:

  • 读取合并单元格 master value
  • 建立 merge_map
  • 子行无备注时,用 merge_map 查找主行备注
  • 从备注提取增量(卡板/托盘+N 优先)

经验9:验证闭环 ✅

每次修正后必须:

  1. 跑完整提取脚本
  2. 对比修正前后条数
  3. 抽查修正行的实际输出
  4. 上传 WPS 原地更新
  5. 不做假设性验证

八、24 列输出标准

from columns_24 import COL_24, validate_columns

所有提取器统一使用 COL_24 列名,输出前调用 validate_columns()。