先说结论:Shopee/Lazada 这类东南亚电商的采集,脚本卡住的原因通常不在解析逻辑,而在访问链路——出口地区是否与目标分站一致、会话能否保持稳定、请求频率是否克制。配置顺序比参数更重要:先定会话粒度,再选资源类型与地区,最后才落代码。
为什么这两个平台对访问链路更敏感
Shopee 和 Lazada 都是按国家分站运营:shopee.sg、shopee.my、shopee.co.id、lazada.co.th……各站商品结构相近,但价格、库存、活动、语言互不相通。跨境选品通常要同时盯多个站点比价,当出口 IP 地区与目标分站不匹配时,轻则被重定向到默认站点,重则直接限制访问。前者更隐蔽:拿到的是另一套价格和配送信息,数据错了你都不一定察觉。
有个容易被忽略的体感:同一款商品,本地直连和经海外出口请求,返回的币种、促销价可能完全不同。做价格监控时如果出口地区漂移,历史曲线会突然出现"断崖式降价",排查半天才发现是链路变了,不是市场变了。
第二个差异是频率控制。电商站点的接口判断比资讯类站点严格得多:数据中心 IP 段在这两个平台的成功率明显低于住宅类资源;高频顺序请求、翻页节奏过于规律,都容易被推向人工验证或直接断开。很多脚本本地直连测试一切正常,一上线大量超时,多数时候是链路问题而不是代码问题——排查顺序应该是先查出口,再查解析。
先定会话粒度,再谈参数
代理配置里第一个要做的决定不是选哪种资源,而是会话粒度:
- 轮换模式:每次请求换一个出口 IP,适合公开商品列表、价格、销量的批量抓取,把请求压力摊到大量出口上。
- 粘性会话(sticky session):同一会话在一段时间内固定同一出口,主流服务商普遍通过在用户名里追加 session 标识实现,一般可维持 5-30 分钟。翻页、Cookie 保持、登录态任务必须用它——否则列表翻到第二页出口就换了,Cookie 反复失效,验证码随之而来。
跨境选品比较省资源的组合:列表页用轮换,详情页和翻页链路用粘性。再进一步是按任务隔离:不同站点、不同任务类型用不同的 session 标识前缀,互不共享——一个任务触发了限制,不会连坐其他任务。
并发设计也在此一并定下:线程或协程数不要超过出口数的一半,单出口请求间隔保持在秒级。并发不是越大越好,出口质量撑不住时,堆并发只会加速整个任务被限制。
requests:代理、超时、重试一次写对
import random, string, requests
GW = "gw.example.com:8000" # 以服务商实际网关地址为准
def make_proxies(sid):
proxy = f"http://USER-{sid}:PASS@{GW}"
return {"http": proxy, "https": proxy} # 两个键都要写
def new_session_id(task="sg1"):
rand = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
return f"{task}-{rand}" # 任务前缀 + 随机串
session = requests.Session()
session.trust_env = False # 忽略系统残留的代理环境变量
session.proxies.update(make_proxies(new_session_id()))
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0 Safari/537.36",
"Accept-Language": "en-SG,en;q=0.9",
})
def fetch(url, retries=3):
for _ in range(retries):
try:
r = session.get(url, timeout=(5, 15))
if r.status_code == 200:
return r
except requests.RequestException:
pass
session.proxies.update(make_proxies(new_session_id())) # 换标识=换出口
return None四个细节:
http和https两个键都要写。只写http键时,HTTPS 请求会直连出去,表现为"代理生效了一半"。timeout传元组(连接超时, 读取超时)。只传一个值时,慢响应被误判为失败,重试反而放大链路压力。- 重试要换 session 标识、重建链路,而不是在已被限制的出口上反复硬试——后者只会让这个出口更快被标记。
trust_env = False排除系统残留的http_proxy干扰。调试期可以先请求一次 IP 回显接口,确认脚本拿到的真实出口和预期一致。
浏览器自动化:Playwright 与环境自洽
需要渲染页面或保持登录态的采集,普遍改用 Playwright,代理在浏览器层配置:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": f"http://{GW}",
"username": "USER-session-abc8k2",
"password": "PASS",
})
ctx = browser.new_context(
locale="en-MY",
timezone_id="Asia/Kuala_Lumpur",
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0 Safari/537.36",
)关键是访问环境自洽:出口在吉隆坡,locale、时区、语言偏好就保持同一地区。IP 在马来西亚、Accept-Language 却是 zh-CN、时区是 UTC,这种组合会明显推高异常判定的概率。同一个任务固定一套自洽配置,不要每换一个出口就换一套 UA——稳定一致的环境特征,比频繁变化更不容易触发异常判定。
如果页面仍返回验证页,正确顺序是先降低频率、更换出口地区观察,而不是急着堆自动化参数。另一个经验:粘性会话到点失效后,不要复用旧 context 直接换代理继续跑,应重建 context,让 Cookie 和会话一起换新。
高频踩坑清单
- 免费资源:成功率与稳定性都撑不起定时任务,失败重试消耗的时间往往超过付费成本,还可能把异常判定风险带进任务。
- 频率超限:单 IP 每秒十几个请求,再干净的出口也会被限制;按站点分别限速,把节奏压在合理区间,比事后补救有效。
- 只测成功不测时效:压测时记录每个出口的连续可用时长和失败类型(超时/重定向/验证页),只看成功率会漏掉"能用但很快失效"的出口。
- 代理串硬编码在各脚本里:换服务商或改认证方式要改动所有脚本。把拼接逻辑收进一个 config 模块,按"任务-地区"两维生成标识,后面扩任务只需要加配置。
- 没有任务级监控:给每个任务记成功率和平均延迟,掉到阈值以下先报警再人工介入,别等下游报表数据缺了一块才发现链路早就不稳。
FAQ
Q:一次任务大概需要多少 IP 量?
按"单 IP 每分钟请求数 × 任务时长 × 2-3 倍冗余"估算。更实际的做法是先用 50-100 个出口小规模跑一轮,观察限制触发情况和失败类型,再决定放量——比一开始按 IP 总量囤划算。
Q:动态住宅和静态住宅怎么分工?
公开商品、价格、评价列表用动态轮换;需要保持登录态的采集(店铺后台数据、会话型任务)用静态长会话。最容易出问题的组合是拿轮换 IP 去做登录态任务——每次出口都变,登录态撑不过两轮请求。
Q:物流轨迹这类查询场景有什么不同?
跨境物流信息查询是典型的读接口任务:单号查询频率低,但要求会话连贯,查一半换出口就可能拿不到完整轨迹。粘性会话配低频节奏即可,不需要大规模 IP 池。
Q:出口地区怎么选?
与目标分站同地区最稳:采集 shopee.my 用马来西亚出口,shopee.co.id 用印尼出口。多站点并行时按站点分池,各池独立限速与会话管理,不要全站共用一个出口地区。
