一个做北美市场的投放团队,同一台办公电脑上跑6个广告账户,代理换了三条,环境隔离工具也买了,结果一个月内还是连着炸了4个。复盘下来发现问题压根不在IP:6个账户里有5个挂在同一个BM下,绑的是同一张信用卡,像素ID也是同一个,主页管理员是同一个人。平台要判定它们是一伙的,根本不需要去猜你的指纹。
Meta的关联判定不是单点判定,是三层叠加。设备与环境层、资产结构层、行为层。绝大多数广告账户被停,真正的因果在资产结构层,环境层只是给平台递了一把实锤。你把代理换得再勤,只要五个账户共用一个BM、一张卡、一个像素,证据链是你自己织好递上去的。
所以只对网络做手脚没用。环境隔离(业内也叫防关联)解决的是第一层,这一类能力MostLogin之类的多账号环境管理工具都在做,它重要,但不是全部。
真正省事的路线是:先把资产结构拆干净,再用Profile级的环境隔离把每一组资产装进互不干扰的容器,接着用分组和权限把人的行为也约束住,这三件事做全了,账户运营稳定性才会有明显变化。
一、Meta的关联判定:三层信号各自长什么样
很多人以为平台在看"你是不是代理IP"。其实IP只是几十个信号里的一个,而且它的权重远没有想象中高。真正让平台下决心的,往往是几条弱信号指向同一个方向。
1.1三层信号的完整清单
平台侧的判定可以理解成一个不断加权的图模型。节点是账号、设备、支付方式、主页、像素、域名、IP;边是你亲手连上去的。任何两条边重合,两个节点就被拉到同一个社群里。
判定层 |
主要信号 |
采集或推断方式 |
环境层能否干预 |
典型误判场景 |
设备与环境层 |
Canvas/WebGL/AudioContext指纹 |
页面JS调渲染API取哈希 |
可以,需源码级一致性 |
换指纹不换字体,露出真实字体列表 |
设备与环境层 |
User-Agent、Platform、硬件并发数 |
navigator系列只读属性 |
可以 |
UA改了,平台字段没改,自相矛盾 |
设备与环境层 |
分辨率、色深、设备像素比 |
screen对象属性 |
可以 |
分辨率设1920×1080,可用区域对不上任务栏 |
设备与环境层 |
时区、语言、地理位置 |
IntlAPI与IP库交叉比对 |
可以 |
时区填纽约,IP落在新加坡 |
设备与环境层 |
出口IP、DNS、网络提供商 |
服务端记录与rDNS反查 |
可以,靠代理与DNS配置 |
代理换了,DNS仍走本地运营商 |
设备与环境层 |
WebRTC本地候选地址 |
RTCPeerConnection建立过程 |
可以 |
关闭方式不对,STUN仍回传真实内网IP |
资产结构层 |
共用BM、共用主页管理员 |
后台资产关系图谱 |
不可干预,只能拆分 |
五个账户挂同一个BM且管理员相同 |
资产结构层 |
共用支付方式、共用主体 |
支付渠道与账户实名信息 |
不可干预,只能拆分 |
一张卡反复绑定不同账户 |
资产结构层 |
共用像素、共用转化域名 |
像素部署与事件回传 |
不可干预,只能拆分 |
两个站的PixelID完全一致 |
行为层 |
鼠标轨迹、点击节奏、滚动曲线 |
前端埋点与事件时序 |
部分可缓解 |
多个账户操作曲线高度重合 |
行为层 |
表单输入习惯、打字间隔 |
键盘事件时序 |
部分可缓解 |
复制粘贴式填写,间隔恒定 |
行为层 |
页面打开顺序、停留时长 |
导航序列建模 |
部分可缓解 |
每天9:00整依次登录十个账户 |
行为层 |
账户互动模式、好友重叠 |
内部社交图谱 |
不可干预 |
多个账户加同一批人、进同样的小组 |
这张表里最该记住的一行是资产结构层。环境层你做得再干净,只要资产层共用,平台有现成的结论。
1.2为什么"资产结构"是主干
原因很简单:设备指纹是可以伪造的,资产关系不行。
一个BM的创建需要主体信息,一个支付方式需要实名,一个像素部署在域名上。这些东西带有强身份属性,平台对它们的信任度远高于对Canvas哈希的信任度。换句话说,指纹那条链是"疑似",资产这条链是"实锤"。
行业里常见的错误顺序是:先买工具解决环境,资产结构原封不动。于是环境做对了,账户还是挂。然后得出结论说工具没用。其实顺序反了。
正确的顺序是先把BM、支付方式、主页管理员、像素、域名这五样按业务线拆开,再谈环境。拆不开的部分,就别开第二个账户。这句话听着刺耳,但它是真的。
二、五类资产结构的隔离要求
把资产当成一张表来管,比靠脑子记靠谱得多。下面这张表可以直接抄进你们的资产台账。
资产类型 |
共用会造成什么 |
隔离要求 |
优先级 |
备注 |
商务管理平台(BM) |
一号受限,同BM下账户连带审查 |
每条业务线一个BM,主体真实且可解释 |
高 |
主体要有真实业务理由,不做重复开户 |
支付方式 |
一张卡绑多个账户,直接坐实同一控制人 |
一组账户一条独立支付路径 |
高 |
卡户名与主体信息保持一致 |
主页与管理员 |
管理员重叠等于身份重叠 |
不同主页尽量不共用同一批管理员 |
高 |
确需共用时控制在最小人数 |
像素(Pixel) |
多站共用像素,域名关系被串起来 |
每个站点或每条业务线独立像素 |
中高 |
事件命名也要区分,别完全照抄 |
投放域名 |
同域名多账户投放,审核与资产双关联 |
按品牌或区域分配独立域名 |
中 |
域名WHOIS信息不要自相矛盾 |
个人号 |
个人号是BM的入口,一个号崩了带一串 |
关键岗位个人号独立环境、独立恢复通道 |
高 |
双人验证与恢复邮箱要真实可用 |
邮箱与手机号 |
恢复通道共用等于身份共用 |
一组资产一套独立邮箱与号码 |
高 |
别用同一个邮箱做多个账户的恢复地址 |
这里有个容易被忽略的点:个人号。很多团队把重心放在广告账户上,结果最先出事的是那个用来管BM的个人号。个人号一受限,下面的广告账户跟着全停。所以个人号的环境隔离优先级反而应该排在最前面。
环境隔离工具解决的是"同一台设备上管理多个真实业务身份"的问题,它不是行为豁免。真实身份、真实业务、遵守Meta的商业条款与广告政策,这是底线。用虚假身份开户、为了规避已有封禁而重复开户,这些做法既违反平台条款,也会让前面所有技术投入全部作废。
三、环境隔离在Profile级是怎么实现的
资产结构拆完之后,才轮到环境,这一层的目标是:让平台从浏览器侧看到的东西,彼此之间没有任何共用的痕迹,且内部自洽。
3.1隔离的六个面
一个Profile(配置)至少要切开六个面:Cookie、LocalStorage、Session、IndexedDB、缓存、代理隧道。
前四个好理解,都是浏览器存储。关键在于"缓存"和"代理隧道"这两项经常被省掉。缓存不隔离的话,HTTP缓存、DNS缓存、字体缓存会跨环境复用,平台的边缘节点照样能看出请求来自同一台机器。代理隧道不隔离更直接:所有环境复用同一个连接池或同一个DNS解析路径,等于把隔离做了一半。
指纹维度 |
常见采集API |
容易漏改的地方 |
隔离实现方式 |
Canvas |
canvas.toDataURL/getImageData |
Worker内绘制、OffscreenCanvas |
渲染引擎层挂钩,取环境设定值 |
WebGL |
getParameter/getExtension/渲染哈希 |
UNMASKED_RENDERER、扩展顺序 |
同上,连同GPU参数一起返回 |
AudioContext |
createOscillator+AnalyserNode |
OfflineAudioContext分支 |
采样缓冲按环境种子改写 |
WebRTC |
RTCPeerConnection本地候选 |
内网IP、mDNS候选 |
候选地址改写并走代理隧道 |
字体 |
measureText宽度比对 |
系统字体枚举、@font-face回退 |
字体列表按环境注入且一致 |
分辨率与色深 |
screen.width/colorDepth/DPR |
可用区域与窗口尺寸不自洽 |
分辨率、DPR、可用区域联动 |
时区与语言 |
Intl.DateTimeFormat/navigator.language |
与IP地理不匹配 |
时区、语言、地理三者同步 |
硬件信息 |
hardwareConcurrency/deviceMemory/platform |
与UA或OS声明矛盾 |
随OS模板整体生成 |
UA与客户端提示 |
navigator.userAgent/userAgentData |
UA改了但brands没改 |
UA与ClientHints同步 |
3.2源码级改写、插件注入、参数覆盖,差在哪
(1)参数覆盖
做法是启动浏览器时通过命令行开关或者启动后覆盖navigator上的属性。优点是快,缺点是自相矛盾。你改了`navigator.userAgent`,但`navigator.userAgentData.brands`还是原值;你改了`platform`,但`navigator.oscpu`没动;你把`hardwareConcurrency`设成8,可`SharedArrayBuffer`的实际并发表现还是16核。
平台只要做一次交叉校验,就能抓出"这个浏览器在说谎"。更麻烦的是这类覆盖跑在JS层,页面可以用`Object.getOwnPropertyDescriptor`看穿属性是后加的,甚至直接删掉你的覆盖。
(2)插件注入
比参数覆盖强一点,通过扩展在文档创建前注入脚本。但扩展的注入时机和内容安全策略打架,遇到CSP严格的页面就失效;而且扩展本身是可枚举的,`navigator.plugins`和扩展ID会露馅。更要命的是Worker和iframe。
主线程里的`navigator`被你改了,`newWorker()`里的`self.navigator`是另一个全局,你在主线程做的覆盖根本传不过去。iframe同理,跨源iframe里的采集拿不到你注入的脚本。平台把指纹采集脚本放进Worker里跑,插件注入的防线就直接被绕过去了。
(3)源码级挂钩
这是在Chromium的C++源码层,对Canvas、WebGL、WebRTC、AudioContext这些采集API的实现做改写,让它们从渲染管线内部就返回与环境设定一致的数值。区别在于:改的是取值本身,不是取值之后的结果。
所以无论采集代码跑在主线程、Worker、SharedWorker还是iframe里,拿到的都是同一套经过处理的值,不存在"改了这边漏了那边"。同时因为改写在渲染管线内部,字体度量、抗锯齿、绘制顺序这些派生特征也是自洽的,不会出现"字体列表是Windows的,但渲染出的字形间距是macOS的"这种破绽。
MostLogin走的是第三条路,基于改良版Chromium,在C++层面对这几类采集API挂钩。判断一个工具是不是真做了源码级改写。
有个土办法:写个页面,在主线程、Worker、iframe三处各取一次Canvas哈希和WebGLrenderer,看三处是否一致且与设定相符;再比对userAgentData.brands、platform、oscpu、hardwareConcurrency四者之间有没有矛盾。参数覆盖的方案一般撑不过这两轮。
四、代理选型:别让IP成为最短的那块板
环境做得再好,代理选错照样白搭。
4.1三类代理的实际差别
代理类型 |
来源特征 |
Meta视角的风险 |
适用场景 |
选型建议 |
住宅代理 |
真实家庭宽带,ISP记录完整 |
较低,接近真实用户 |
主力投放账户、长期运营 |
静态独享优先 |
移动代理 |
运营商蜂窝网络,NAT出口 |
较低,但IP共享度偏高 |
移动端场景、区域化测试 |
注意出口是否轮换 |
数据中心代理 |
机房IP,ASN一眼可辨 |
较高,易被标记 |
内部工具、非核心流程 |
不建议用于核心账户 |
共享住宅 |
多人复用同一出口 |
中高,容易被邻座拖累 |
预算极有限的短期测试 |
尽量避免 |
ASN这件事值得多说一句。平台看的不只是"这是不是一个家庭宽带IP",还包括这个IP段的历史行为密度。同一段里如果已经有大量被标记的账户,你分到一个新IP也容易跟着受影响。所以问供应商要ASN信息和IP段的纯净度说明,比单纯比价格有用。
4.2静态独享为什么优先
稳定本身就是一种可信度。
一个真实用户的行为模式是"长期在某个地理范围内活动"。今天纽约明天伦敦后天新加坡,这不是出差,这是异常。平台对"IP频繁换国家"的敏感度远高于对"IP是住宅还是机房"的敏感度。行业里很多账户出问题,不是因为IP不好,是因为换得太勤。
具体到配置上:一个Profile固定绑一个静态出口,地理位置、时区、语言、货币四件套跟着这个出口走,长期不变。确实需要多地区投放时,按区域分组,每组一套固定出口,而不是在单个账户上频繁切换。
另外别忘了DNS。代理只接管了流量,DNS如果还是走本地运营商,解析请求的来源地就跟IP对不上,这属于典型的"半吊子隔离"。务必确认代理配置里DNS也走隧道。
五、广告账户分组策略与权限设计
环境准备好之后,剩下一个管理问题:怎么分组,怎么分权。
5.1分组维度
分组这件事没有标准答案,但有三个维度几乎总是用得上:品牌维度、区域维度、测试与转化分离。
分组维度 |
划分依据 |
环境要求 |
代理要求 |
典型风险 |
品牌维度 |
不同品牌或独立项目 |
每组独立Profile组,OS与分辨率差异化 |
每组独立静态出口 |
跨品牌共用像素串起关系 |
区域维度 |
不同投放市场 |
时区、语言、货币随目标市场 |
出口固定在目标国家 |
出口与投放地区不一致 |
测试组 |
素材与策略验证 |
与转化组物理隔离,独立个人号 |
独立出口,允许适度轮换 |
测试失败拖累主力账户权重 |
转化组 |
稳定跑量的主力账户 |
环境长期不变,变更需走审批 |
静态独享,禁止轮换 |
频繁改动触发重新审查 |
客户代投 |
每个客户独立资产包 |
客户间环境完全不共用 |
客户间出口完全隔离 |
一个客户违规连带其他客户 |
测试组和转化组必须分开,这点要单独强调。测试意味着高频率的创建、修改、暂停,这些动作本身就是风险信号。把它们和稳定跑量的账户混在一起,等于让主力账户替测试行为背锅。
5.2权限与操作日志
多人协作是另一大关联来源。不是技术上的关联,是行为上的。
角色 |
可操作范围 |
环境约束 |
日志要求 |
管理员 |
创建配置、分配权限、变更代理 |
操作走独立管理端,不下场操作账户 |
全量记录,留存可追溯 |
投放负责人 |
所属分组内全部配置 |
一人一组,避免多组并行 |
记录启停、代理变更、配置导出 |
素材执行 |
仅素材上传与广告编辑 |
禁止修改代理与指纹 |
记录编辑动作与时间戳 |
数据观察 |
只读查看数据面板 |
不允许登录账户后台 |
记录查询行为即可 |
外部协作方 |
限定单个配置,限期访问 |
通过配置分享授予,不给原始凭证 |
访问起止时间必须留痕 |
两条硬规则:同一时间只有一个人在一个账户里操作;任何代理与指纹的变更都要留痕。前者避免行为曲线叠加,后者保证出事之后能回溯到具体动作。MostLogin这类工具的团队权限与操作日志就是干这个用的,配置分享还能在不暴露原始登录凭证的前提下把指定环境交给外部协作方。
六、用本地API按分组批量启动并绑定各自代理
讲完原理和配置,上代码。下面的示例以本地RESTAPI为入口,接口路径与字段名请以当前客户端版本的官方文档为准,不同版本可能有调整。
6.1分组定义
先把分组写成配置,别散落在各个脚本里。
{
"workspace":"meta-ads",
"rateLimitPerSec":5,
"groups":[
{
"name":"US-BrandA-Conversion",
"profilePrefix":"BM-A-US-",
"profileIds":["BM-A-US-01","BM-A-US-02","BM-A-US-03"],
"fingerprint":{"os":"win","resolution":"1920x1080","dpr":1,"fonts":"win-en"},
"geo":{"country":"US","timezone":"America/New_York","locale":"en-US"},
"proxy":{"type":"http","host":"us-a.resi.example","port":8000,"user":"p1","pass":"s1","dnsOverProxy":true}
},
{
"name":"DE-BrandB-Testing",
"profilePrefix":"BM-B-DE-T",
"profileIds":["BM-B-DE-T01","BM-B-DE-T02"],
"fingerprint":{"os":"mac","resolution":"1512x982","dpr":2,"fonts":"mac-de"},
"geo":{"country":"DE","timezone":"Europe/Berlin","locale":"de-DE"},
"proxy":{"type":"socks5","host":"de-b.resi.example","port":9000,"user":"p2","pass":"s2","dnsOverProxy":true}
}
]
}
6.2按分组批量启动
//batch-start-groups.mjs
//用法:nodebatch-start-groups.mjsgroups.json
//说明:接口路径/字段名以当前客户端版本官方文档为准
importfsfrom'node:fs';
constAPI='http://127.0.0.1:30898/api/v1';
constTOKEN=process.env.MOSTLOGIN_TOKEN;//授权值等同密码,不要写进仓库
constcfg=JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
constsleep=(ms)=>newPromise((r)=>setTimeout(r,ms));
asyncfunctionapi(path,body){
constres=awaitfetch(API+path,{
method:'POST',
headers:{'Content-Type':'application/json','Authorization':`Bearer${TOKEN}`},
body:JSON.stringify(body??{})
});
if(!res.ok)thrownewError(`${path}->${res.status}${awaitres.text()}`);
return(awaitres.json()).data;
}
//把分组里的geo/fingerprint/proxy写回配置,再启动
asyncfunctionprepareAndStart(group,profileId){
awaitapi('/browser/update',{
profileId,
os:group.fingerprint.os,
resolution:group.fingerprint.resolution,
devicePixelRatio:group.fingerprint.dpr,
fontPreset:group.fingerprint.fonts,
timezone:group.geo.timezone,
locale:group.geo.locale,
proxy:{
type:group.proxy.type,
host:group.proxy.host,
port:group.proxy.port,
username:group.proxy.user,
password:group.proxy.pass,
dnsOverProxy:group.proxy.dnsOverProxy//DNS必须跟着代理走
}
});
conststarted=awaitapi('/browser/start',{profileId});
return{profileId,group:group.name,debugPort:started.debugPort,ws:started.ws};
}
//按套餐限速排队:基础版2/s、进阶版5/s、专业版10/s、企业版20/s
asyncfunctionrunGroup(group){
constgap=Math.ceil(1000/cfg.rateLimitPerSec);
constout=[];
for(constidofgroup.profileIds){
out.push(awaitprepareAndStart(group,id));
awaitsleep(gap);
}
returnout;
}
constresults=[];
for(constgofcfg.groups){
console.log(`[group]${g.name}->${g.profileIds.length}个配置`);
results.push(...awaitrunGroup(g));
}
fs.writeFileSync('sessions.json',JSON.stringify(results,null,2));
console.log('已写入sessions.json,下一步交给审计脚本逐个体检');
这段代码的关键不是启动本身,是启动之前的"写回"。分组里的时区、语言、分辨率、DNS设置必须在启动前落到配置上,否则启动之后的环境和分组定义对不上,体检会一片红。
6.3挂CDP做环境体检
启动完了别急着投放,先跑一遍体检。这个脚本检查四件事:WebRTC是否泄漏真实地址、DNS是否走了代理、时区与IP地理是否一致、字体与分辨率是否自洽。
#env_audit.py用法:pythonenv_audit.pysessions.json
importjson,re,sys,urllib.request
fromplaywright.sync_apiimportsync_playwright
UA_RE=re.compile(r"WindowsNT10\.0")
EXPECT={#每个分组期望的国别,实际项目里从配置读取
"US-BrandA-Conversion":"US",
"DE-BrandB-Testing":"DE",
}
defip_geo():
withurllib.request.urlopen("https://ipinfo.io/json",timeout=10)asr:
d=json.load(r)
returnd.get("ip"),d.get("country"),d.get("timezone")
defaudit(ws,group,page_url="https://www.facebook.com/adsmanager"):
withsync_playwright()asp:
browser=p.chromium.connect_over_cdp(ws)
ctx=browser.contexts[0]
page=ctx.new_page()
page.goto(page_url,wait_until="domcontentloaded",timeout=60000)
report=page.evaluate("""()=>{
//1)WebRTC本地候选,看是否漏出内网或真实公网地址
constcands=[];
returnnewPromise((resolve)=>{
constpc=newRTCPeerConnection({iceServers:[]});
pc.createDataChannel('x');
pc.onicecandidate=(e)=>{
if(e.candidate)cands.push(e.candidate.candidate);
};
pc.createOffer().then(o=>pc.setLocalDescription(o));
setTimeout(()=>{
constm=navigator.mediaDevices;
resolve({
webrtc:cands,
//2)三处取Canvas哈希:主线程/Worker/iframe一致性
canvasMain:(()=>{
constc=document.createElement('canvas');
constg=c.getContext('2d');
g.fillText('ml-test',10,10);
returnc.toDataURL().slice(-32);
})(),
//3)UA与相关字段是否自相矛盾
ua:navigator.userAgent,
brands:navigator.userAgentData
?navigator.userAgentData.brands.map(b=>b.brand).join('|'):null,
platform:navigator.platform,
oscpu:navigator.oscpu,
cores:navigator.hardwareConcurrency,
mem:navigator.deviceMemory,
//4)分辨率与可用区域是否自洽
screen:[screen.width,screen.height,screen.colorDepth,devicePixelRatio],
avail:[screen.availWidth,screen.availHeight],
tz:Intl.DateTimeFormat().resolvedOptions().timeZone,
lang:navigator.language,
dnt:navigator.doNotTrack,
});
pc.close();
},1500);
});
}""")
#Worker与iframe里再取一次,比对是否与主线程一致
worker_canvas=page.evaluate("""async()=>{
constsrc=URL.createObjectURL(newBlob([`
self.onmessage=()=>{
constc=newOffscreenCanvas(200,40);
constg=c.getContext('2d');
g.fillText('ml-test',10,10);
postMessage(c.convertToBlob?'offscreen-ok':'no-offscreen');
};
`],{type:'text/javascript'}));
constw=newWorker(src);
returnawaitnewPromise(r=>{w.onmessage=e=>r(e.data);w.postMessage(1);});
}""")
ip,country,tz_from_ip=ip_geo()
report.update(workerCanvas=worker_canvas,ip=ip,ipCountry=country,ipTz=tz_from_ip)
problems=[]
#WebRTC泄漏:出现mDNS之外的私有地址或本地真实地址即告警
forcinreport["webrtc"]:
ifre.search(r"typ(host|srflx)",c)andnotc.endswith(".local"):
problems.append(f"WebRTC疑似泄漏:{c}")
#地理一致性
ifEXPECT.get(group)andcountry!=EXPECT[group]:
problems.append(f"IP国别{country}!=分组期望{EXPECT[group]}")
iftz_from_ipandreport["tz"]andtz_from_ip!=report["tz"]:
problems.append(f'时区{report["tz"]}与IP时区{tz_from_ip}不一致')
#UA自洽
ifUA_RE.search(report["ua"])andreport["platform"]!="Win32":
problems.append(f'UA声明Windows,platform却是{report["platform"]}')
ifreport["cores"]andreport["mem"]andreport["cores"]>32:
problems.append("硬件并发数异常")
#分辨率自洽
w,h,depth,dpr=report["screen"]
ifreport["avail"][0]>worreport["avail"][1]>h:
problems.append("可用区域大于物理分辨率")
report["group"]=group
report["problems"]=problems
browser.close()
returnreport
if__name__=="__main__":
sessions=json.load(open(sys.argv[1]))
forsinsessions:
r=audit(s["ws"],s["group"])
flag="OK"ifnotr["problems"]else"FAIL"
print(f'[{flag}]{s["profileId"]}{r["ip"]}{r["ipCountry"]}tz={r["tz"]}')
forpinr["problems"]:
print("-",p)
跑完只要有一个FAIL,就别急着上量。最常见的三类问题:DNS没走隧道导致ipinfo返回的还是本地地址;时区改了但语言没跟上;分辨率设成1920×1080但可用区域算错了任务栏高度。这些都是配置文件级别的疏漏,改完重启就行。
6.4批量导入导出配置
环境多了之后,手工点是不现实的。导出做成每日快照,导入用于迁移或新机器恢复。
#!/usr/bin/envbash
#profile-sync.sh导出快照/恢复配置
#接口路径以当前客户端版本官方文档为准
set-euopipefail
API="http://127.0.0.1:30898/api/v1"
TOKEN="${MOSTLOGIN_TOKEN:?请设置MOSTLOGIN_TOKEN环境变量}"
STAMP="$(date+%Y%m%d-%H%M)"
OUT="./backup/${STAMP}"
mkdir-p"$OUT"
auth=(-H"Content-Type:application/json"-H"Authorization:Bearer${TOKEN}")
#1)导出全部配置元数据(不含登录凭证,凭证由平台侧加密保管)
curl-sS"${auth[@]}"-XPOST"${API}/browser/export"\
-d'{"scope":"all","includeProxy":false}'-o"${OUT}/profiles.json"
echo"导出完成:${OUT}/profiles.json($(wc-c<"${OUT}/profiles.json")字节)"
#2)只导出某个分组,配合分组策略做增量备份
GROUP="${1:-US-BrandA-Conversion}"
curl-sS"${auth[@]}"-XPOST"${API}/browser/export"\
-d"{\"scope\":\"group\",\"group\":\"${GROUP}\",\"includeProxy\":false}"\
-o"${OUT}/${GROUP}.json"
#3)恢复:逐条导入,失败的配置单独列出
if["${2:-}"="--restore"];then
jq-c'.data[]'"${OUT}/profiles.json"|whileread-ritem;do
name="$(echo"$item"|jq-r'.name')"
code="$(curl-sS-o/dev/null-w'%{http_code}'"${auth[@]}"\
-XPOST"${API}/browser/import"-d"$item")"
["$code"="200"]||echo"导入失败:${name}(HTTP${code})"
done
fi
导出时建议把includeProxy关掉。代理密码属于高敏感信息,别躺在一个json文件里到处传。恢复的时候单独走一遍代理批量更新接口,从你们自己的密钥管理里取值。
七、验证与排错:三个高频坑
(1)体检全绿但账户还是被标记
这种情况大概率不在环境层,回到资产结构层查:BM是不是共用了,支付路径是不是同一条,像素是不是同一个ID,域名是不是同一批。
(2)WebRTC一直报泄漏
确认改的是候选地址本身,不是简单的"禁用WebRTC"。直接禁用会让部分页面的功能异常,反而成了新的异常信号。正确做法是让它返回与代理一致的候选。
(3)时区对了但地理还是判错
多半是DNS或IPv6没接管。IPv6出口经常是默认开启的,代理只管了IPv4,平台照样能看到真实地址。
八、平台检测技术的演进:接下来会怎么打
把视角拉长一点看,这场对抗正在从"静态特征比对"转向"动态行为建模"。
早几年平台比的是指纹哈希:两个账户的Canvas值一样,那就关联
现在指纹哈希依然是基础项,但权重在下降,因为工具侧的指纹质量普遍上来了,光靠哈希区分度不够。平台真正在加码的是行为序列建模:鼠标移动的加速度曲线、从打开页面到点击第一个按钮的时间分布、编辑广告时字段的填写顺序、多账户之间的操作时间间隔是否呈现周期性。这些特征不太容易靠静态配置伪造,它们来自人的习惯。
工具侧的应对也在变
一类是行为随机化,在按键与点击之间插入随机延迟(比如同步器里推荐的50到100毫秒输入延迟),让节奏不像脚本;另一类是自然交互模拟,把操作路径变成有噪声的序列而不是固定脚本;再往前一步是自适应指纹轮换,根据账户的生命周期阶段动态调整环境参数的变化幅度,新账户求稳,成熟账户才允许小幅波动。
AI进来之后,节奏会更快
平台侧用大模型做跨域的行为表征,把广告账户的操作、主页的内容、支付的路径揉成一个向量做相似度检索,这比规则引擎难对付得多。工具侧的对应打法大概率是让AI来生成"人类噪声",也就是符合某个具体人群习惯的行为分布,而不是简单的随机数序列。同时MCP这类协议让AI客户端能直接调度本地浏览器环境,"一句话拉起两个分组做对照测试"这种事已经能做了,运营的分工会从人操作工具变成人指挥Agent。
但有一条不会变:技术只能压低环境层的风险,压不掉资产结构层和内容合规层的风险。共用BM就是共用,虚假主体就是虚假,违反广告政策就是违反。工具提供的是环境隔离,从来不是行为豁免。把这条记牢,比多买几套指纹有用得多。
回到开头那个团队。后来他们做的第一件事不是换工具,而是把五条业务线拆成五个BM、五条支付路径、五套像素和域名,然后才按品牌和区域建了两组环境,每组固定静态出口,操作日志全部留痕。三个月后同类问题的发生率明显下降,投放节奏也稳了。
环境隔离是手段,资产结构是主干,行为合规是底线。这三句话的顺序别搞反了