影刀RPA 国际网站数据采集:多语言多币种处理实战
2026/7/13 9:48:13 网站建设 项目流程

影刀RPA 国际网站数据采集:多语言多币种处理实战

作者:林焱


什么情况用

做跨境电商、外贸跟单、海淘比价,经常需要从国外网站采集数据。这些网站和国内的有几个显著区别:

  • 页面编码不是UTF-8(可能是Latin-1、Shift-JIS、EUC-KR)
  • 日期格式千奇百怪(Jun 26, 2024/26/06/2024/2024年6月26日
  • 价格带货币符号($1,299.99/€899,00/£1,200
  • 数字格式不统一(千分位逗号 vs 小数点逗号)

直接套用国内网站的采集逻辑,大概率翻车。

核心场景:从非中文网站采集数据,需要处理编码、日期、货币等国际化问题。


怎么做

第一步:编码处理——不是所有网站都是UTF-8

importrequestsfromcharset_normalizerimportdetect# pip install charset-normalizerclassEncodingHandler:"""页面编码处理"""@staticmethoddefdetect_and_decode(content_bytes):""" 自动检测编码并解码 比手动试编码靠谱得多 """result=detect(content_bytes)encoding=result["encoding"]confidence=result["confidence"]print(f"检测编码:{encoding}(置信度:{confidence:.2f})")returncontent_bytes.decode(encoding)@staticmethoddeffetch_with_auto_decode(url):"""自动获取并解码网页"""resp=requests.get(url,timeout=30,headers={"User-Agent":"Mozilla/5.0"})# 方法1:从响应头获取编码content_type=resp.headers.get("Content-Type","")if"charset="incontent_type:encoding=content_type.split("charset=")[-1].strip()try:returnresp.content.decode(encoding)except:pass# 方法2:从HTML的meta标签获取importre meta_match=re.search(rb'<meta[^>]+charset=["\']?([a-zA-Z0-9\-_]+)["\']?',resp.content[:2000]# 只搜索前2000字节)ifmeta_match:try:returnresp.content.decode(meta_match.group(1).decode())except:pass# 方法3:自动检测returnEncodingHandler.detect_and_decode(resp.content)

第二步:多币种价格解析

importrefromdecimalimportDecimalclassCurrencyParser:"""多币种价格解析器"""# 常见货币符号CURRENCY_SYMBOLS={"$":"USD","US$":"USD","€":"EUR","£":"GBP","¥":"JPY","¥":"CNY","A$":"AUD","C$":"CAD","HK$":"HKD","S$":"SGD","₩":"KRW","₹":"INR","R$":"BRL",}@classmethoddefparse_price(cls,text):""" 解析各种格式的价格文本 支持: "$1,299.99", "€899,00", "£1,200", "JPY 12,800", "1.299,99 €" """text=str(text).strip()# 提取货币currency="UNKNOWN"forsymbol,codeincls.CURRENCY_SYMBOLS.items():ifsymbolintext:currency=code text=text.replace(symbol,"").strip()break# 如果没匹配到符号,尝试匹配货币代码code_match=re.match(r'([A-Z]{3})\s*',text)ifcode_match:currency=code_match.group(1)text=text[3:].strip()# 解析数字——处理欧洲格式(逗号是小数点)# 策略:如果有逗号且后面正好2位数字 → 逗号是小数点(欧洲格式)# 否则 → 逗号是千分位(美国格式)text=text.strip()# 先去掉可能的货币名称后缀(如 "EUR")text=re.sub(r'\s*[A-Z]{3}$','',text).strip()# 判断格式ifre.search(r',\d{2}$',text)and'.'notintext:# 欧洲格式:1.299,99 → 1299.99text=text.replace('.','').replace(',','.')elifre.search(r',\d{3}',text):# 美国格式:1,299.99 → 1299.99text=text.replace(',','')try:amount=float(text)exceptValueError:returnNonereturn{"amount":amount,"currency":currency}@classmethoddefconvert_to_cny(cls,amount,currency,rates=None):""" 转换为人民币(需要汇率数据) rates: {"USD": 7.25, "EUR": 7.85, ...} """ifratesisNone:# 示例汇率(需要从实时API获取)rates={"USD":7.25,"EUR":7.85,"GBP":9.20,"JPY":0.048,"KRW":0.0054,"AUD":4.72,"HKD":0.93,"SGD":5.38,"INR":0.087}rate=rates.get(currency)ifrateisNone:returnNonereturnround(amount*rate,2)# 测试parser=CurrencyParser()test_prices=["$1,299.99","€899,00","£1,200.00","JPY 12,800","HK$ 1,580.00","1.299,99 €",]forpintest_prices:result=parser.parse_price(p)cny=parser.convert_to_cny(result["amount"],result["currency"])print(f"{p:20s}{result['amount']:10.2f}{result['currency']:5s}≈ ¥{cny:.2f}")

第三步:多格式日期解析

fromdatetimeimportdatetimeimportreclassDateParser:"""国际日期格式解析"""# 月份名称映射MONTHS={"january":1,"february":2,"march":3,"april":4,"may":5,"june":6,"july":7,"august":8,"september":9,"october":10,"november":11,"december":12,"jan":1,"feb":2,"mar":3,"apr":4,"jun":6,"jul":7,"aug":8,"sep":9,"oct":10,"nov":11,"dec":12,# 其他语言"janvier":1,"février":2,"mars":3,"avril":4,"mai":5,"juin":6,"juillet":7,"août":8,"septembre":9,"octobre":10,"novembre":11,"décembre":12,}@classmethoddefparse(cls,date_str):""" 解析各种日期格式 "Jun 26, 2024" / "26/06/2024" / "2024-06-26" / "26.06.2024" """date_str=str(date_str).strip()ifnotdate_str:returnNone# 方法1:尝试标准格式iso_formats=["%Y-%m-%d","%Y/%m/%d","%d/%m/%Y","%m/%d/%Y","%d-%m-%Y","%m-%d-%Y","%d.%m.%Y","%Y.%m.%d","%d %b %Y","%b %d, %Y","%B %d, %Y",]forfmtiniso_formats:try:returndatetime.strptime(date_str,fmt)exceptValueError:continue# 方法2:处理英文月份名称lower=date_str.lower()formonth_name,month_numincls.MONTHS.items():ifmonth_nameinlower:# 提取天数day_match=re.search(r'\b(\d{1,2})\b',lower)# 提取年份year_match=re.search(r'\b(\d{4})\b',lower)ifday_matchandyear_match:day=int(day_match.group(1))year=int(year_match.group(1))try:returndatetime(year,month_num,day)exceptValueError:pass# 方法3:纯数字 20240626ifdate_str.isdigit()andlen(date_str)==8:try:returndatetime.strptime(date_str,"%Y%m%d")except:passreturnNone# 实在解析不了# 测试dates=["Jun 26, 2024","26/06/2024","2024-06-26","26.06.2024","20240626","26 June 2024"]fordindates:print(f"{d:20s}{DateParser.parse(d)}")

第四步:数字格式国际化

classNumberParser:"""国际化数字格式解析"""@staticmethoddefparse_number(text):""" 解析各种格式的数字 "1,299.99" (US) → 1299.99 "1.299,99" (EU) → 1299.99 "1 299,99" (FR) → 1299.99 "12,800" (JP) → 12800 """text=str(text).strip()# 去掉中文单位text=re.sub(r'[万億]','',text)# 找到小数点/逗号comma_count=text.count(',')dot_count=text.count('.')space_count=text.count(' ')# 处理空格千分位(1 299,99)ifspace_count>0:text=text.replace(' ','')# 判断哪个是小数点ifcomma_count==1anddot_count==0:# 只有一个逗号iflen(text.split(',')[-1])==2orlen(text.split(',')[-1])==3:# 逗号后2或3位 → 逗号是小数点text=text.replace(',','.')# 否则逗号是千分位(如12,800)elifdot_count==1andcomma_count==1:# 同时有逗号和点号dot_pos=text.rfind('.')comma_pos=text.rfind(',')ifdot_pos>comma_pos:text=text.replace(',','').replace('.','.')else:text=text.replace('.','').replace(',','.')elifcomma_count>1ordot_count>1:text=text.replace(',','').replace('.','')try:returnfloat(text)exceptValueError:returnNone# 测试numbers=["1,299.99","1.299,99","1 299,99","12,800","1,234,567.89"]forninnumbers:result=NumberParser.parse_number(n)print(f"{n:20s}{result}")

第五步:整合——跨境比价采集器

classCrossBorderScraper:"""跨境商品比价采集"""def__init__(self):self.products=[]defscrape_amazon_us(self,keyword):"""采集Amazon美国站(示例框架,实际需要配合影刀网页采集)"""# 用影刀的网页操作获取商品信息# 然后在这里做数据处理passdefprocess_product(self,raw_data):"""处理单条商品数据"""processed={"平台":raw_data.get("platform",""),"商品名":raw_data.get("name",""),"原价文本":raw_data.get("price_text",""),"原价":None,"货币":None,"人民币":None,"评分":None,"上架日期":None,}# 解析价格price_info=CurrencyParser.parse_price(raw_data.get("price_text",""))ifprice_info:processed["原价"]=price_info["amount"]processed["货币"]=price_info["currency"]processed["人民币"]=CurrencyParser.convert_to_cny(price_info["amount"],price_info["currency"])# 解析评分rating_text=raw_data.get("rating","")rating_match=re.search(r'(\d+\.?\d*)',str(rating_text))ifrating_match:processed["评分"]=float(rating_match.group(1))# 解析日期date_str=raw_data.get("release_date","")dt=DateParser.parse(date_str)ifdt:processed["上架日期"]=dt.strftime("%Y-%m-%d")returnprocesseddefgenerate_comparison_report(self,output_path):"""生成比价报告"""df=pd.DataFrame(self.products)# 按人民币价格排序df=df.sort_values("人民币",ascending=True)withpd.ExcelWriter(output_path)aswriter:df.to_excel(writer,sheet_name="比价表",index=False)# 按平台汇总platform_summary=df.groupby("平台").agg({"商品名":"count","人民币":["min","mean","max"]})platform_summary.to_excel(writer,sheet_name="平台汇总")returndf

有什么坑

坑1:strptime不支持中文「年月日」

datetime.strptime只支持英文格式代码。遇到2024年6月26日需要先用正则替换。

text="2024年6月26日"text=re.sub(r'年|月','-',text).replace('日','')dt=datetime.strptime(text,"%Y-%m-%d")

坑2:编码检测不是100%准确

短文本(如标题)编码检测的准确率可能很低。charset_normalizer对你处理整个页面正文还行,但对只有10个字的标题可能猜错。

解决方法:先以整个页面的编码为准,如果个别字段乱码再单独处理。

坑3:汇率变化

把外币转成人民币写死在代码里的汇率,一个月后就不准了。

解决方法:从汇率API动态获取当日汇率。

defget_exchange_rate(base="USD"):"""免费汇率API"""url=f"https://api.exchangerate-api.com/v4/latest/{base}"resp=requests.get(url,timeout=10)returnresp.json()["rates"]

坑4:地区限制(Geo-blocking)

直接请求某些国外电商网站(如日本亚马逊、韩国Coupang),可能被301重定向到中国站,甚至直接返回403。

解决方法:使用代理IP(对应国家的IP)、设置正确的Accept-Language请求头。


总结:国际化数据采集的两个核心敌人是编码和格式。用charset_normalizer自动检测编码、用多策略解析价格/日期/数字、汇率用API动态获取。别试图写一个「万能解析器」——针对每个目标网站单独写解析规则更靠谱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询