|
一个人,两台电脑,十几个店铺后台,早上八点爬起来先看有没有平台通知邮件。这是很多跨境个人卖家的日常。也有人更狠,一台笔记本加两块显示器,硬扛三十个店铺。 翻车的时候,多数人第一反应是产品出了问题、物流出了问题、差评来了。真实情况往往更朴素:你的这几个店,在平台的设备图谱里长着同一张脸。 个人卖家选环境工具,逻辑跟团队是反过来的。团队有预算、有专人维护、有流程规范,缺的是规模化能力;个人卖家反过来,账号数量其实不多,缺的是钱、是时间、是没有人替你做日常巡检。所以不要一上来问"哪个工具功能多",先问三个问题:我这批店一年愿意为环境花多少钱;我能接受多久做一次检查;我能不能承受"每次启动换一套指纹"带来的不确定性。 第三个问题的答案,几乎永远是"不能"。 量级上也别贪大。一个人管三个店和管三十个店,需要的根本不是同一套东西。像MostLogin这类工具,基础版当前免费、5个窗口起步,对刚开第二第三个店的人来说,这个量级卡在"够用且不花钱"的位置,比一上来买几百个窗口的套餐实在得多。先把三个店跑顺,再谈三十个店。 一、个人卖家的选型逻辑跟团队是倒过来的 1.1三类使用者的需求差异 下面这张表是我这几年跟卖家打交道总结出来的。注意看"失败代价"这一行,它决定了后面所有的配置选择。 个人卖家那一行,"运维人力:零"。这一条比什么都重要。凡是要求你每天手动维护的方案,对个人卖家来说都是假方案,因为你坚持不了一个月。 1.2一年到底要花多少钱 很多人只算工具订阅费,忘了代理才是大头。按12个月算一笔账,心里就有数了。 工具年费区间是市场同类档位的常见报价区间,MostLogin各档具体价格以官网当期公示页为准,别照着第三方文章的数字下单。代理单价按静态住宅独享IP的常见区间¥40–60/个/月估算,不同服务商、不同地区差异很大,量大通常能谈到更低。 看这张表能得出一个反直觉的结论:账号从10个涨到30个,单账号年成本几乎没变,甚至略降。真正的成本拐点不在工具,在于你有没有为每个账号配独立且稳定的IP。所以个人卖家的钱应该优先砸在代理上,工具选够用的那一档就行。 二、个人卖家常踩的六个坑 2.1一机多环境,代理却共用一个 这个坑排第一,因为太多人踩。买了环境隔离工具,开了十个配置,然后代理那一栏填的是同一个住宅IP,理由是"省钱"。 从平台视角看,这等于十个账号的出口IP完全一致。IP是权重极高的关联信号,十个账号共用一个IP,前面做的指纹隔离基本白做。业内对TikTok这类移动优先平台的普遍说法是同一IP下账号数要压得很低,常见建议是不超过3个。 正确的做法是"一号一环境一IP"。静态住宅独享优先,排在其后的是长期租用的移动IP。动态住宅池要看轮换策略,如果每次请求都换IP,对店铺后台这种需要长期稳定会话的场景反而是负担。 2.2指纹每次启动都变 不少工具默认"每次启动随机生成新指纹",听起来很安全,实际上对个人卖家是灾难。 想一下真实用户的行为:一个人用一台电脑,他的屏幕分辨率、时区、字体列表、硬件并发数,半年都不会变一次。你一个环境每启动一次就换一套,等于在告诉平台"这个账号背后是个每隔几小时就换一台电脑的人"。 更麻烦的是排错。账号一旦进审核,你需要向平台证明这是一个长期、稳定的经营主体。如果环境参数每周都在跳,你自己都解释不清。 个人卖家要的是"一个环境长期稳定不变",不是"每次启动换一套"。 2.3时区、语言跟IP地理位置对不上 IP在洛杉矶,系统时区却是北京时间,浏览器语言是zh-CN。这种组合在检测脚本眼里非常扎眼。 Intl.DateTimeFormat().resolvedOptions().timeZone返回Asia/Shanghai,navigator.language返回zh-CN,而出口IP的ASN归属是美国加州某家宽带运营商。三个字段互相打架,任何一个做地理一致性校验的模型都会给这条会话打高分异常标记。 时区要跟IP的城市对齐,语言要跟目标市场对齐。做美国站,America/New_York或America/Los_Angeles配en-US;做德国站,Europe/Berlin配de-DE。这里还有个细节:时区偏移量要考虑夏令时,America/New_York在夏令时是UTC-4,冬令时是UTC-5,选IANA时区名而不是手动填偏移值,浏览器会自己处理。 2.4 Cookie和本地存储串环境 表现是:A店的环境里登录了B店的账号,或者退出A店之后B店的登录态莫名还在。 根因通常是图省事,在一个配置里登录过多个账号,或者用了工具之外的普通浏览器临时开了一下后台。一旦Cookie串了,后面再怎么隔离都补不回来,只能推倒重来。 补救办法是初始化时就定死规矩:一个配置从创建到废弃,只碰一个账号。临时要查看某个后台,用对应的配置,不要就近找一个"差不多"的打开。 2.5被封过的设备继续用 有人会把已经出过问题的配置改名、换代理,继续拿来做新店。这属于自我安慰。 平台记录的不只是当前指纹,还有历史行为序列。一个曾经触发过处罚的环境,其关联的设备特征、IP段、支付方式、主体信息,都会被记进风控图谱。换代理改指纹,改不掉业务侧的历史记录。 出过问题的环境,直接废弃,从干净的主体、干净的凭证、干净的网络重新开始。这里说的"干净"指的是真实独立:独立法律主体、独立的企业邮箱与电话、独立的银行账户与收款方式。 2.6同一套图和文案跨店复用 这条跟技术无关,却是格外容易被判关联的一条。 同样是那张白底图,同样的五点描述,只是把品牌名换了一下,标题句式结构一模一样,连typo都复制过去了。平台的图像相似度模型和文本相似度模型对这种重复极其敏感,其敏感度甚至高于IP相同。 不同店铺要有差异化的Listing:重新拍摄或至少重新构图、重新撰写文案结构、独立的客服模板与话术、独立的售后流程。这不是形式主义,而是真实独立经营的应有之义。 三、指纹怎么被采走,环境隔离怎么挡回去 3.1指纹采集维度清单 平台拿不到你的身份证号,但它能用几十个字段拼出一个"设备身份"。下表列出常见维度和对应的取值建议。 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | navigator.hardwareConcurrency | | | | | | |
这张表可以打印出来贴在显示器边上,建一个新环境就照着核对一遍,五分钟的事,能省掉后面大量麻烦。 3.2 Profile级隔离到底隔离了什么 "环境隔离"这四个字听着玄,落到实现上就是给每个配置分配一套完全独立的存储与网络栈。 存储侧要隔离的东西有五类:Cookie、LocalStorage、SessionStorage、IndexedDB、浏览器缓存与磁盘缓存。这五类数据只要有一类共享,平台通过在A环境写入标识再在B环境读取,就能确认两个环境同源。这也是为什么用Chrome的多用户profile或者无痕窗口都不行,它们解决的是用户数据目录的问题,不解决指纹与网络栈的问题。 网络侧要隔离的是代理隧道本身。每个配置应该有自己独立的HTTP(S)或SOCKS5隧道,DNS解析也要走这条隧道,否则会出现"IP在美国、DNS查询却发给了本地运营商"的DNS泄漏。 会话层还有一个容易忽略的点:TLS会话票据与HTTP/2连接复用。如果多个环境共用同一条底层连接,即使IP不同,也会留下可关联的痕迹。成熟的方案会给每个环境单独维护连接池。 3.3源码级hook、插件注入、参数覆盖,差别在哪 第一种是参数覆盖 原理是在页面加载前用脚本改掉navigator上的若干字段,比如把userAgent换成目标值。成本低,见效快,问题也明显:它只改了你能想到的那几个字段。你改了UA,忘了navigator.platform;改了platform,忘了navigator.hardwareConcurrency;改了并发数,忘了navigator.deviceMemory。更麻烦的是navigator.userAgentData(ClientHints)里还有一套独立的结构化数据,很多覆盖脚本压根不处理。结果就是字段之间互相矛盾,UA说是Windows上的Chrome,其他字段却处处露出macOS的痕迹。这种自相矛盾在检测模型里比"指纹普通但自洽"要危险得多。 第二种是插件注入 通过扩展在渲染进程里注入脚本,能覆盖的面比纯参数覆盖广一些,也可以拦截Canvas的toDataURL。但注入脚本本身是可见的:它会出现在JS执行栈里,会引入可观测的Error.stack特征,函数调用耗时分布也和普通浏览器不一样。有经验的检测方只要看一眼调用栈深度或者原型链上多出来的属性,就能判断这个环境被改过。另外,扩展注入依赖加载时机,页面首屏的早期脚本有可能跑在注入之前,出现"前几帧真实、后面被改"的时间窗。 第三种是源码级hook 做法是维护一份Chromium的定制分支,直接改C++源码,在指纹采集API的实现层做挂钩,让它返回与环境设定一致的数值。MostLogin走的就是这条路,团队在渲染引擎源码层对Canvas、WebGL、WebRTC、AudioContext这些采集点做了改写。 这条路的价值在于自洽。因为改动发生在API的实现内部,而不是在JS层打补丁,所以JS执行栈干净,渲染管线的行为与真实设备一致,字段之间不会互相打架。代价是维护成本高,每次上游Chromium发版都要重新合并补丁,这也是为什么不同工具的跟进速度差异很大。 总之,判断一个环境是否可信,不看它有多少个开关,看它的字段能不能互相印证。 3.4为什么"每次启动换一套指纹"是错的 平台侧的模型不是只看单次会话的指纹值,它看的是一段时间内的行为序列:这个设备身份第一次出现是什么时候,之后的登录时间分布如何,操作节奏是否连续,指纹值是否有过跳变。 一个稳定不变的指纹,即使某几项参数不够"独特",只要它长期一致、行为合理,模型给它的风险分并不高。反倒是频繁跳变的指纹,会被归入"疑似自动化环境"这一类。 所以正确的策略是:建环境时一次性把参数配好,之后锁死,只在有明确理由时(比如换了常驻城市、换了设备)才调整。指纹应该是你的"数字身份证",不是"数字迷彩服"。 四、3个店、10个店、30个店怎么配 4.1三档配置参数清单 "台账"这两个字对个人卖家尤其重要。一个人管三十个店,靠脑子是记不住哪个环境配的哪个IP、上次巡检是什么时候的。用一张表记下来,环境名、对应店铺、代理IP、地区、创建日期、最近检查日期,六列就够。 4.2代理怎么选 三类代理的取舍: 数据中心代理便宜、带宽足,但IP段公开可查,平台通常会给这类IP更高的初始风险分。适合做市场调研、竞品页面查看这类不涉及账号登录的事。 住宅代理来自真实家庭宽带的ISP分配记录,看起来与普通用户无异,是店铺后台这类场景的主流选择。独享优于共享,静态优于频繁轮换。 移动代理是蜂窝网络的出口IP,对移动优先的平台(TikTok、Instagram)匹配度更高,价格也偏高。只在必要场景用。 判断标准很简单:这个账号需要长期稳定的会话吗?需要就选静态独享住宅,别图便宜选动态轮换池。 4.3云手机什么时候才值得上,什么时候纯属浪费 先说浪费的情况。如果你的业务全在网页端,亚马逊卖家中心、eBay后台、Shopee卖家中心、TikTokShop的网页版,那上云手机就是把钱扔水里。这些场景指纹浏览器完全够用,一个网页环境几毛钱的成本,跟云手机不在一个量级。 值得上的情况只有一类:你要跑的是原生移动App。TikTokApp、WhatsApp、Telegram、Instagram这些应用会读取设备型号、系统版本、AndroidID、广告ID、SIM与运营商信息、传感器与陀螺仪数据,网页端指纹技术根本覆盖不到这些维度。这时候需要的是真实Android实例。 云手机与x86模拟器的区别也在这里。模拟器跑在x86架构上靠指令翻译执行ARM代码,IMEI、MAC、基带、传感器数据都是模拟出来的,且会留下明显的模拟器特征(特定的构建指纹、特定的驱动名)。云手机由机房的真实安卓设备提供计算、内存、存储,能还原IMEI、MAC、传感器这些硬件级细节,支持600多家全球运营商的SIM参数配置,还带ADB与root权限,能跑自定义脚本。 成本上,云手机按月订阅是$25/月/台,按12个月付是$210(每月$17.5);按需租赁$0.1/15分钟/台,单日上限$1.6,另有$0.02–0.03/24小时的环境费。对只需要偶尔用一下App的场景,按需租赁明显更划算,用半小时就关,一天封顶也就十来块人民币。 五、操作示例:一个人也要有的轻量自动化 个人卖家没有运维,但可以用脚本把重复的检查工作自动化。下面三段代码都是能直接落地的。 5.1用本地API批量启停配置 先说一个容易忽略的细节:本地API有速率限制,随套餐不同,基础版2次/秒、进阶版5次/秒、专业版10次/秒、企业版20次/秒。批量操作不控制节奏,就会被限流。 //batch_start.js:批量启动配置并按限速排队 ) ^- @# L1 u5 o" V* ^) ?
//说明:接口路径与字段名以MostLogin官方API文档当前版本为准
$ D5 p0 Q" s5 g9 [# E3 P: G! r/ ^! NconstBASE='http://127.0.0.1:30898'; , f2 U) e; Y: l9 l8 s/ M
constTOKEN=process.env.MOSTLOGIN_TOKEN;//令牌等同密码,不要硬编码进仓库
- f# d! w1 \8 E7 h$ |
1 k9 f! n2 `/ kconstsleep=(ms)=>newPromise((r)=>setTimeout(r,ms)); 4 L: D5 |0 o3 J8 D4 X
$ u9 X9 a" u8 k8 a% z4 I M8 O
//基础版限速2次/秒,留100ms余量 * _/ Z3 E# O( B1 f: {
constRATE={intervalMs:600,perBatch:1};
! b+ v: f# D' y& @/ Y1 v. V" z" \: Z2 o" }% o2 n
asyncfunctionlaunchProfile(profileId){
$ `8 X' t' Z; H I: mconstres=awaitfetch(`${BASE}/api/v1/browser/start`,{
8 b V! x# e+ ~9 N9 b( c/ e- Lmethod:'POST', $ s6 k ?. u1 O& c" ~7 i) U
headers:{ ' a: `7 j* i; q- R% {
'Content-Type':'application/json',
8 F+ [/ ?6 @, N& n: q7 Q( [, ~Authorization:`Bearer${TOKEN}`, + P: Z T K0 e& y4 `
},
' U" c: B' ~( l/ D/ pbody:JSON.stringify({profileId,headless:false}), ) ~% x) X4 s. i% ~( |
});
+ F6 G1 V4 t; H* I! C& N' Uif(!res.ok)thrownewError(`启动失败${profileId}:HTTP${res.status}`);
0 E7 h5 i$ a- K% ]! @const{data}=awaitres.json(); + n6 ?2 a6 y, Y3 Y
return{profileId,debugPort:data.debugPort,ws:data.ws}; " r" D. Q5 r6 f
} 4 a. d3 ?- |, Q, i4 {' r0 E' L
" u( \3 O e# ?- L( Q+ C6 TasyncfunctionstartAll(ids){
/ ]! V& u& W9 r% t' ]0 }constopened=[];
& K V! Z! ~$ O7 b8 [. nfor(constidofids){ 8 V4 Y: j+ F: p! F- o1 }5 \/ h
try{ , h% U% A5 B2 u( i3 L) ]
constinfo=awaitlaunchProfile(id); 5 A$ j( J7 A$ B1 }- V: g D( U
opened.push(info);
" J! O9 K4 P% G& b8 wconsole.log(`[ok]${id}->CDP端口${info.debugPort}`);
# P- R, _: A4 e! Q9 X! [( j}catch(e){ * M8 ?) P; f0 c, ]3 ~5 A$ M6 z" n
console.error(`[skip]${id}  {e.message}`);
f; B* y0 N# x4 o5 m! b) Z" o+ Z} 0 J2 \" n$ X. [1 b* a+ j: H7 f
awaitsleep(RATE.intervalMs); / W1 F x9 l$ q/ ~# b' h1 |
} . f4 }! o8 n- [9 S' Y) |- ]
returnopened; 6 Y; }) x i6 v0 f' ~7 t1 B
} 6 p/ |7 T6 U- K& q, F' _/ F
' r; h: U( g% k! G- b; k# h
//收工后统一关闭,避免挂着十几个进程占内存
$ I. r8 C- x7 L/ N4 Q; OasyncfunctionstopAll(ids){
- I) m$ ?" C% l& @$ b* U8 ^0 {5 Pfor(constidofids){
& b1 X3 ~. H# x: U) P1 F4 wawaitfetch(`${BASE}/api/v1/browser/stop`,{
+ z5 o* W* ^( h6 O9 w) Q% B* pmethod:'POST',
- Y1 @* Z; A$ t5 f- Hheaders:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},
. [( k7 f* G" Q" qbody:JSON.stringify({profileId:id}),
6 e1 [* `, q4 h/ n( r}); . i. F9 v6 h+ E S1 ]' p4 j: \ ^
awaitsleep(RATE.intervalMs);
9 @5 x1 R6 C% ~}
1 Y( W, P1 g/ I, V+ {}
5 S3 Z& _) {6 Q( a9 z) v( i0 y0 }4 Y& X1 s- D2 ^4 t/ ?# r! B
startAll(['ebay-us-01','ebay-us-02','etsy-uk-01']).then((list)=>{ ( W) |7 R! y' i# h; F$ _
//把debugPort写进台账,后面的巡检脚本要用
& K) ?) h6 f9 bconsole.log(JSON.stringify(list,null,2)); M: \% Y- [- e, V
}); 5.2批量更新代理 换代理服务商、或者季度轮换IP的时候,逐个手工改三十个配置会改到崩溃。用API批量刷一遍,几分钟的事。 //update_proxy.js:从CSV台账批量回填代理,并做格式预校验
/ u: U. l2 a ~. Rimportfsfrom'node:fs';+ l4 a4 ?1 ^* \$ B
; W, l0 S- _6 dconstBASE='http://127.0.0.1:30898';* M0 d; R' m7 l* ~+ ^. c
constTOKEN=process.env.MOSTLOGIN_TOKEN;+ L+ Y# Y u. `1 t
6 S; ~' B' D8 p* ]4 c9 h! O4 H//台账格式:profileId,host,port,username,password,protocol
& Y$ v% _# l; T; Wconstrows=fs
& I1 j, {/ H2 ]. ~.readFileSync('proxies.csv','utf8')5 g8 y+ Z9 d7 d1 n
.trim()0 o3 t7 J8 Y$ e
.split('\n')
6 o' _2 F3 V7 f" Q& d.slice(1). j7 b7 j6 [+ T5 q$ X, f3 q
.map((line)=>line.split(','));9 ?7 s& D: }% @6 D* @
5 m7 M# e+ i# D) s7 o9 p8 D
constPROTOCOL=newSet(['http','https','socks5']);/ B; t \. |- n" B/ N2 D
0 ]2 {; b9 [" e$ Y2 }8 kfunctionvalidate([id,host,port,user,pass,protocol]){5 o4 X! Z2 ]+ |/ G; k U. P
if(!PROTOCOL.has((protocol||'').trim()))return`${id}:协议只支持http/https/socks5`;
) Q2 z2 h' j) Z/ r2 nif(!/^\d{2,5}$/.test((port||'').trim()))return`${id}:端口不合法`;' F7 V a1 @; _- M* W9 p
if(!host||!host.includes('.'))return`${id}:主机地址不合法`;
5 f/ K N" O& e6 ? z6 y0 w* ?returnnull;0 [7 m& P$ V. l5 s/ V. M
}
+ p" ~/ G) @- C4 U- O' ~0 J; E# k4 c7 i/ ?: s- u9 R: R
asyncfunctionupdateProxy(row){
~3 A2 [0 s" _* ^+ dconst[id,host,port,user,pass,protocol]=row.map((s)=>(s||'').trim());0 M1 Q$ a5 f" z
consterr=validate(row);
0 T& P# P$ V- ?6 m* Qif(err)returnconsole.error(`[skip]${err}`);
* k) R# g/ l, e4 L7 g& J. ], }
8 D3 N3 [5 `* a8 \; uconstres=awaitfetch(`${BASE}/api/v1/profile/update`,{
$ r; m, s- @# I& l! x, q8 zmethod:'POST',6 s$ X5 S# C& A; O" i' b! r! h& H# F
headers:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},
) |! @& J; F' E, j# Nbody:JSON.stringify({
7 T& n8 h# e. S4 K( | `profileId:id,* z+ B2 o& S/ h9 b) j0 Y
proxy:{host,port:Number(port),username:user,password:pass,protocol},
1 q4 b, n5 h4 e; ]}),1 i/ l* ~7 V+ ^0 H% M) G8 P% W5 b
});/ k2 N' U- o5 O- T% \' F
console.log(res.ok?`[ok]${id}`:`[fail]${id}:HTTP${res.status}`);
2 ^7 c2 g+ _9 t4 ]/ a+ Y}
' r. Z! O+ b& }: U V s4 N; o
# X1 r7 N" {& b: ~) F) {( m+ l( j(async()=>{7 P2 u9 U) x2 r) S; O" @# v( v
for(constrowofrows){
) k& W3 \5 \. j9 nawaitupdateProxy(row);, f( a& g( K9 c5 D$ r0 U% h
awaitnewPromise((r)=>setTimeout(r,600));//基础版限速2/秒
- ^! J6 n* ~$ z4 |" W}, b2 f7 P; I- ]2 p7 J9 ?7 Z2 i
})(); 注意一点:改完代理一定要重启对应环境并重新验证时区一致性。IP换了城市,时区没跟着改,就掉进2.3那个坑了。 5.3用Playwright挂CDP做每周巡检 这段是整套自动化里价值很高的一环。每周跑一次,检查两件事:WebRTC有没有泄漏本地地址,时区与IP归属地是否一致。 #weekly_check.py:挂CDP巡检,检查WebRTC泄漏与时区IP一致性 ) B7 V- N/ Q4 f& @4 s$ Z$ E
#依赖:pipinstallplaywrightrequests;接口路径以官方文档为准
, h2 K# I6 p7 aimportjson * ^3 W) e; W# y6 S4 |1 q: i' J: B/ z
importrequests / x7 j6 z" z+ t
fromplaywright.sync_apiimportsync_playwright + g/ ]7 |3 S3 C5 H: \
* j: f* K' Q9 n6 m
BASE="http://127.0.0.1:30898"
( e- F. X* b+ YTOKEN="YOUR_TOKEN"#生产环境请从环境变量读取 9 c6 c( [. S0 b* A- x; z0 y4 r
IP_GEO_API="https://<你的IP归属查询服务>/json"#换成自己用的查询端点
4 U0 F" W f" `( {& G5 I9 X4 F5 X( O; Z! ~, P) B$ N5 A7 R
PROFILES=["ebay-us-01","ebay-us-02","etsy-uk-01"]
- e) ^7 n* K' m/ C6 M
( T) h6 H" Y! O3 K2 c. M6 s+ F+ b( l#IP城市->期望IANA时区,做美国站与英国站的最小示例 " v4 r( y) N2 T; F, e2 ]
EXPECT_TZ={ & b& }- r; G* [( l. ^
"LosAngeles":"America/Los_Angeles", ) w7 A `! Z+ g% L8 y
"NewYork":"America/New_York",
: m7 u4 a6 l. o- \: X$ F"London":"Europe/London",
" E& p" F- ~' q3 d/ m: H}
9 b/ k' Q. H- d5 D. l* [# X" q/ h4 P7 z6 j1 w
7 T; v8 ?8 b* i/ f; Z8 A
defstart(profile_id): 4 Y/ [' @1 B( Q
r=requests.post( 7 w1 G% d$ ]& F: j$ s7 D4 k: V
f"{BASE}/api/v1/browser/start", ; `" z9 T, n' [- o1 n. [
headers={"Authorization":f"Bearer{TOKEN}","Content-Type":"application/json"},
$ ?) n$ g8 p) ~! ]json={"profileId":profile_id}, / q U7 `" A, o* |. a4 T0 L
timeout=20,
# o) v2 F3 D. u/ J# v$ \) " G! n8 v$ U# S$ x* w3 b' i
r.raise_for_status() : y! g- c2 [, @' Y, Q4 e$ D
returnr.json()["data"]["debugPort"] * M. K1 j& m9 W/ t5 f0 k' I% a
' j* Q% p5 w0 X% R
' }: D' b5 {& V( j5 w- Q% |
defwebrtc_leak(page):
# {5 C1 s2 C6 i7 T: O"""收集ICE候选里的地址,看有没有暴露内网或本机真实公网IP"""
9 j: k/ F' l+ R3 Treturnpage.evaluate("""()=>newPromise((resolve)=>{ / h( _& D5 f' x* z& m
constfound=newSet();
8 f9 l: h# t* ^* B( L; g* g7 Hconstpc=newRTCPeerConnection({iceServers:[]});
6 I: q j4 v0 k' Cpc.createDataChannel('probe');
9 ?* v- H- M0 d2 R4 W9 Cpc.onicecandidate=(e)=>{ 4 k" c& b+ c4 O1 O" q: E
if(e.candidate&&e.candidate.candidate){ - N; C" N4 s. {6 i. v
constm=/([0-9]{1,3}(\\.[0-9]{1,3}){3})/.exec(e.candidate.candidate);
( q/ u4 l0 q. ~* nif(m)found.add(m[1]); 0 D- r, u) _; ^# d: |
}
( L: `. ~/ i; D3 p! a" p9 r% ~# x};
: ^; m; R3 c9 n& cpc.createOffer().then(o=>pc.setLocalDescription(o));
' F& h% j6 q5 UsetTimeout(()=>{pc.close();resolve([...found]);},2500); 0 R) k1 |3 j% t' z6 L. y- b- ~( j
})""")
1 {" C9 ~, [! n+ I
) \$ Z: w. K! Y& s1 o
+ [+ S K% G" Q+ p; W' Y8 y* Idefcheck(profile_id): * p8 O, B8 E6 ], o, _
port=start(profile_id)
8 K; [7 I% f$ C# k; ^" Swithsync_playwright()asp: ; y7 J& ?2 y7 ^* q0 @
browser=p.chromium.connect_over_cdp(f"http://127.0.0.1:{port}")
- Y4 o$ h/ x* G4 Rctx=browser.contexts[0]
7 L- t$ ]3 | _# F/ k+ I ~1 Vpage=ctx.new_page() 6 |; i! m- c$ E& K
page.goto("about:blank")
! a8 O- P7 ?. o/ i, g6 p
/ w" Y) c2 I- f. D; stz=page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone")
7 ~ e+ k& e& S R( j4 a) H" @lang=page.evaluate("navigator.language") * t# ~" r \. M0 Z% ~
ua=page.evaluate("navigator.userAgent")
7 _1 L5 U# q0 F! n1 c/ F3 A5 fplatform=page.evaluate("navigator.platform")
- a9 S3 P4 m1 V3 Eaddrs=webrtc_leak(page)
4 E3 O# h$ j$ @+ ^! w8 ?3 |& S. P
( {$ [- ?( z6 g* dgeo=requests.get(IP_GEO_API,timeout=15).json() % l5 t* ?2 e* o% k7 f k9 G4 G# e/ o
city,ip=geo.get("city"),geo.get("ip")
. a, u5 G. C) [, M/ _* v# U% J
. N% i) C* ~! t/ M, `& m#内网地址属于正常,公网地址必须与代理出口一致
. b# o2 f7 W5 e" K) V& q/ Mprivate=[aforainaddrsifa.startswith(("192.168.","10.","172.16.","127."))]
5 D# P7 Q6 `& @( `. w( vleaked=[aforainaddrsifanotinprivateanda!=ip] ; j V' `; o0 `! f& t* w
2 X+ v2 J! P' u- g
report={
; n/ J& b4 H$ b& _# B% z9 W"profile":profile_id,
, S! f1 I( K/ ^. j D8 u5 c( l. E"exit_ip":ip, ) C' O* m; `& ?$ v
"city":city,
9 k7 n$ s; W& H5 y! b/ |"timezone":tz,
, J$ P- C2 u0 X. U6 v"expect_timezone":EXPECT_TZ.get(city,"未配置映射,需人工核对"), 4 ~. D& k7 K+ r R
"language":lang, 2 M( X- D' H5 d0 r* r6 i3 I8 m% y
"platform":platform, : F4 s& \4 v/ H
"ua":ua[:60], 8 F6 x) d% R7 E$ v9 V
"webrtc_private_candidates":private,
* |# f' x2 k z, `; ?/ J"webrtc_leaked":leaked, 3 @- y# l, o" q
"tz_match":tz==EXPECT_TZ.get(city), # b/ o" z! w4 Q4 }: ~9 a! P; W# H
"ua_platform_consistent"  "Win"inplatformand"Windows"inua) 0 S+ o/ h7 y+ A/ {
or("Mac"inplatformand"Macintosh"inua), 6 c$ ?! f7 |4 @# B
}
! O- S' y% i9 pbrowser.close()
! r& b) k! S! i `# C* K7 L/ [/ Breturnreport ! }" w' j7 _6 O, A3 ]$ }6 G
' V5 v1 u8 z6 M' Q, i
# T, Q5 L: v5 A x/ B: ~7 X" w
if__name__=="__main__":
! P3 q W0 X( m4 ]results=[check(pid)forpidinPROFILES]
) ]1 {- x# A# V+ }0 `& ~7 Zprint(json.dumps(results,ensure_ascii=False,indent=2))
# b1 f' ?9 c( u( ^* O6 ~3 ?6 Bbad=[r["profile"]forrinresultsifr["webrtc_leaked"]ornotr["tz_match"]] ( v$ Z! t' o8 M7 T
print("\n需要人工处理:",bador"无") 跑完看输出里的三个字段:webrtc_leaked应该是空列表,tz_match应该是True,ua_platform_consistent应该是True。任何一项不对,先别开这个环境的店铺后台,把问题修掉再说。 5.4用MCP把调度交给AI 如果你在用支持MCP的AI客户端,可以把本地服务挂上去,用自然语言调度环境,比如"列出所有配置""启动名称带US的配置"。配置形态如下。 {
% x/ V# f9 F. D: [( x/ Y"mostlogin":{6 @* |& I# u" J L4 l: }. v
"command":"npx",; i1 Y5 {1 s: W& v
"args":[' c) \1 q' g- g: u7 s9 A
"-y",% y* m: O. v% q6 X% E
"mcp-remote",1 |6 ]( r" x* N$ w- o
"http://127.0.0.1:30898/mcp",
5 T& A( K) P. N, A7 S% o"--transport",
3 O1 H) f+ I9 P6 Z3 E& q. I+ ~/ U"http-only",
f# T# q. Z# s* o7 l"--allow-http",
+ S1 d' u# G2 |- E) `/ C"--header",
. i' n$ p8 k/ Z, j& w/ c"Authorization:YOUR_MOSTLOGIN_TOKEN"
$ g8 c$ R5 O4 a/ j5 g]
6 H, `7 O" n1 j" d}! o. Z; s8 e- w
} 两个安全提醒。一是Authorization的值等同于密码,别出现在截图、公开文档和代码仓库里。二是这个端点在127.0.0.1,只能被同一台机器上的软件访问,网页版的远程AI应用通常连不上,需要走mcp-remote这类桥接。另外,MCP与同步器目前面向浏览器环境,云手机侧不适用。 六、验证与排错:怎么确认环境真的隔离了 新环境建好,先做四项检查,全部通过再投入日常使用。 (1)指纹自洽性。打开任意指纹检测站点,比对UA、Platform、分辨率、时区、语言、字体数量这几项是否互相印证。重点看有没有"UA是Windows但Platform露MacIntel"这类矛盾。 (2)WebRTC泄漏。用5.3那段脚本跑一遍,`webrtc_leaked`必须为空。很多工具的WebRTC开关默认是关的,忘了开就会泄漏真实内网IP。 (3)DNS泄漏。访问DNS泄漏检测站点,看解析服务器归属是否跟代理IP同地区。如果解析服务器显示的是本地运营商,说明DNS没走隧道。 (4)存储隔离。在A环境写入一条LocalStorage记录,切到B环境读取,必须读不到。 常见的翻车现象和对应处理,整理成下表。 七、工具提供环境隔离,不是行为豁免 多账号经营在很多平台上是被允许的,前提是你有真实的商业理由并且事先获得批准。亚马逊的规则是:每个销售区域通常对应一个卖家中心账号,除非你有合理的商业需求需要额外账号,且所有账号状态良好。在实际操作上,这意味着你要为每个经营主体单独注册公司,各有独立的税号、营业执照、企业银行账户和专用信用卡,并在创建新账号之前向平台申请书面审批。 凭证也要完全独立。用在该企业名下注册域名的专用邮箱、企业电话、银行账户和注册地址,不复用现有账号的任何联系信息。运营层面同样要独立:独立的客服工作流、独立的库存管理流程、差异化的Listing策略。跨账号共用ASIN、把同一张供应商发票上传给多个账号、在重叠类目上使用雷同的定价结构,这些都会在关联审查中浮现出来。 日常还要盯账号健康。亚马逊的账号健康评分(AHR)高于200算健康,低于100可能触发主动审查,新账号前90天格外脆弱。eBay侧重点略有不同,更强调注册主体信息、收款账户、地址、联系方式、IP与设备环境的一致性。 回到工具本身。指纹浏览器做的是环境隔离:让每个账号拥有独立的Cookie、本地存储、指纹参数和网络出口。它提供的是技术层面的隔离能力,不是行为层面的豁免。用独立环境去掩盖同一个经营主体、复用同一套素材、或者在账号受处罚后换个环境继续经营,这些都不是工具能解决的事,也不在合规经营的范围之内。 一句话:环境隔离解决"多个账号看起来像同一个人在同一台电脑上操作"的问题,解决不了"这些账号本来就不该同时存在"的问题。 八、一个人+AI,能管的店会变多 说说趋势,也说说个人卖家接下来能怎么借力。 平台侧的检测正在快速模型化。早年是比对固定的几个字段,现在是拿机器学习建模分析行为模式:鼠标移动轨迹、打字节奏、页面导航序列、会话时长分布、操作时间段的规律性。这些信号不依赖任何单个指纹字段,靠堆参数躲不掉。工具侧的应对方向也随之变化,从静态指纹走向行为随机化与自然交互模拟。 AI在这一轮变化里同时出现在两端。平台用它做识别,运营者用它做调度。对个人卖家来说,后者的意义更大,因为你缺的恰恰是人力。 具体地讲,有三条路已经可以走通了。 一是本地API加脚本。把重复的批量启停、批量更新代理、定时巡检写成脚本,一个人维护三十个环境的日常开销可以压到每周半小时。这在两年前需要专职运维,现在一份几十行的代码就够了。 二是MCP。把本地服务挂进支持MCP的AI客户端之后,"启动名称带US的配置并打开卖家后台"这类需求可以用一句话完成。MostLogin的MCP能力从基础版起全套餐支持,这对预算敏感的个人卖家是个友好的设定。运营动作从"人操作工具"转向"人描述意图、工具去执行",一个人能覆盖的环境数量会上一个台阶。 三是AI辅助的日常运营本身。多语言Listing撰写、客服模板本地化、差评归因分析、竞品页面变化跟踪,这些原本要一个小组干的活,现在一个人加几个AI工具就能扛下来。环境隔离工具负责保证这些AI调用发生在正确的环境里、使用正确的网络出口和身份,两者是接得上的。 对一个人的团队来说,这意味着规模的天花板在往上抬。以前一个人管十个店就是极限,现在三十个店是可行的,前提是把环境、代理、台账这三件事做扎实,把重复劳动交给脚本。 技术会继续演进,平台的检测也会继续加码。但有一条不会变:稳定的、真实的、可解释的经营环境,永远比花哨的参数更值钱。
. c: A4 L( T" j |