同一个采集流程,在美国站能跑,到了德国站就报错。
不是流程逻辑错了,是货币符号不认识、日期格式不兼容、页面语言对不上。
TEMU和TikTok Shop把我们的业务从国内推到了海外。北美、欧洲、东南亚,店铺矩阵横跨十几个国家。原本跑得好好的自动化流程,迁移到新站点时开始出现各种“水土不服”——提取的价格对不上、翻页按钮找不到、时间筛选器完全失效。
这不是平台改版,也不是架构瓶颈。根源在于我们把国内店铺的自动化经验直接复制到了海外,低估了多语言、多区域带来的隐性复杂度。
这篇文章专门讲我们怎么在工程层面系统性地处理国际化带来的挑战。目标是让一套流程逻辑可以在不同语言、不同币种、不同时区的店铺里稳定运行,而不需要为每个区域单独维护一份流程文件。
一、国际化带来的三类差异
我们把多区域运营中影响自动化流程的差异分成了三类。
第一类:语言差异。 页面上的文本、按钮、提示信息、错误消息使用不同语言。最直接的影响是元素定位——依赖文本内容的XPath或CSS选择器会失效。次要影响是数据提取后的解析逻辑需要适配。
第二类:格式差异。 日期、时间、数字、货币、地址在不同区域有不同的表达习惯。德国用dd.mm.yyyy,美国用mm/dd/yyyy。德国价格显示1.234,56 €,美国则是$1,234.56。如果解析逻辑写死了某种格式,换个区域就全错。
第三类:业务规则差异。 某些功能只在特定区域可用,某些操作受当地法规限制。比如欧洲有GDPR相关的数据最小化要求,某些买家信息不能采集。东南亚部分国家货到付款比例极高,需要专门的订单处理流程。
前两类可以通过技术手段解决,第三类则需要业务层面的判断,我们这里重点讨论前两类的工程化处理。
二、选择器国际化:告别文本依赖
最早期的流程里,很多元素是通过可见文本定位的——//button[contains(text(),'确认')]、//span[text()='下一页']。这在单一语言下没问题,跨语言直接失效。
我们做的第一步,是彻底改造选择器策略,让所有与语言无关的属性承担定位职责。
优先级从高到低:
- ID和data-testid:语义明确、跨语言稳定,优先使用。
- CSS类名中的功能语义部分:即使平台做了多语言适配,功能类名通常不变。比如
.btn-submit、.pagination-next。 - ARIA属性:
aria-label通常是平台用来做无障碍支持的,往往包含功能描述,比可见文本更稳定。 - DOM结构位置:在列表或表格中,通过相对位置而非文本定位。比如“表格第三列”比“标题为‘价格’的列”更通用。
实在无法通过以上方式定位的元素,才考虑文本定位,但文本不再硬编码在选择器里,而是从区域化文本资源文件中动态注入。
class LocalizedSelector:
def __init__(self, locale: str, resource_manager):
self.locale = locale
self.resources = resource_manager
def get_text_selector(self, key: str) -> str:
# 从资源文件获取对应语言的文本
text = self.resources.get(key, self.locale)
return f"//*[contains(text(),'{text}')]"
资源文件维护了各语言下关键业务文本的映射:
{
"button.confirm": {
"en-US": "Confirm",
"de-DE": "Bestätigen",
"fr-FR": "Confirmer",
"es-ES": "Confirmar"
},
"pagination.next": {
"en-US": "Next",
"de-DE": "Weiter",
"fr-FR": "Suivant"
}
}
这样一来,流程脚本本身是语言无关的。当需要文本定位时,通过LocalizedSelector动态获取当前店铺语言对应的文本并生成选择器。切换区域只需要切换资源文件,不需要改动流程。
三、日期、时间与数字格式的自动适配
格式差异是数据采集和处理中最容易出Bug的地方。我们曾有一个采集流程在德国站跑了三天没人发现异常——因为它把03.05.2026解析成了3月5日,实际上是5月3日。滞后了两个月的数据偏差才被运营发现。
我们实现了一个区域感知的数据解析器,根据店铺配置中的locale字段自动选择解析规则。
from datetime import datetime
import re
class LocaleAwareParser:
def __init__(self, locale: str):
self.locale = locale
self.config = self._load_locale_config(locale)
def _load_locale_config(self, locale):
# 返回该区域的日期格式、货币符号、数字分隔符等配置
configs = {
"de-DE": {"date_format": "%d.%m.%Y", "decimal_sep": ",", "thousands_sep": ".", "currency_symbol": "€"},
"en-US": {"date_format": "%m/%d/%Y", "decimal_sep": ".", "thousands_sep": ",", "currency_symbol": "$"},
"en-GB": {"date_format": "%d/%m/%Y", "decimal_sep": ".", "thousands_sep": ",", "currency_symbol": "£"},
"fr-FR": {"date_format": "%d/%m/%Y", "decimal_sep": ",", "thousands_sep": " ", "currency_symbol": "€"},
}
return configs.get(locale, configs["en-US"])
def parse_date(self, date_str: str) -> datetime:
# 尝试多个可能的格式
for fmt in self.config.get("date_formats", [self.config["date_format"]]):
try:
return datetime.strptime(date_str.strip(), fmt)
except ValueError:
continue
raise ValueError(f"Cannot parse date '{date_str}' with locale {self.locale}")
def parse_price(self, price_str: str) -> float:
# 移除货币符号,根据区域配置替换分隔符
cleaned = price_str.replace(self.config["currency_symbol"], "").strip()
cleaned = cleaned.replace(self.config["thousands_sep"], "")
cleaned = cleaned.replace(self.config["decimal_sep"], ".")
return float(cleaned)
所有流程中的数据解析都通过这个解析器,不再手工datetime.strptime。店铺注册时配置其所属区域,解析器自动选择规则。
四、时区一致性治理
多区域运营中,时区不一致导致的问题也很隐蔽。一个店铺在洛杉矶(UTC-7),调度中心在中国(UTC+8),定时任务配置的是“每天早上8点采集”,这个8点到底是哪个时区?
我们强制推行了一套时区规范:
- 所有调度和任务时间戳内部统一使用UTC。数据库存储、Redis缓存、日志时间戳全部是UTC。
- 每个店铺配置中声明其业务时区。定时任务的cron表达式基于店铺的本地时区定义,调度中心在计算触发时间时做转换。
- 流程内部的时间展示也基于店铺时区。买家消息的发送时间、订单创建时间,在采集和展示时转换为店铺本地时间,方便运营理解。
import pytz
def convert_task_schedule_to_utc(cron_expr: str, shop_timezone: str) -> str:
# 将基于店铺时区的cron表达式转换为UTC时间触发
# 简化处理:实际需要更复杂的cron时区转换
tz = pytz.timezone(shop_timezone)
# 省略具体转换逻辑
return cron_expr_utc
这套规范让跨时区的问题定位变得简单——看到UTC时间戳就知道它是唯一的、无歧义的参考点,本地时间只是展示层的事。
五、多语言模板与消息国际化
消息回复流程需要支持多语言。买家用德语提问,自动化要用德语回复。不同区域的买家沟通风格也不同——德国买家偏好简洁准确,美国买家接受相对热情的营销话术。
我们的模板渲染引擎(第十一篇)在国际化场景下做了扩展。模板Key除了按平台区分,还可以按语言和区域分层覆盖:
config:template:reply:general # 默认模板(英文)
config:template:reply:general:de-DE # 德语覆盖
config:template:reply:general:fr-FR # 法语覆盖
ConfigClient读取模板时,根据店铺绑定的语言偏好,按优先级查找:语言特定版 → 区域默认版 → 全局默认版。
模板内容本身也支持变量替换,并且变量值的格式化遵循对应区域的规则。比如金额嵌入模板时,渲染引擎会根据区域配置自动格式化:
模板: "您的订单${order_id}总金额为${amount},预计${delivery_date}送达。"
渲染后(de-DE): "Ihre Bestellung PO123 hat einen Gesamtbetrag von 29,99 €, voraussichtliche Lieferung am 15.06.2026."
渲染后(en-US): "Your order PO123 total is $29.99, estimated delivery on 06/15/2026."
六、区域差异化流程的组件复用
有些业务逻辑确实因区域而异——比如欧盟订单需要额外处理VAT税号,东南亚需要处理COD订单。这些不是简单的参数差异,而是流程分支的差异。
我们采用的做法是在DAG工作流和子流程层面处理差异化,而不是在一个流程里写满if-else。
基础流程——比如“订单采集”——保持通用。区域特有的逻辑被封装成可选子流程,在工作流定义中按区域条件挂载。
workflow:
name: "order_processing"
shop_id: "temu_de_001"
locale: "de-DE"
nodes:
- id: "collect_orders"
component: "order_collect"
- id: "vat_validation"
component: "eu_vat_check"
depends_on: ["collect_orders"]
condition: "locale in ['de-DE', 'fr-FR', 'es-ES']"
- id: "fulfillment"
component: "order_fulfill"
depends_on: ["vat_validation"]
这样,通用能力在资产库里统一维护,区域差异在编排层按需插入,各自独立演进。
七、本地化测试的策略
国际化之后,测试的复杂度翻倍。一套流程要在十几个语言环境下验证,手工测试显然不现实。
我们的做法是页面快照多语言对比测试。在沙箱环境里,对同一操作,用不同语言的店铺环境各跑一次,对比DOM结构和关键元素的定位结果。如果某个语言下的元素定位失败或结构偏差过大,测试自动标记为高风险,阻止发布。
同时,我们录制了一批多语言页面的DOM基线。平台页面改版时,不仅对比同语言的旧版,还对比不同语言之间的结构一致性,确保平台没有某个语言站点单独改版。
八、货币处理与财务安全
涉及金额的操作,在国际化场景下格外需要谨慎。浮点数在不同区域的货币转换中可能产生精度损失,而财务容不得半分钱的偏差。
我们的规则是:所有金额在系统内部以“最小货币单位”的整数存储和运算。 美元用美分,欧元用欧分。只在展示和输入层进行格式化转换。
def parse_price_to_cents(price_str: str, locale: str) -> int:
parser = LocaleAwareParser(locale)
amount = parser.parse_price(price_str)
return round(amount * 100) # 转为分
def format_cents_to_display(cents: int, locale: str) -> str:
parser = LocaleAwareParser(locale)
amount = cents / 100.0
# 根据区域格式化
return f"{parser.config['currency_symbol']}{amount:,.2f}"
这个看似微小的规定,避免了我们在跨境财务对账中可能出现的一分钱差异。每一分钱都算得清楚,财务才能放心让自动化系统处理订单金额。
九、持续跟进平台国际化变更
不同语言站点的页面结构不一定完全同步。平台在某些区域可能先上线新功能,或者保留旧版页面。我们的页面元素库需要感知这种差异。
我们给每个页面对象增加了一个locale_specific_overrides机制。默认使用通用选择器,特定区域有差异时,在区域覆盖文件中声明,加载时自动合并。
class PDDEOrderPage(PDDOrderPage):
# 德国站特有的覆盖
locale = "de-DE"
# 如果某个元素在德国站的选择器不同
def get_order_status(self, row_element):
# 德国站的订单状态用不同的class
return row_element.find_element(".bestellstatus-tag").text
页面对象继承体系保证了大部分逻辑复用,只在必要处覆盖。这比维护多套完全独立的页面对象成本低得多。
十、最终沉淀
国际化改造不是一次性的项目,而是随着店铺矩阵的全球扩张持续演进。做完这套适配后,新开一个国家站点,自动化流程的迁移成本从之前的一周降到了一天以内。大部分工作变成了配置——配语言、配时区、配货币、配区域特有的模板文本。
更重要的是,这套体系把“多语言支持”从流程开发者的脑子里搬到了系统的基础能力层。新的流程开发者不需要懂德语或法语,他只需要按规范写语言无关的流程逻辑,剩下的适配由配置和组件层自动完成。
国际化不是翻译几个按钮上的文字那么简单。
它是让你写的每一行流程逻辑,在世界上任何一个角落跑起来都不觉得异样。
作者:林焱
一个用自动化帮店铺跨越语言障碍的工程师
