|
一个人,两台电脑,十几个店铺后台,早上八点爬起来先看有没有平台通知邮件。这是很多跨境个人卖家的日常。也有人更狠,一台笔记本加两块显示器,硬扛三十个店铺。 翻车的时候,多数人第一反应是产品出了问题、物流出了问题、差评来了。真实情况往往更朴素:你的这几个店,在平台的设备图谱里长着同一张脸。 个人卖家选环境工具,逻辑跟团队是反过来的。团队有预算、有专人维护、有流程规范,缺的是规模化能力;个人卖家反过来,账号数量其实不多,缺的是钱、是时间、是没有人替你做日常巡检。所以不要一上来问"哪个工具功能多",先问三个问题:我这批店一年愿意为环境花多少钱;我能接受多久做一次检查;我能不能承受"每次启动换一套指纹"带来的不确定性。 第三个问题的答案,几乎永远是"不能"。 量级上也别贪大。一个人管三个店和管三十个店,需要的根本不是同一套东西。像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:批量启动配置并按限速排队 ' o! a9 j" `1 S) _6 E0 @8 M m( ~
//说明:接口路径与字段名以MostLogin官方API文档当前版本为准 9 a( L* [- y2 {$ Q: [! ~% K# {
constBASE='http://127.0.0.1:30898';
$ \ t, a- |) Y9 q& ?constTOKEN=process.env.MOSTLOGIN_TOKEN;//令牌等同密码,不要硬编码进仓库 ! ^/ x4 z/ u# t& I
" a/ ]8 a8 s* I1 p ?/ {" j3 @
constsleep=(ms)=>newPromise((r)=>setTimeout(r,ms));
2 J$ ^1 H) V/ J/ A; H+ I6 g1 Y% P4 z8 l
//基础版限速2次/秒,留100ms余量
0 i/ {2 H: m6 FconstRATE={intervalMs:600,perBatch:1};
1 R8 f3 n+ J+ ]6 h$ `0 Z: o0 Z7 E* z4 D' A( f1 h
asyncfunctionlaunchProfile(profileId){ " b: |7 q* i& U& H* x, R
constres=awaitfetch(`${BASE}/api/v1/browser/start`,{
# Z. K' Z/ o rmethod:'POST', : }" Y# T* K$ b; S. F
headers:{ . ? o; R; X8 M/ p
'Content-Type':'application/json', / H% \1 v& T' l& L" u
Authorization:`Bearer${TOKEN}`,
& }& B _. u7 S0 K7 K},
6 z$ e7 ?' o' I9 Hbody:JSON.stringify({profileId,headless:false}),
" q- W, Z- k [+ l* K4 ]}); 6 {/ e6 h& c$ f& Y
if(!res.ok)thrownewError(`启动失败${profileId}:HTTP${res.status}`);
; E# j5 }1 ]) A3 ~9 C7 dconst{data}=awaitres.json();
! m4 q3 ^9 `* _9 {" b5 xreturn{profileId,debugPort:data.debugPort,ws:data.ws}; . I5 f& R8 V6 ]9 {& |) M( F
} & M0 c# a0 L- y t8 m; E9 h, H
6 ^( z0 f# y" e0 ^! \$ X9 Z
asyncfunctionstartAll(ids){
" O4 T+ Q- n l, g. Vconstopened=[];
; w( A R% q" k" Yfor(constidofids){
/ Q) [4 L0 j3 A; t( itry{ - w7 X- E- ~: h- g F" z; B
constinfo=awaitlaunchProfile(id); , n" d$ p# d# V
opened.push(info); 0 U) ~' k- I) e3 c) [; s" `
console.log(`[ok]${id}->CDP端口${info.debugPort}`); $ M$ s( B! M7 l7 E8 z
}catch(e){
4 d1 D% c( f$ Q' e& b5 n; Hconsole.error(`[skip]${id}  {e.message}`);
" X8 F9 g8 y6 j$ h. J* F}
6 ]. @. v3 ]- P6 aawaitsleep(RATE.intervalMs); 6 @2 u! w% ~* W7 j! O) C3 Q
} # N& a+ e) P: I" N. c" [
returnopened; - s: r( L, F2 l0 f0 L# d
}
( r. ?7 D0 I/ z! f; }# C6 W/ e" c0 E
//收工后统一关闭,避免挂着十几个进程占内存
2 ] x, J( Y) Z( lasyncfunctionstopAll(ids){
+ l5 c- b7 K( j7 [% z4 yfor(constidofids){ ) q7 n0 u, ]" m# k6 j
awaitfetch(`${BASE}/api/v1/browser/stop`,{ * T! e/ K5 n Y; L& y! `
method:'POST', ( N! i! ^ n1 v: I
headers:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},
: f, K! \/ ~ m! t. S: Fbody:JSON.stringify({profileId:id}),
2 |& e, W8 T. R}); ^+ o5 a0 \- D, w
awaitsleep(RATE.intervalMs);
) q3 t8 ~( D7 D}
2 c3 K, r3 V! j4 }/ n} / d; H) z' [+ w W( l$ B' @! m
4 f: t) H+ c F8 astartAll(['ebay-us-01','ebay-us-02','etsy-uk-01']).then((list)=>{ $ z* Z* N* {+ j# W' J* n
//把debugPort写进台账,后面的巡检脚本要用
& A. Z6 h7 c% \console.log(JSON.stringify(list,null,2));
3 m" L' A. @* p0 e$ h1 ]}); 5.2批量更新代理 换代理服务商、或者季度轮换IP的时候,逐个手工改三十个配置会改到崩溃。用API批量刷一遍,几分钟的事。 //update_proxy.js:从CSV台账批量回填代理,并做格式预校验+ b! I$ n! V$ _* h. `0 E) v
importfsfrom'node:fs';
( Q6 P: ]7 L1 g) C; n: K2 g- V
7 C* f$ ]- U3 k+ v9 g4 sconstBASE='http://127.0.0.1:30898';) @; g) ^( x4 @! O" f) t- X% p2 A) _0 s
constTOKEN=process.env.MOSTLOGIN_TOKEN;
7 q0 A' W+ L( n6 \& D3 V% @2 R8 s
% S4 K, Z% b. y+ z& H9 }$ M//台账格式:profileId,host,port,username,password,protocol/ A4 g; ~; L$ q: b+ y6 `8 s
constrows=fs
3 q- `0 V9 M7 C; B.readFileSync('proxies.csv','utf8')1 L) B# V# R- D! s" H" Z: Y
.trim()
% ?" q: d3 A. Y.split('\n'). @. a8 V, y, w; F
.slice(1)( K( h- J/ Z1 V# P+ M. K$ ^
.map((line)=>line.split(','));
0 j3 r# q- j# t" k7 V( B: r! s, ], s- M8 x
constPROTOCOL=newSet(['http','https','socks5']);
+ X# t( g+ t4 |& [
: l3 k: ^3 R& H4 g; }# S- D1 Wfunctionvalidate([id,host,port,user,pass,protocol]){* l$ u% m9 J: k2 W) K8 I3 k. I- R
if(!PROTOCOL.has((protocol||'').trim()))return`${id}:协议只支持http/https/socks5`;3 ]" X. j ^& l
if(!/^\d{2,5}$/.test((port||'').trim()))return`${id}:端口不合法`;- V: S/ H; g" F1 @& B
if(!host||!host.includes('.'))return`${id}:主机地址不合法`;/ p, i% W+ l# c. q0 R
returnnull;
: F/ r6 c7 I1 ~( v7 _}
/ [+ A M9 g5 p( F. I# u! v( P) D7 G; S4 J* H
asyncfunctionupdateProxy(row){3 {/ g: N% U2 P! ^/ [
const[id,host,port,user,pass,protocol]=row.map((s)=>(s||'').trim());
& Q9 M1 I4 t& I) z" t3 l6 Wconsterr=validate(row);
5 B* }6 H; T$ _. T7 X4 H% w6 k) Fif(err)returnconsole.error(`[skip]${err}`);; X. _5 k/ ^* \" {9 [: P4 }/ [
% A( m) E) S% u3 K" Y
constres=awaitfetch(`${BASE}/api/v1/profile/update`,{
) E6 Q4 G$ P5 T$ P/ Gmethod:'POST',. M. e0 S9 k. t5 f3 c
headers:{'Content-Type':'application/json',Authorization:`Bearer${TOKEN}`},
# k- Z! l% m; G) x) B' T8 Ybody:JSON.stringify({# _/ h' o4 |3 n
profileId:id," m9 Q7 f3 ^5 Y$ {- G3 u
proxy:{host,port:Number(port),username:user,password:pass,protocol},
" }, J- @+ d" D) {}),# d$ }2 O. |# q0 g4 ^, j
});
1 B2 |) D" I# B2 Jconsole.log(res.ok?`[ok]${id}`:`[fail]${id}:HTTP${res.status}`);2 M) L$ {" A8 d1 V# p' P
}
3 P# M3 ^. T) }. s: @$ w& H+ t1 y/ V' |& Q* q0 l2 G, O; T
(async()=>{) r4 V: a" b8 W$ V
for(constrowofrows){
( |. g4 p$ C) V5 z. o: Z& NawaitupdateProxy(row);2 W+ K0 Y1 o$ M- w
awaitnewPromise((r)=>setTimeout(r,600));//基础版限速2/秒
; V6 Q1 b% y9 O( F8 O}3 y' r$ z. R# N# Y1 Q. U9 Y5 o/ S
})(); 注意一点:改完代理一定要重启对应环境并重新验证时区一致性。IP换了城市,时区没跟着改,就掉进2.3那个坑了。 5.3用Playwright挂CDP做每周巡检 这段是整套自动化里价值很高的一环。每周跑一次,检查两件事:WebRTC有没有泄漏本地地址,时区与IP归属地是否一致。 #weekly_check.py:挂CDP巡检,检查WebRTC泄漏与时区IP一致性
, b& u& O' m9 R' |6 l- o#依赖:pipinstallplaywrightrequests;接口路径以官方文档为准 " l# N& B1 N) o0 f- I$ m. Y
importjson 8 J& @9 r N2 c6 I1 K: I% y
importrequests 7 k) A9 z4 L0 y, A8 C, ]8 ~ S
fromplaywright.sync_apiimportsync_playwright
% S# O( B, W7 p5 d3 N0 @
) C9 Z" \# B6 DBASE="http://127.0.0.1:30898" , B& s4 g" a( m3 Z T
TOKEN="YOUR_TOKEN"#生产环境请从环境变量读取
: s0 L+ D+ N6 s8 e5 M5 D; P! ?IP_GEO_API="https://<你的IP归属查询服务>/json"#换成自己用的查询端点 9 N! X! A" G2 p; {7 b+ F
7 k" w7 `: s# P% ~' \# ~0 lPROFILES=["ebay-us-01","ebay-us-02","etsy-uk-01"] + |! j7 H6 q5 J; P" s2 ?
% [6 ^& u ]5 a! W
#IP城市->期望IANA时区,做美国站与英国站的最小示例
( X' a+ _$ U8 A+ v- s( O6 d! KEXPECT_TZ={
+ I. U/ }6 M4 b- t& X. P"LosAngeles":"America/Los_Angeles",
) R) t0 o% |: O" m$ P9 t"NewYork":"America/New_York",
5 u5 s& B- c" _; T: J2 d) r5 w* R, g"London":"Europe/London", 6 I) N* L. u4 p4 G- n
} + e" p' i* R& |5 z- u- c
( ]) H9 [- F' ~% E1 b3 _
1 o) U/ B& \/ B: H; Qdefstart(profile_id): % B# y/ {% a2 O; h+ b1 N
r=requests.post(
# M6 {% u, @+ F7 B0 C! S8 `/ Rf"{BASE}/api/v1/browser/start", 4 u: `/ k1 U4 W* c1 u$ D% L' s! b
headers={"Authorization":f"Bearer{TOKEN}","Content-Type":"application/json"},
' z2 d U9 }/ f' P8 m/ V/ Fjson={"profileId":profile_id}, $ U: W6 j6 m7 U2 w+ |
timeout=20,
$ N' Z& j/ E2 e8 ~% L)
& @% F+ h9 \" N' U xr.raise_for_status() % A/ z1 [7 }/ J; P* U5 I" G8 u
returnr.json()["data"]["debugPort"] $ ]. C$ ?- u1 r9 s) a# H0 U/ v1 q
7 X1 V; m+ V" y. k
: O- G# }+ U) t
defwebrtc_leak(page):
: H k/ `) K8 i- e( X"""收集ICE候选里的地址,看有没有暴露内网或本机真实公网IP"""
5 t3 `4 u! _; @( N1 |returnpage.evaluate("""()=>newPromise((resolve)=>{
$ f/ j3 P# \% {constfound=newSet();
/ Y* v* R }0 {# d0 F$ Nconstpc=newRTCPeerConnection({iceServers:[]}); + Y+ \, E- Y& Z0 [. L
pc.createDataChannel('probe'); [5 | ?' q U* ~2 B, V4 m
pc.onicecandidate=(e)=>{ ) M2 A. N6 p& e1 p, ^; |
if(e.candidate&&e.candidate.candidate){ & Z" L8 S2 U) G; h$ f& N W
constm=/([0-9]{1,3}(\\.[0-9]{1,3}){3})/.exec(e.candidate.candidate); - {" r* ^+ A3 p, C! k! G7 _6 j
if(m)found.add(m[1]); + _5 b! ?4 s N- M0 k( Z
}
3 I, e6 S# i! S/ ]4 _# ?}; % e8 C9 j* m) ]+ d) ?: A
pc.createOffer().then(o=>pc.setLocalDescription(o));
8 ]4 q4 n9 V) m* @, @* TsetTimeout(()=>{pc.close();resolve([...found]);},2500);
/ x3 y$ d" L3 o5 b})""") * E. w3 C q+ y) Y! g
G- Z( j& W1 p4 ?7 I4 Q7 \
* v" ~. O7 s/ C' v, F, V- Adefcheck(profile_id): : r6 g1 A; E; Y% f; G [5 X! P
port=start(profile_id) # u9 ]0 T3 y9 e8 X3 E( u. K
withsync_playwright()asp: & q4 e* d( `( K5 H1 y/ i y: Y
browser=p.chromium.connect_over_cdp(f"http://127.0.0.1:{port}") ! j- s* J8 ~+ s; T4 x$ y" H
ctx=browser.contexts[0] 3 `/ R/ k1 S# K x3 g) i$ ~
page=ctx.new_page() V2 m* g. S J) ^. B
page.goto("about:blank") 2 O0 R4 T- k. p. \& o0 l
' s# \4 B+ s/ E* v
tz=page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone") $ Z2 ~/ p) t y
lang=page.evaluate("navigator.language")
* ~( F1 D& m5 f# ?1 Tua=page.evaluate("navigator.userAgent") & A' P! F! M. l8 K
platform=page.evaluate("navigator.platform") # X$ R" ~- Y' @7 N5 e: Q- ^% m+ s
addrs=webrtc_leak(page)
% Z* {3 h/ o: |7 D6 D1 k: E
' z1 D9 ^) ?1 y( |+ u/ ggeo=requests.get(IP_GEO_API,timeout=15).json()
1 c/ @3 O+ X- Q4 ]city,ip=geo.get("city"),geo.get("ip") # }: A! ?: {. a3 N2 z8 X
) g5 g& V/ p: b* p
#内网地址属于正常,公网地址必须与代理出口一致 * Y1 f) s9 |1 F' D+ \
private=[aforainaddrsifa.startswith(("192.168.","10.","172.16.","127."))]
1 L# S4 O8 _9 X9 ], l/ u Cleaked=[aforainaddrsifanotinprivateanda!=ip]
9 q& {. F) f0 y
& Q8 V6 t. K' ~, Oreport={ 2 e, U8 [& g+ g" T
"profile":profile_id,
Q5 W# t+ @9 y E"exit_ip":ip, 3 B$ w8 f7 _$ R) r9 U9 Y
"city":city,
# a4 W" q. U1 d6 M# P2 V"timezone":tz, ; j9 C& V. w- f3 Z) X {$ ]
"expect_timezone":EXPECT_TZ.get(city,"未配置映射,需人工核对"),
# v0 }6 \! i# N. W; C0 l"language":lang,
+ I5 n0 e& ^! m1 i0 ^"platform":platform,
" v2 f; ]8 f Z$ J4 a"ua":ua[:60],
3 u$ F, ]# }8 z! [6 l. T"webrtc_private_candidates":private,
! Y2 E3 ?/ M; p" u. s" `0 i1 {"webrtc_leaked":leaked,
8 ~# s+ y5 m) P3 z& d"tz_match":tz==EXPECT_TZ.get(city), 2 N& t& R# V4 j
"ua_platform_consistent"  "Win"inplatformand"Windows"inua)
# F$ h, S# G9 y5 Zor("Mac"inplatformand"Macintosh"inua), ' N8 O# {0 [8 ~
} 7 s' V/ u; h, e! K
browser.close()
. T( X5 a4 i' d6 x' Freturnreport
; _( A1 h6 l, f3 O4 z% J Z: V/ {& M8 ~8 J' ~- ~4 O
* r F3 Q: }: u# q: \. C# L
if__name__=="__main__": 4 S) j; P& `9 D4 O$ y' I) k
results=[check(pid)forpidinPROFILES]
# D9 e5 e5 X6 G6 S8 nprint(json.dumps(results,ensure_ascii=False,indent=2)) ! g' Z7 r. L! N: Z3 E/ s# R6 ^- ^- {
bad=[r["profile"]forrinresultsifr["webrtc_leaked"]ornotr["tz_match"]] 3 n, J: K) g" A
print("\n需要人工处理:",bador"无") 跑完看输出里的三个字段:webrtc_leaked应该是空列表,tz_match应该是True,ua_platform_consistent应该是True。任何一项不对,先别开这个环境的店铺后台,把问题修掉再说。 5.4用MCP把调度交给AI 如果你在用支持MCP的AI客户端,可以把本地服务挂上去,用自然语言调度环境,比如"列出所有配置""启动名称带US的配置"。配置形态如下。 {
# N- B- V" y1 G# J- z( e% C* U. o5 s"mostlogin":{9 H) L0 U3 |% c' H- ~5 r& }5 U
"command":"npx",) Q4 B$ `( E4 C; S" ]
"args":[- h7 p+ j! `/ B
"-y",
' X) r+ z+ J2 w( r( R$ _4 J y( S; ]1 P* s"mcp-remote",4 M5 v$ E: Z; `& j9 q$ t
"http://127.0.0.1:30898/mcp",9 L4 Y# V* T0 q
"--transport",7 f9 N0 @3 G- D$ {& M$ _: K
"http-only",8 W2 z8 J7 `' p" V
"--allow-http",7 s! |" b2 c( g( s& h
"--header",
) Z* J, s' E d( L, [/ s0 f! s" ]"Authorization:YOUR_MOSTLOGIN_TOKEN". }4 Q2 N2 `5 Q
]
0 s6 [8 `/ R1 S9 @2 M. j) N}5 x, @2 {% ~( m' ^' e" n
} 两个安全提醒。一是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调用发生在正确的环境里、使用正确的网络出口和身份,两者是接得上的。 对一个人的团队来说,这意味着规模的天花板在往上抬。以前一个人管十个店就是极限,现在三十个店是可行的,前提是把环境、代理、台账这三件事做扎实,把重复劳动交给脚本。 技术会继续演进,平台的检测也会继续加码。但有一条不会变:稳定的、真实的、可解释的经营环境,永远比花哨的参数更值钱。
+ b6 t# Y( n% U7 d; C2 F |