影刀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-8importrequestsfromcharset_normalizerimportdetect# pip install charset-normalizerclassEncodingHandler:页面编码处理staticmethoddefdetect_and_decode(content_bytes): 自动检测编码并解码 比手动试编码靠谱得多 resultdetect(content_bytes)encodingresult[encoding]confidenceresult[confidence]print(f检测编码{encoding}置信度{confidence:.2f})returncontent_bytes.decode(encoding)staticmethoddeffetch_with_auto_decode(url):自动获取并解码网页resprequests.get(url,timeout30,headers{User-Agent:Mozilla/5.0})# 方法1从响应头获取编码content_typeresp.headers.get(Content-Type,)ifcharsetincontent_type:encodingcontent_type.split(charset)[-1].strip()try:returnresp.content.decode(encoding)except:pass# 方法2从HTML的meta标签获取importre meta_matchre.search(rbmeta[^]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 € textstr(text).strip()# 提取货币currencyUNKNOWNforsymbol,codeincls.CURRENCY_SYMBOLS.items():ifsymbolintext:currencycode texttext.replace(symbol,).strip()break# 如果没匹配到符号尝试匹配货币代码code_matchre.match(r([A-Z]{3})\s*,text)ifcode_match:currencycode_match.group(1)texttext[3:].strip()# 解析数字——处理欧洲格式逗号是小数点# 策略如果有逗号且后面正好2位数字 → 逗号是小数点欧洲格式# 否则 → 逗号是千分位美国格式texttext.strip()# 先去掉可能的货币名称后缀如 EURtextre.sub(r\s*[A-Z]{3}$,,text).strip()# 判断格式ifre.search(r,\d{2}$,text)and.notintext:# 欧洲格式1.299,99 → 1299.99texttext.replace(.,).replace(,,.)elifre.search(r,\d{3},text):# 美国格式1,299.99 → 1299.99texttext.replace(,,)try:amountfloat(text)exceptValueError:returnNonereturn{amount:amount,currency:currency}classmethoddefconvert_to_cny(cls,amount,currency,ratesNone): 转换为人民币需要汇率数据 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}raterates.get(currency)ifrateisNone:returnNonereturnround(amount*rate,2)# 测试parserCurrencyParser()test_prices[$1,299.99,€899,00,£1,200.00,JPY 12,800,HK$ 1,580.00,1.299,99 €,]forpintest_prices:resultparser.parse_price(p)cnyparser.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_strstr(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处理英文月份名称lowerdate_str.lower()formonth_name,month_numincls.MONTHS.items():ifmonth_nameinlower:# 提取天数day_matchre.search(r\b(\d{1,2})\b,lower)# 提取年份year_matchre.search(r\b(\d{4})\b,lower)ifday_matchandyear_match:dayint(day_match.group(1))yearint(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 textstr(text).strip()# 去掉中文单位textre.sub(r[万億],,text)# 找到小数点/逗号comma_counttext.count(,)dot_counttext.count(.)space_counttext.count( )# 处理空格千分位1 299,99ifspace_count0:texttext.replace( ,)# 判断哪个是小数点ifcomma_count1anddot_count0:# 只有一个逗号iflen(text.split(,)[-1])2orlen(text.split(,)[-1])3:# 逗号后2或3位 → 逗号是小数点texttext.replace(,,.)# 否则逗号是千分位如12,800elifdot_count1andcomma_count1:# 同时有逗号和点号dot_postext.rfind(.)comma_postext.rfind(,)ifdot_poscomma_pos:texttext.replace(,,).replace(.,.)else:texttext.replace(.,).replace(,,.)elifcomma_count1ordot_count1:texttext.replace(,,).replace(.,)try:returnfloat(text)exceptValueError:returnNone# 测试numbers[1,299.99,1.299,99,1 299,99,12,800,1,234,567.89]forninnumbers:resultNumberParser.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_infoCurrencyParser.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_textraw_data.get(rating,)rating_matchre.search(r(\d\.?\d*),str(rating_text))ifrating_match:processed[评分]float(rating_match.group(1))# 解析日期date_strraw_data.get(release_date,)dtDateParser.parse(date_str)ifdt:processed[上架日期]dt.strftime(%Y-%m-%d)returnprocesseddefgenerate_comparison_report(self,output_path):生成比价报告dfpd.DataFrame(self.products)# 按人民币价格排序dfdf.sort_values(人民币,ascendingTrue)withpd.ExcelWriter(output_path)aswriter:df.to_excel(writer,sheet_name比价表,indexFalse)# 按平台汇总platform_summarydf.groupby(平台).agg({商品名:count,人民币:[min,mean,max]})platform_summary.to_excel(writer,sheet_name平台汇总)returndf有什么坑坑1strptime不支持中文「年月日」datetime.strptime只支持英文格式代码。遇到2024年6月26日需要先用正则替换。text2024年6月26日textre.sub(r年|月,-,text).replace(日,)dtdatetime.strptime(text,%Y-%m-%d)坑2编码检测不是100%准确短文本如标题编码检测的准确率可能很低。charset_normalizer对你处理整个页面正文还行但对只有10个字的标题可能猜错。解决方法先以整个页面的编码为准如果个别字段乱码再单独处理。坑3汇率变化把外币转成人民币写死在代码里的汇率一个月后就不准了。解决方法从汇率API动态获取当日汇率。defget_exchange_rate(baseUSD):免费汇率APIurlfhttps://api.exchangerate-api.com/v4/latest/{base}resprequests.get(url,timeout10)returnresp.json()[rates]坑4地区限制Geo-blocking直接请求某些国外电商网站如日本亚马逊、韩国Coupang可能被301重定向到中国站甚至直接返回403。解决方法使用代理IP对应国家的IP、设置正确的Accept-Language请求头。总结国际化数据采集的两个核心敌人是编码和格式。用charset_normalizer自动检测编码、用多策略解析价格/日期/数字、汇率用API动态获取。别试图写一个「万能解析器」——针对每个目标网站单独写解析规则更靠谱。