|
一个人,两台电脑,十几个店铺后台,早上八点爬起来先看有没有平台通知邮件。这是很多跨境个人卖家的日常。也有人更狠,一台笔记本加两块显示器,硬扛三十个店铺。 翻车的时候,多数人第一反应是产品出了问题、物流出了问题、差评来了。真实情况往往更朴素:你的这几个店,在平台的设备图谱里长着同一张脸。 个人卖家选环境工具,逻辑跟团队是反过来的。团队有预算、有专人维护、有流程规范,缺的是规模化能力;个人卖家反过来,账号数量其实不多,缺的是钱、是时间、是没有人替你做日常巡检。所以不要一上来问"哪个工具功能多",先问三个问题:我这批店一年愿意为环境花多少钱;我能接受多久做一次检查;我能不能承受"每次启动换一套指纹"带来的不确定性。 第三个问题的答案,几乎永远是"不能"。 量级上也别贪大。一个人管三个店和管三十个店,需要的根本不是同一套东西。像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:批量启动配置并按限速排队
8 |5 K, y* `, n1 v//说明:接口路径与字段名以MostLogin官方API文档当前版本为准 & @6 w6 C4 Q4 q9 ?. y
constBASE='http://127.0.0.1:30898'; - ^* T" [+ H# p7 G& A' ]
constTOKEN=process.env.MOSTLOGIN_TOKEN;//令牌等同密码,不要硬编码进仓库 3 `/ l% Q1 x4 {2 v; n
& V( y& q6 o' i3 O- J* J
constsleep=(ms)=>newPromise((r)=>setTimeout(r,ms));
! y9 C3 {# Y( D& [) n! c! v, |$ a8 f* J* {4 o& }: Y e& Y
//基础版限速2次/秒,留100ms余量 9 M+ x, X' ~1 V" B) D, X+ s
constRATE={intervalMs:600,perBatch:1};
: @3 J! k' S+ }# \( C
/ A# Q- U3 r- U8 q8 _+ kasyncfunctionlaunchProfile(profileId){ * ~) k+ M% i% _2 a
constres=awaitfetch(`${BASE}/api/v1/browser/start`,{
3 Z: e# G0 w9 A/ \7 e& \method:'POST',
" i }9 u, V6 [* Z8 `headers:{ 2 U7 F6 _6 s6 L0 F1 b0 N6 y
'Content-Type':'application/json', , r" o, u/ o& R/ G8 ]6 S7 l o
Authorization:`Bearer${TOKEN}`,
" I# R0 }! C6 l4 }0 R0 R8 |. o# r},
. L& q8 ^. i8 h; _body:JSON.stringify({profileId,headless:false}), \! n6 Z8 X1 S2 N3 ^" P# _
}); 1 k# u& _" q; M& F
if(!res.ok)thrownewError(`启动失败${profileId}:HTTP${res.status}`);
" R3 j2 U6 C/ P; k4 G3 p- Rconst{data}=awaitres.json(); 5 [4 [! j! L( B1 X% ?
return{profileId,debugPort:data.debugPort,ws:data.ws}; % @; Y4 o' a9 f6 }
}
, Z4 b8 |1 s) R8 m6 e: B5 e. m0 b Q) h" k
asyncfunctionstartAll(ids){ " H; p/ Z) z4 K8 d% {- k7 C
constopened=[];
1 u I% @( k" f" ~* ^" H! A( yfor(constidofids){
6 i2 d( l' G" c( Z% V# Htry{
8 S$ f* w$ E1 @6 u' L% Q, Qconstinfo=awaitlaunchProfile(id); & S: T" g& N/ }& T8 C
opened.push(info);
) C; j/ S8 W7 Z; A) V5 Sconsole.log(`[ok]${id}->CDP端口${info.debugPort}`); 7 ~4 a0 M: b. Y* v3 m
}catch(e){
3 T" m1 r8 g4 F2 \$ X% U; k& v+ Vconsole.error(`[skip]${id}  {e.message}`); # Z7 `: c# |- M; U' d
} - t( Q7 s7 |# [2 y
awaitsleep(RATE.intervalMs); 1 k8 `; x5 _ k' q' ^: ~! Z: x" D
}
+ P( m9 G$ o4 ^" l3 jreturnopened;
$ j- d, h F, T& ?& T d} $ {; l% J: r- u& N) c0 {
7 k+ D% E/ f! D0 w//收工后统一关闭,避免挂着十几个进程占内存 " M; S) E4 d& \
asyncfunctionstopAll(ids){ 0 k# X$ v/ V% R0 q. Y" U
for(constidofids){ 7 z' b; S8 Z7 @) X% I6 J8 f3 R" s2 B
awaitfetch(`${BASE}/api/v1/browser/stop`,{
q& T: @! {) X/ J/ Rmethod:'POST',
k* M8 q, c+ e! \' y: n' @9 e; Q$ A% Vheaders:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`}, 4 u% ?( `& D* ]0 u s0 t5 b# G- q8 e
body:JSON.stringify({profileId:id}),
0 q( p4 f* x% w) C8 x# v% W});
! s* P- I. _" z9 M- Q' Q5 s7 H, Gawaitsleep(RATE.intervalMs); 2 U5 l l$ O& {7 _' n4 N
}
4 A4 [7 l% Z9 @( @% p( Q0 O. E+ \+ d}
. K w' I f# E+ n% S* C/ O7 W6 [
3 m1 i* Q4 \+ x- P, k; J- xstartAll(['ebay-us-01','ebay-us-02','etsy-uk-01']).then((list)=>{
! w2 M. h3 v& ?5 v {//把debugPort写进台账,后面的巡检脚本要用 7 g5 h. q/ T6 t$ a- N3 l' K
console.log(JSON.stringify(list,null,2)); & K$ T* J/ r" b
}); 5.2批量更新代理 换代理服务商、或者季度轮换IP的时候,逐个手工改三十个配置会改到崩溃。用API批量刷一遍,几分钟的事。 //update_proxy.js:从CSV台账批量回填代理,并做格式预校验3 I" p' N* \6 ^$ R
importfsfrom'node:fs';
' y7 w/ w+ {: y/ H' i e+ `* K$ s8 f1 w: w
constBASE='http://127.0.0.1:30898';
! Y. U$ I: u& U b3 S: H3 u, z' vconstTOKEN=process.env.MOSTLOGIN_TOKEN;
0 ^1 S4 E* j2 ^6 p& g% ]. ]
* T4 c/ C) N h& ^//台账格式:profileId,host,port,username,password,protocol; n+ g) ^, {# A- o0 R; A
constrows=fs* L a. _$ {& g/ Y' U
.readFileSync('proxies.csv','utf8')! z: C$ {6 [: k- N/ W
.trim()
7 |1 ^9 s% r( t. b# F( u.split('\n')
+ b" ^6 b$ _. s& q5 h, S$ t/ {( {4 @.slice(1)# g1 y. `8 E" _$ G
.map((line)=>line.split(','));/ x1 j8 O& k) ] y) P) ?7 ]
' j- g; y% @% M! `constPROTOCOL=newSet(['http','https','socks5']);
) p' b, T# B+ R* G' o
( S) `" w0 R5 T9 E* Efunctionvalidate([id,host,port,user,pass,protocol]){
% ]6 H( C+ D$ \% h' Y8 `$ oif(!PROTOCOL.has((protocol||'').trim()))return`${id}:协议只支持http/https/socks5`;' p ^1 E# X- q
if(!/^\d{2,5}$/.test((port||'').trim()))return`${id}:端口不合法`;# k) x& C" W; K4 k3 t& M- |4 E
if(!host||!host.includes('.'))return`${id}:主机地址不合法`;6 c! `" E5 G$ A7 ~
returnnull;
. k0 U* g: X& }6 F9 o& Y}; s9 @' T; [1 \
- J C# B/ j6 | }: c, ~+ PasyncfunctionupdateProxy(row){
D1 Q5 T- [4 Bconst[id,host,port,user,pass,protocol]=row.map((s)=>(s||'').trim());3 p7 O, T/ b- [( N7 d3 ^
consterr=validate(row);
: r) r7 i; [0 C/ u: p7 Kif(err)returnconsole.error(`[skip]${err}`);0 z0 H; D/ z5 ]+ Y
5 X: O8 {$ }4 m5 dconstres=awaitfetch(`${BASE}/api/v1/profile/update`,{
% T5 S( `( Q3 t3 A" E. E/ A" Nmethod:'POST',
, ]9 p" B L& B4 s T- Yheaders:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},) u8 i4 e: Y) d& n8 R
body:JSON.stringify({1 r5 ?6 c( ^) i
profileId:id," q4 ~( h, x+ j. J
proxy:{host,port:Number(port),username:user,password:pass,protocol},
: \% R* X/ }6 h6 @0 O% M2 }}),5 S$ E* H/ q2 c+ K5 K5 i( W
});
: q) o2 g- k. r' Z2 |console.log(res.ok?`[ok]${id}`:`[fail]${id}:HTTP${res.status}`);
" H" |3 u5 \( u) R' r3 B: x# [}
' O- I' p1 v d5 D+ k
0 d, I5 U% C, D8 H(async()=>{
% m; ` r* i8 d2 ofor(constrowofrows){
; v; P; c+ O$ Q" c& r+ wawaitupdateProxy(row);$ ]! \; b. N( Y9 s! ]. l
awaitnewPromise((r)=>setTimeout(r,600));//基础版限速2/秒
0 s" L0 t$ A3 T, Z) T$ {. U6 P3 P( r) ]}
3 F$ f" }+ _2 ]3 I; |, a( }})(); 注意一点:改完代理一定要重启对应环境并重新验证时区一致性。IP换了城市,时区没跟着改,就掉进2.3那个坑了。 5.3用Playwright挂CDP做每周巡检 这段是整套自动化里价值很高的一环。每周跑一次,检查两件事:WebRTC有没有泄漏本地地址,时区与IP归属地是否一致。 #weekly_check.py:挂CDP巡检,检查WebRTC泄漏与时区IP一致性
/ t/ V) a" ^0 T#依赖:pipinstallplaywrightrequests;接口路径以官方文档为准 3 @; U! k2 m0 o
importjson * \; \/ B% [8 {6 ~8 g) ]$ c$ G
importrequests & s8 V, V8 I4 t$ |. K* \9 ~" z* ^
fromplaywright.sync_apiimportsync_playwright
& n0 } j. o! |1 H0 T$ J7 j! Q0 ~4 D1 w/ }
BASE="http://127.0.0.1:30898"
) g8 B3 c: n, p* D0 D6 tTOKEN="YOUR_TOKEN"#生产环境请从环境变量读取
, q$ B+ `0 c$ S. `IP_GEO_API="https://<你的IP归属查询服务>/json"#换成自己用的查询端点
. Z9 ?! x! V8 `5 n0 B. Q( }3 z7 C ?5 ]3 X+ @( [/ O/ C
PROFILES=["ebay-us-01","ebay-us-02","etsy-uk-01"] ( h: D3 E2 X% Y
. M# E( J2 J: L: R#IP城市->期望IANA时区,做美国站与英国站的最小示例 + l2 |! C: w1 Z9 ~ m1 u7 [. P
EXPECT_TZ={
R! Q V9 V8 \$ V! z6 Z2 j8 n"LosAngeles":"America/Los_Angeles",
! n: R2 F" F8 Z( y7 ]"NewYork":"America/New_York", / H; O! D) | K
"London":"Europe/London",
: S' X( D2 _( F! S- u! `}
4 m. L0 }& F" ~9 V9 T4 T4 y* @4 q% ~
3 Y9 X9 r q V
defstart(profile_id): # }4 ^8 T, l4 J1 `) J
r=requests.post( " v3 S$ b6 K8 O! I% T/ s2 h6 w
f"{BASE}/api/v1/browser/start", % x7 W9 V( O/ |* p; M& a# g, J
headers={"Authorization":f"Bearer{TOKEN}","Content-Type":"application/json"},
3 c3 k& Q/ C' U% X9 \) I+ Wjson={"profileId":profile_id}, / C3 p- n+ D) F6 X6 @$ n
timeout=20, 8 i- h! Q( `7 ~" e0 _
) . x3 B! {, l, i7 s3 q
r.raise_for_status()
d. v4 E3 p6 y: L- X8 ?2 Vreturnr.json()["data"]["debugPort"] % X8 g) x* h& Y c9 M7 ?, Q
, c; G0 r m7 P& v7 ?+ \) q' S
+ ?' f3 A* q) r+ Qdefwebrtc_leak(page):
5 {( `" E+ L5 }7 g: t/ i# L"""收集ICE候选里的地址,看有没有暴露内网或本机真实公网IP""" ' A e# A9 ?! K, T j3 Y
returnpage.evaluate("""()=>newPromise((resolve)=>{ ! ?" g" Y9 u; p8 V- {
constfound=newSet(); $ h6 i- m- @$ o6 P* ~+ H. |
constpc=newRTCPeerConnection({iceServers:[]}); * |8 ^) ^; F" `# V! E8 t: W* d
pc.createDataChannel('probe');
v" q& Q0 g# N" n( |pc.onicecandidate=(e)=>{
3 G/ J2 k7 R: t0 y& H, p" Lif(e.candidate&&e.candidate.candidate){ 8 V, K7 |$ o( t4 T5 o7 l
constm=/([0-9]{1,3}(\\.[0-9]{1,3}){3})/.exec(e.candidate.candidate);
1 ]6 b' {' x# W: Fif(m)found.add(m[1]); 7 n/ |, P" l4 i' e, x
} + }: `3 ^3 ^8 x9 T
}; " S: J, H5 ^* \! X+ `( f
pc.createOffer().then(o=>pc.setLocalDescription(o));
% u2 k9 V. I) m( @- n- hsetTimeout(()=>{pc.close();resolve([...found]);},2500);
0 A6 r8 V( H e})""")
/ m/ z- U0 a7 J1 L
3 M n" w/ z5 W( G; g! k6 k' z/ Z4 z- l, U8 Z
defcheck(profile_id):
! p5 V0 z* o2 Dport=start(profile_id) 6 ~# q0 M5 l9 c8 o3 h% M6 ] U
withsync_playwright()asp:
* G. s# U9 B, ]) X: f t1 Qbrowser=p.chromium.connect_over_cdp(f"http://127.0.0.1:{port}")
" \. X; Y8 }9 ^& W' pctx=browser.contexts[0] ! F7 z- A% O- i+ l; G( T, |% K2 ^
page=ctx.new_page() 9 p4 m8 z1 ?1 t# Y0 P# } {+ u2 Y
page.goto("about:blank")
& R* q: w3 L9 _4 w( y0 O) M/ @
tz=page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone")
5 S' X2 E: X+ D. ]( Y9 |( tlang=page.evaluate("navigator.language")
# C8 R: a* N! i2 Dua=page.evaluate("navigator.userAgent")
+ W2 e( W1 |* \4 g- n2 dplatform=page.evaluate("navigator.platform")
: U. K; c+ [7 {- D4 Maddrs=webrtc_leak(page)
% r; C0 Q( w A- s
9 b& E& K" c# {3 w" v( L% Zgeo=requests.get(IP_GEO_API,timeout=15).json()
. M) h. s4 H1 E2 Q& x- dcity,ip=geo.get("city"),geo.get("ip")
/ H! S5 [ r5 U+ v7 }- u
* G& n% G' g/ u#内网地址属于正常,公网地址必须与代理出口一致 6 h) h4 j; S( ^4 V) a4 N1 |* i
private=[aforainaddrsifa.startswith(("192.168.","10.","172.16.","127."))] $ T5 {) J( n% ^ I h& D8 z
leaked=[aforainaddrsifanotinprivateanda!=ip] 6 b1 l1 q6 e" R; l! b- C
" j/ {: e9 L2 ^! ^) treport={ 6 Z9 p2 z; ~/ R4 w
"profile":profile_id,
# k; ?* M, K" W- ?. Q"exit_ip":ip, j& y$ G$ G* O# t* g
"city":city,
9 k/ V% _7 t6 j: n5 \( f"timezone":tz, + l+ `' F! f5 L, k, h
"expect_timezone":EXPECT_TZ.get(city,"未配置映射,需人工核对"),
3 N% \$ l1 T9 l% o" O( X"language":lang, 9 a5 }$ s4 r1 t/ z% S8 t
"platform":platform,
! o4 l+ I X7 Z"ua":ua[:60],
; J5 l l0 L v& w; M"webrtc_private_candidates":private,
" a1 m' R0 G- b& O4 E"webrtc_leaked":leaked,
5 |5 D! G! |* q- ^) j, p, }"tz_match":tz==EXPECT_TZ.get(city),
7 z: b& C0 y; [. E% T"ua_platform_consistent"  "Win"inplatformand"Windows"inua)
8 @$ K! j- S3 s- p& R0 Sor("Mac"inplatformand"Macintosh"inua), ( o1 k4 y! p$ H
}
8 R8 k8 Q& @2 J9 xbrowser.close() / f" T4 I% b# E$ |4 O9 A
returnreport
- ]6 M: @* T8 M4 @
0 m- G: G& a' z% A X. D; |4 D" C. B0 ~ `
if__name__=="__main__":
3 e1 g) I5 n& p9 v9 H- K- s, zresults=[check(pid)forpidinPROFILES] , o' @0 p( p; B, Y# u% I- Q
print(json.dumps(results,ensure_ascii=False,indent=2))
# }2 s2 x* ?' F5 i+ Dbad=[r["profile"]forrinresultsifr["webrtc_leaked"]ornotr["tz_match"]] # q" D% t0 ~) e9 }
print("\n需要人工处理:",bador"无") 跑完看输出里的三个字段:webrtc_leaked应该是空列表,tz_match应该是True,ua_platform_consistent应该是True。任何一项不对,先别开这个环境的店铺后台,把问题修掉再说。 5.4用MCP把调度交给AI 如果你在用支持MCP的AI客户端,可以把本地服务挂上去,用自然语言调度环境,比如"列出所有配置""启动名称带US的配置"。配置形态如下。 {
, I* v, k* n4 L0 g"mostlogin":{1 a1 y: Q( x' Q, e; o
"command":"npx",
" F( ^4 G3 L1 H"args":[
( q: ]% W+ G/ f/ O* z" }+ Q"-y",
$ D/ S" e, q" k"mcp-remote",! \8 d' ?3 q1 l4 K! x
"http://127.0.0.1:30898/mcp",
- f; z1 `% _: T; b }$ h"--transport",+ ~- T. x) C/ X& m) M+ B: ]
"http-only",2 D( T/ H) B0 w0 H# j- S: {
"--allow-http",0 N9 }0 Z& m, y# y1 p9 G- b
"--header",
+ H( g2 [ u; S2 ~7 x/ f6 S! \" c"Authorization:YOUR_MOSTLOGIN_TOKEN"
8 O/ Q8 u1 X7 C, O b/ [( `]
3 r! e Z8 Z, {; k* E}
W1 m8 o# o- o! G- p4 |" _} 两个安全提醒。一是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调用发生在正确的环境里、使用正确的网络出口和身份,两者是接得上的。 对一个人的团队来说,这意味着规模的天花板在往上抬。以前一个人管十个店就是极限,现在三十个店是可行的,前提是把环境、代理、台账这三件事做扎实,把重复劳动交给脚本。 技术会继续演进,平台的检测也会继续加码。但有一条不会变:稳定的、真实的、可解释的经营环境,永远比花哨的参数更值钱。 * y5 R- M& C P* N% `3 c
|