|
一个人,两台电脑,十几个店铺后台,早上八点爬起来先看有没有平台通知邮件。这是很多跨境个人卖家的日常。也有人更狠,一台笔记本加两块显示器,硬扛三十个店铺。 翻车的时候,多数人第一反应是产品出了问题、物流出了问题、差评来了。真实情况往往更朴素:你的这几个店,在平台的设备图谱里长着同一张脸。 个人卖家选环境工具,逻辑跟团队是反过来的。团队有预算、有专人维护、有流程规范,缺的是规模化能力;个人卖家反过来,账号数量其实不多,缺的是钱、是时间、是没有人替你做日常巡检。所以不要一上来问"哪个工具功能多",先问三个问题:我这批店一年愿意为环境花多少钱;我能接受多久做一次检查;我能不能承受"每次启动换一套指纹"带来的不确定性。 第三个问题的答案,几乎永远是"不能"。 量级上也别贪大。一个人管三个店和管三十个店,需要的根本不是同一套东西。像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:批量启动配置并按限速排队
$ N5 ?& ]% w- f//说明:接口路径与字段名以MostLogin官方API文档当前版本为准
( A5 _4 H/ ?0 [' h2 `2 j* t- N1 GconstBASE='http://127.0.0.1:30898';
/ s( D# [; V1 a4 w4 l( D! p- B' zconstTOKEN=process.env.MOSTLOGIN_TOKEN;//令牌等同密码,不要硬编码进仓库
- X2 K* T4 H6 r4 e7 v5 c) O6 Q3 q, T. c) D; x
constsleep=(ms)=>newPromise((r)=>setTimeout(r,ms));
+ }) ^/ R" C/ \: z, ]5 r# @$ @3 x7 w) }6 \! b
//基础版限速2次/秒,留100ms余量 0 j: ]9 a( S3 ?. M: y& r
constRATE={intervalMs:600,perBatch:1};
0 F! h$ N/ j. t `3 M& y0 @2 J+ z# l
asyncfunctionlaunchProfile(profileId){
7 a9 {" b! y; Tconstres=awaitfetch(`${BASE}/api/v1/browser/start`,{
$ k* Q1 O" V/ l/ pmethod:'POST', : \+ f+ E% w/ Y: S0 n
headers:{ ) e: ] `2 `, B
'Content-Type':'application/json',
) K, L2 {& l' c. J( [7 h2 jAuthorization:`Bearer${TOKEN}`,
* s# }. F, E4 O! M5 u},
. ~& m5 [0 w" E0 pbody:JSON.stringify({profileId,headless:false}),
1 [7 Z9 B5 l: C/ }! |: h0 u});
) _& o4 O( u- p! E% A9 Fif(!res.ok)thrownewError(`启动失败${profileId}:HTTP${res.status}`); - m2 A3 ^' O3 S/ K, d
const{data}=awaitres.json(); $ |8 F& ?/ w/ V1 t B' s$ `, N
return{profileId,debugPort:data.debugPort,ws:data.ws};
7 Y, g2 l# A& V7 B( I( V} ! ~4 D( b% G* a. x$ \0 {$ K
. p y" z% l" ~/ N- m' P+ }2 d
asyncfunctionstartAll(ids){ & a" F& { D) C# F0 G4 p x
constopened=[]; v4 R8 S- o+ V
for(constidofids){ 5 X b- s0 n( d ~2 P# \" R; M7 C
try{ 6 g# J& z- S0 w/ Y$ ]* c* I
constinfo=awaitlaunchProfile(id);
6 O' a* Y8 F: Q6 e8 uopened.push(info);
: v7 }2 ]3 c. G- W( hconsole.log(`[ok]${id}->CDP端口${info.debugPort}`);
6 w- \6 s/ B+ p) g}catch(e){
7 s9 Y& d8 b: T; R/ z- H' A+ pconsole.error(`[skip]${id}  {e.message}`);
' C5 ?" \/ J8 E}
/ M8 ?! t& { E2 ?0 o$ ~4 U. `awaitsleep(RATE.intervalMs);
' J I4 w N6 T4 g9 _ t) Q} ; M5 ~" _3 d' R; i
returnopened;
1 F/ u: u. F* U5 x; Z$ w}
/ ]" C# l. }' c; T- V4 w: d- U4 U
; V# A+ {- H7 B; X//收工后统一关闭,避免挂着十几个进程占内存
. [9 \8 J, W6 x+ JasyncfunctionstopAll(ids){ + L7 {) M4 A! K8 e; y
for(constidofids){ 0 k. O2 f$ d7 g+ z( E
awaitfetch(`${BASE}/api/v1/browser/stop`,{
, N4 T, N, G; Zmethod:'POST', * L6 F( P V2 s' }; J
headers:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},
/ J; \" f1 r( p+ l0 a4 ubody:JSON.stringify({profileId:id}), ; @+ |9 ?6 m% C1 c1 ^
}); 5 u& @# ^3 y9 F$ ^+ c: k& S6 V2 [
awaitsleep(RATE.intervalMs); ! ]+ L" \2 U5 ^9 U1 d
} . e3 a m- E) O2 d
} , E! H) [% v5 d; ~; w9 S/ R* z# M
: _ u* D$ L7 [& p4 mstartAll(['ebay-us-01','ebay-us-02','etsy-uk-01']).then((list)=>{
4 j$ X; G1 K4 k9 \0 C. |) ^. X//把debugPort写进台账,后面的巡检脚本要用
a. Z! b% R/ A# {0 v! Dconsole.log(JSON.stringify(list,null,2)); $ s' P% o5 z* @3 f
}); 5.2批量更新代理 换代理服务商、或者季度轮换IP的时候,逐个手工改三十个配置会改到崩溃。用API批量刷一遍,几分钟的事。 //update_proxy.js:从CSV台账批量回填代理,并做格式预校验
7 ~1 ^( n* r7 N/ q0 P$ pimportfsfrom'node:fs';
% k6 e" l9 O# _' ~9 _
3 q h! A8 i: L% T/ ^3 l( Z6 nconstBASE='http://127.0.0.1:30898';% r ], A6 v' i+ I* U2 ~
constTOKEN=process.env.MOSTLOGIN_TOKEN;
" }8 c! u7 l [& W
3 _ Q1 C$ U/ b: Y& Y! {//台账格式:profileId,host,port,username,password,protocol
, I5 Z, R2 a! X- ?7 D( Aconstrows=fs
# }5 _, A; e2 C: Z! P.readFileSync('proxies.csv','utf8')7 p: M' W1 @" T- Y
.trim()
2 }! }5 |& P7 J, t9 A/ k4 B% R% }.split('\n')
* g) H' {% s" p; e* g* E.slice(1)# k$ \0 |; K. E3 [* O2 Q% @
.map((line)=>line.split(','));
( ~5 K. ^( s0 i+ ~$ K
, V( b9 k/ J! w+ J* F$ k, _5 D5 q) z" lconstPROTOCOL=newSet(['http','https','socks5']);
7 X' a) j/ h5 X, `! d0 Z6 l8 j8 E( a( v' q! c6 u; b
functionvalidate([id,host,port,user,pass,protocol]){
5 s, A7 s/ X! y; E4 s) s K9 o3 _if(!PROTOCOL.has((protocol||'').trim()))return`${id}:协议只支持http/https/socks5`;
1 X2 E* d/ B$ `+ O+ x: Bif(!/^\d{2,5}$/.test((port||'').trim()))return`${id}:端口不合法`;/ h- C9 x. Y$ e6 o# z8 y1 |
if(!host||!host.includes('.'))return`${id}:主机地址不合法`;, M6 i- R( [1 X, F) X4 W
returnnull;; z1 p8 d% f& N7 l* r* M& `
}/ M' g e% h% E
" t% Q/ z" ?* o6 {0 [asyncfunctionupdateProxy(row){! S: A& z) B! u: w
const[id,host,port,user,pass,protocol]=row.map((s)=>(s||'').trim());" v( s5 t6 k& ^# k2 }& Q3 ]* v" u
consterr=validate(row);) n9 \$ X, m$ q, n: k5 E
if(err)returnconsole.error(`[skip]${err}`); F E1 Z! j7 |; V% B
* J! f) o7 y) k
constres=awaitfetch(`${BASE}/api/v1/profile/update`,{; M7 h& b& V- T5 n
method:'POST',* I4 n/ v2 R7 ^8 K' \: X8 j+ U
headers:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},/ k6 _( L, X& m9 p+ _ x) S
body:JSON.stringify({8 y' }8 E( N2 Z3 O% ^! k
profileId:id,
+ a- C: |- G) X2 b: E% \proxy:{host,port:Number(port),username:user,password:pass,protocol},: I$ Q& r- T; z9 b) q7 b% z
}),
4 A( c- k) l6 G});8 T/ v% G/ h) ?1 q5 J. a. d% I
console.log(res.ok?`[ok]${id}`:`[fail]${id}:HTTP${res.status}`);4 m4 j, B A& n: t
}
% V! N# U2 Q" U% H9 W
8 r2 | ~2 K2 Q0 y/ Z* f4 X(async()=>{
1 `* V( x' D7 T0 J7 o! `; qfor(constrowofrows){
) U6 Q" n' m+ i- v' U1 _; R! MawaitupdateProxy(row); C# r2 l- D$ w* y/ ]
awaitnewPromise((r)=>setTimeout(r,600));//基础版限速2/秒
( ~ A6 }5 A/ Y0 J8 u4 G8 Q}
; d: c/ G" Y9 r! p})(); 注意一点:改完代理一定要重启对应环境并重新验证时区一致性。IP换了城市,时区没跟着改,就掉进2.3那个坑了。 5.3用Playwright挂CDP做每周巡检 这段是整套自动化里价值很高的一环。每周跑一次,检查两件事:WebRTC有没有泄漏本地地址,时区与IP归属地是否一致。 #weekly_check.py:挂CDP巡检,检查WebRTC泄漏与时区IP一致性
3 p' \4 P8 E- G. j8 U7 |+ m#依赖:pipinstallplaywrightrequests;接口路径以官方文档为准
% e2 D6 \8 R& nimportjson . O9 L6 F: v" t7 a3 c. ~/ K9 K
importrequests
3 B, |0 V; G. H) o1 I8 e1 dfromplaywright.sync_apiimportsync_playwright " U7 R" R; E: |& F c" I
8 {6 V+ o0 m& [0 w8 L) Q6 uBASE="http://127.0.0.1:30898" " f+ ^' c+ g' D, @
TOKEN="YOUR_TOKEN"#生产环境请从环境变量读取 & a* w+ N/ t" T0 d4 d- y% b2 W4 P8 t
IP_GEO_API="https://<你的IP归属查询服务>/json"#换成自己用的查询端点 ) }1 @* a" _9 b+ w/ M: Z
( c& {% G# J0 d& @) LPROFILES=["ebay-us-01","ebay-us-02","etsy-uk-01"] + V- [( E5 l: q0 r* g# x/ p7 \" S
% H' N- \& L3 o; L. U; M! ~1 ~
#IP城市->期望IANA时区,做美国站与英国站的最小示例 7 y7 J; F3 @# I" y+ H2 z
EXPECT_TZ={ ; [( r- o, j3 P$ j) P# N
"LosAngeles":"America/Los_Angeles", ' p" I2 U( o$ T8 s. N: \
"NewYork":"America/New_York", 1 X7 B' w& W! Z2 n! T& X
"London":"Europe/London", # t0 K4 {8 H1 k9 D/ ]
}
% C+ M/ y' l/ ^% B
1 M# b* c8 N7 x C0 h# D1 C% [; l$ W) f
defstart(profile_id): ! e/ d6 D& @- c5 X4 o
r=requests.post( / O3 D1 B3 m! B7 I6 p" o" D+ d5 v
f"{BASE}/api/v1/browser/start", K/ V! E- ~5 M+ V. K, `
headers={"Authorization":f"Bearer{TOKEN}","Content-Type":"application/json"}, 6 h) A$ K8 a9 K% [8 L8 K- p8 @
json={"profileId":profile_id}, 1 @+ Y- _* r1 o) F4 J' V
timeout=20,
% S; W& f$ I" P)
9 O- w% F1 E. y, l2 `# V+ or.raise_for_status() & P4 i7 _6 _5 |0 I# s" N/ U
returnr.json()["data"]["debugPort"] ( z# H& u2 [" V! v' T. {
, X- C! n% V" @ D& J; q
& B j: V4 i! P# I! o. h+ F4 Jdefwebrtc_leak(page): # r+ o3 b; a0 w( ], Z
"""收集ICE候选里的地址,看有没有暴露内网或本机真实公网IP"""
; o* c, _" p; x' K g: nreturnpage.evaluate("""()=>newPromise((resolve)=>{ + a) `" [. ]1 }% F4 q
constfound=newSet(); + s: u9 ^8 w7 R9 P! N8 A" T
constpc=newRTCPeerConnection({iceServers:[]}); 6 j/ c+ {7 r/ k) d4 } f" o
pc.createDataChannel('probe'); 0 k$ h; l7 A+ y( B( ]: }# M& h
pc.onicecandidate=(e)=>{ 5 K! g, J7 H. U8 U# O* A( j3 V
if(e.candidate&&e.candidate.candidate){ 3 ~ \- _ ~0 k9 E
constm=/([0-9]{1,3}(\\.[0-9]{1,3}){3})/.exec(e.candidate.candidate);
$ e, e$ M8 @* a/ Q& d/ `" w" c. lif(m)found.add(m[1]); ; ^9 H; f, M' H
} 7 d. U( c5 l# w! T' t
};
0 l* ?! m& v" @" upc.createOffer().then(o=>pc.setLocalDescription(o)); 9 W2 K4 D! n6 e5 u' {
setTimeout(()=>{pc.close();resolve([...found]);},2500);
: p! ~& Y( @. T+ p/ l6 J( x$ ?})""") . V6 {5 ]& |8 k. a$ i5 X0 W% c: ~7 E
' E9 u4 _4 L- B; Y: P1 L" O2 e5 l
; Y9 r- b# l. C2 }) Y- l) ydefcheck(profile_id):
( }: }( q& J; L8 C) J1 Oport=start(profile_id) 3 P3 P* F- |( W9 ^8 m
withsync_playwright()asp:
. o( [$ Z- I/ m6 e: Obrowser=p.chromium.connect_over_cdp(f"http://127.0.0.1:{port}") % q" L+ H3 X8 h; S6 F6 d
ctx=browser.contexts[0] $ J/ @ m' U0 m3 L- P
page=ctx.new_page() # Q/ f& `1 M5 _( @) ^, [
page.goto("about:blank")
a% z) Z8 @# |2 |, }# L$ F
D) {6 l) @# m+ r9 Rtz=page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone")
. O4 J4 v% |8 j+ W" {. ?; b. N I- nlang=page.evaluate("navigator.language") 5 e0 [2 s$ D3 d, n- g4 s+ J% M% T
ua=page.evaluate("navigator.userAgent")
8 X* C$ o" F) q* b0 [3 Z2 kplatform=page.evaluate("navigator.platform")
) @; [; t- h/ | i; w/ paddrs=webrtc_leak(page)
8 b5 M& l' ]8 [% n" M, _: E
' Q0 A7 V0 x4 ?geo=requests.get(IP_GEO_API,timeout=15).json()
8 X$ Y9 t- N5 P; Q2 W* E7 Z5 Fcity,ip=geo.get("city"),geo.get("ip")
, i- n" h' k# P4 a+ Z9 e @/ O1 p3 k O. b7 Y: D8 B. f7 a2 O
#内网地址属于正常,公网地址必须与代理出口一致
4 h; f" s- ~9 kprivate=[aforainaddrsifa.startswith(("192.168.","10.","172.16.","127."))] 4 ]9 J% G; v+ |, U2 d0 G( Z0 D
leaked=[aforainaddrsifanotinprivateanda!=ip]
Z( r. ^" q. j% g7 I7 A N4 N0 U9 R+ E- R7 V P
report={ : R' g; U3 T* p ?" |# G, W
"profile":profile_id,
: n, _5 Z3 B/ v% {"exit_ip":ip, / d& K7 P* L/ a5 x7 @5 i4 X
"city":city, 0 O* t- J) f& K" h( A T
"timezone":tz,
5 S/ b: A, E! \$ D4 H L"expect_timezone":EXPECT_TZ.get(city,"未配置映射,需人工核对"), , h& k: d3 p! p/ S2 n
"language":lang, / V1 I: \7 m8 [3 I; s* ?! o
"platform":platform,
, I- }% E$ N& n"ua":ua[:60],
. ~0 O, B# {# c% i; q( O: C P"webrtc_private_candidates":private,
# x5 T% V1 U) i/ V! K+ s2 t) B"webrtc_leaked":leaked,
9 x, B; B! s3 e" e$ H {"tz_match":tz==EXPECT_TZ.get(city),
- ~# X/ T+ P: D2 u"ua_platform_consistent"  "Win"inplatformand"Windows"inua)
, z# U0 [! E! L$ ior("Mac"inplatformand"Macintosh"inua),
0 U' q! p* A4 c- O$ ~2 H% N" q} - Q! u& f' z9 {! D/ \: A5 z% k
browser.close() ' B" }" q" L# C5 ]1 [6 a/ Q" e
returnreport
1 U% ^2 R: r" B. e" U& E- Q [% r, ]
2 D1 h9 O0 ?4 \# G% t* w; r
if__name__=="__main__":
1 K7 ], S! e8 N# iresults=[check(pid)forpidinPROFILES] + | Q9 {; O1 U9 q% i
print(json.dumps(results,ensure_ascii=False,indent=2)) % y" l8 @% V3 _) e( n: B) }
bad=[r["profile"]forrinresultsifr["webrtc_leaked"]ornotr["tz_match"]] / Z1 Y- g2 e; e5 @
print("\n需要人工处理:",bador"无") 跑完看输出里的三个字段:webrtc_leaked应该是空列表,tz_match应该是True,ua_platform_consistent应该是True。任何一项不对,先别开这个环境的店铺后台,把问题修掉再说。 5.4用MCP把调度交给AI 如果你在用支持MCP的AI客户端,可以把本地服务挂上去,用自然语言调度环境,比如"列出所有配置""启动名称带US的配置"。配置形态如下。 {
4 o9 Z) ~, E1 X8 c"mostlogin":{" _, K& ?+ q- I g+ z4 Q
"command":"npx",, }- E$ A& X' l6 [$ h+ ]/ M- }/ \
"args":[3 V: O r! u) h3 V2 N& s9 \( C, S
"-y",0 d* O- Q. T3 ]; `% s% h- f0 ]
"mcp-remote",
# w. Y( D% p6 H! x- T/ t" ?"http://127.0.0.1:30898/mcp",
6 _: ^0 {5 k8 E5 A"--transport",
/ z6 `: }3 x& @0 }( Q* k"http-only",
) s8 n# n& O# H4 U"--allow-http",: F# V6 }9 X' U! @3 I6 u' R/ i# {
"--header",- M9 j6 ~5 [* X8 e' P
"Authorization:YOUR_MOSTLOGIN_TOKEN"; m$ m) q- h, A' G% K0 V/ G# p
]# \; i1 @/ X( K3 s0 K k4 R" d
}
s' o: L3 X3 K} 两个安全提醒。一是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调用发生在正确的环境里、使用正确的网络出口和身份,两者是接得上的。 对一个人的团队来说,这意味着规模的天花板在往上抬。以前一个人管十个店就是极限,现在三十个店是可行的,前提是把环境、代理、台账这三件事做扎实,把重复劳动交给脚本。 技术会继续演进,平台的检测也会继续加码。但有一条不会变:稳定的、真实的、可解释的经营环境,永远比花哨的参数更值钱。
+ H9 k, |# f% ~ |