写在最前面:
- 禁止说“这不就是多开几个浏览器”。你多开30个Chrome试试,不蓝屏算我输。
- 本文所有坑都是我拿命换的,看到就是赚到。
- 店群老板们,如果你还在让运营手动切号,建议直接把这篇文章转给技术。
两年前的夏天,我被一个做TEMU店群的朋友拉去“参观”他的工作室。
两排电脑,六个运营,每人面前贴着密密麻麻的便利贴:账号、密码、代理IP、Cookie过期时间。
整个屋子像极了达美乐后厨——每个人都低着头,机械地重复“登录-截图-退出-清缓存-换代理-登录”。
我站了半小时,一个姑娘连头都没抬过。
朋友叹气:林焱,你看到了,这就是我的日常。一百多个店铺,人肉运维,每个月工资发出去五六万,利润全被吃掉了。买了影刀脚本,十来个号一起跑就卡死;上了指纹浏览器,隔离还行,但并发一高电脑直接崩。
他说这话时,隔壁工位的电脑刚好蓝屏了。
我当天晚上回去就开始写代码。
半年后,Alien 1.0 跑在他那台32核工作站上,22个浏览器窗口整整齐齐,一百多个店铺的任务并发调度,CPU稳稳压在70%。
那个曾经蓝屏的工位,现在放着一盆绿萝。
一、店群“并发卡死”的本质
很多人以为卡死是因为电脑配置不够。
不对。
我们做过测试:同样开22个Chrome窗口,手动打开,每个窗口随便逛逛,机器很流畅。
但一旦用影刀脚本自动化操作,半小时后就开始卡。
为什么?
因为脚本不会“节制”。
它不管当前CPU是不是已经满了,不管内存是不是在疯狂分配,不管磁盘IO是不是在排队。
它只管一件事:拿到一个店铺,就开一个浏览器,执行任务,然后——不退出。
几十个Chrome进程同时跑,每个都在读写自己的缓存目录,再加上影刀本身的日志和截图,磁盘队列长度直接飙到几十。
这才是卡死的真相:不是硬件不够,是资源调度没有章法。
所以我的破局思路很简单:
不要用影刀去管理浏览器实例。影刀只负责“用手操作”,Python负责“用脑子调度”。
调度器控制同时运行的窗口数量,超出就排队。任务执行完,浏览器不急着关,复用给下一个任务。空闲太久才回收,顺便清理缓存。
就这么一套机制,把“并发卡死”变成了“并发稳如老狗”。
下面我拆开讲Alien的核心模块。
二、环境隔离矩阵:Alien的“环境管理中心”
2.1 界面设计:让老板一眼看懂
Alien的“环境管理中心”不是给极客用的,是给每天切号切到手抽筋的运营用的。
所以界面必须像Excel一样直观。
左侧是一棵分组树。根节点是平台(拼多多/TEMU/TikTok),下面可以建多级子分组,比如“TEMU → 美区 → 服装类目”。
右侧是一个表格,每一行是一个店铺环境。列包括:
- 店铺ID(可编辑别名)
- 代理IP(带在线检测图标)
- 环境状态(🟢在线 / 🟡需重新登录 / 🔴异常)
- 最后操作时间
- 健康分(基于失败率自动计算)
- 操作按钮(打开、编辑、运行、删除)
顶部四个大按钮:导入CSV、新建分组、批量打开、导出报表。
导入CSV模板只有三列:shop_id, platform, proxy。其他指纹参数(分辨率、时区、语言)系统自动生成,生成规则用店铺ID做哈希种子——保证同一店铺每次指纹不变,不同店铺差异足够大。
“批量打开”是运营最爱。选中10个店铺,点一下,系统依次启动10个独立的Chrome窗口,每个窗口已经配好代理和指纹。她们可以像操作普通浏览器一样进去处理订单、改价格,不用记任何账号密码。
2.2 技术实现:独立Profile + 指纹生成
环境隔离的核心是Chromium的 --user-data-dir。
每个店铺启动时,指定一个完全独立的用户数据目录。这个目录里装着该店铺所有的Cookies、LocalStorage、缓存、插件状态。只要目录不串,店铺就不会串。
但只有目录隔离不够。平台还会采集分辨率、时区、语言、WebGL、Canvas指纹。如果两个店铺这些特征完全一样,仍然容易被关联。
所以我写了一个 EnvironmentManager,为每个店铺生成一套稳定的指纹配置。
import os
import json
import hashlib
import random
from pathlib import Path
class AlienEnv:
BASE = Path("./AlienData")
def __init__(self, shop_id, platform):
self.shop_id = shop_id
self.platform = platform
self.dir = self.BASE / platform / shop_id
self.profile = self.dir / "profile"
self.config = self.dir / "fingerprint.json"
def create(self, proxy, group="default"):
self.dir.mkdir(parents=True, exist_ok=True)
self.profile.mkdir(exist_ok=True)
fp = self._gen_fingerprint(proxy, group)
with open(self.config, "w") as f:
json.dump(fp, f, indent=2)
# 预建缓存目录
(self.profile / "Cache").mkdir(exist_ok=True)
(self.profile / "Local Storage").mkdir(exist_ok=True)
return str(self.profile)
def _gen_fingerprint(self, proxy, group):
seed = hashlib.md5(f"{self.platform}:{self.shop_id}:{group}".encode()).hexdigest()[:8]
rng = random.Random(int(seed, 16))
res_pool = [(1920,1080), (1366,768), (1440,900), (1536,864)]
tz_map = {
"pdd": ["Asia/Shanghai"],
"temu": ["America/New_York", "America/Los_Angeles"],
"tiktok": ["America/New_York", "Europe/London"]
}
lang_map = {
"pdd": ["zh-CN"],
"temu": ["en-US", "en-GB"],
"tiktok": ["en-US", "en-GB"]
}
return {
"proxy": proxy,
"group": group,
"screen_w": rng.choice(res_pool)[0],
"screen_h": rng.choice(res_pool)[1],
"timezone": rng.choice(tz_map.get(self.platform, ["UTC"])),
"language": rng.choice(lang_map.get(self.platform, ["en-US"])),
"platform_os": rng.choice(["Win32", "MacIntel"]),
"webgl": rng.choice(["Google Inc.", "Intel Inc."]),
"cores": rng.choice([2,4,8])
}
def load(self):
if not self.config.exists():
return None
with open(self.config) as f:
return json.load(f)
启动浏览器时,读取指纹配置,拼装命令行参数,并开启远程调试端口供影刀连接。
def launch(env):
cfg = env.load()
cmd = [
"chrome.exe",
f"--user-data-dir={env.profile}",
f"--proxy-server={cfg['proxy']}",
f"--window-size={cfg['screen_w']},{cfg['screen_h']}",
f"--lang={cfg['language']}",
"--remote-debugging-port=0",
"--disable-blink-features=AutomationControlled"
]
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# 从输出解析调试端口...
这样每个店铺就是一个完全独立的“虚拟电脑”。
三、自动化调度编排:22个窗口并发不卡不崩
3.1 拖拽式流程设计
Alien的“自动化编排流”模块,让运营像搭积木一样组合业务流程。
左侧动作库:
- 店铺控制:打开环境、关闭环境
- 登录管理:自动登录、刷新Cookie
- 电商操作:上架商品、领优惠券、同步订单、回复消息
- 流程控制:等待、循环、条件判断
运营把动作拖到画布上,用箭头串联。
比如一个“TEMU每日养号”流程:
打开环境 → 自动登录 → 浏览首页30秒 → 随机点开3个商品 → 每个停留15秒 → 关闭环境
流程保存后,可以分配给单个店铺、一个分组或所有店铺。
3.2 调度核心:有界并发 + 店铺级互斥
调度器要解决三个问题:
- 同时运行的浏览器窗口不能超过物理极限(我们设为22)
- 同一个店铺不能同时执行多个任务(防止数据错乱)
- 任务结束后浏览器不急着关,复用给下一个任务
下面这段是调度器的核心骨架:
import threading
import queue
import time
from concurrent.futures import ThreadPoolExecutor
class Scheduler:
def __init__(self, max_concurrent=22):
self.max_concurrent = max_concurrent
self.task_q = queue.Queue()
self.active = 0
self.active_lock = threading.Lock()
self.shop_busy = {}
self.shop_lock = threading.Lock()
self.pool = ThreadPoolExecutor(max_workers=max_concurrent)
def submit(self, shop_id, flow, params=None):
self.task_q.put((shop_id, flow, params))
def _worker(self):
while True:
shop_id, flow, params = self.task_q.get()
# 等该店铺空闲
while True:
with self.shop_lock:
if not self.shop_busy.get(shop_id, False):
self.shop_busy[shop_id] = True
break
time.sleep(0.5)
# 等有空闲槽位
while True:
with self.active_lock:
if self.active < self.max_concurrent:
self.active += 1
break
time.sleep(0.5)
self.pool.submit(self._run, shop_id, flow, params)
def _run(self, shop_id, flow, params):
try:
debug_port = self._get_or_launch_browser(shop_id)
self._call_yingdao(flow, shop_id, debug_port, params)
finally:
with self.active_lock:
self.active -= 1
with self.shop_lock:
self.shop_busy[shop_id] = False
self.task_q.task_done()
def _get_or_launch_browser(self, shop_id):
# 检查该店铺是否已有存活浏览器实例,没有则启动
pass
def _call_yingdao(self, flow, shop_id, port, params):
import subprocess, json
cmd = [
"影刀RPA.exe", "-run", flow,
"-param", f"shop_id={shop_id}",
"-param", f"debug_port={port}",
"-param", f"params={json.dumps(params)}"
]
subprocess.run(cmd, timeout=600, check=True)
def start(self):
for _ in range(self.max_concurrent):
threading.Thread(target=self._worker, daemon=True).start()
这个调度器上线后,22个窗口长期满负荷运行,CPU和内存曲线是一条直线,再也没有出现过“跑着跑着卡死”的情况。
踩坑:早期版本没有 shop_busy 互斥锁,结果同一个店铺的两个任务(比如“上架”和“领券”)同时操作浏览器,一个在点“发布”,另一个在点“删除”,把商品删光了。加上锁之后,同一店铺的任务强制串行,问题解决。
3.3 智能平铺
22个窗口如果全部堆叠在左上角,影刀点击时会点错目标。
我在启动每个浏览器后,调用Windows API把窗口平铺到整个屏幕。
def tile_windows(hwnds, cols=5):
sw = 1920
sh = 1080
tw = sw // cols
rows = (len(hwnds) + cols - 1) // cols
th = sh // rows
for i, hwnd in enumerate(hwnds):
row = i // cols
col = i % cols
x = col * tw
y = row * th
win32gui.SetWindowPos(hwnd, None, x, y, tw, th, 0x0040)
运营第一次看到22个窗口整整齐齐排列时,说:“这才是自动化该有的样子。”
四、底层工程封装:双击exe就能用
老板和运营不懂Python、不懂ChromeDriver、不懂环境变量。
所以Alien必须是一个双击就能跑的商业软件。
4.1 PyQt6 做的专业界面
我用PyQt6写了完整的GUI,抛弃了命令行黑框框。
- 仪表盘:用pyqtgraph画实时曲线,显示并发数、任务成功率、内存占用
- 环境管理:QTreeView分组树 + QTableView表格,支持拖拽、右键菜单、批量操作
- 流程编排:基于QGraphicsView的画布,节点可拖拽、连线可编辑
- 日志面板:彩色输出,支持按店铺ID搜索
暗黑主题,圆角卡片,按钮有悬停效果。客户第一次打开说:“这软件看起来就很贵。”
4.2 PyInstaller 打包成单文件
客户不需要安装任何运行环境。
我用PyInstaller把Python解释器、所有依赖库、一个便携版Chromium(约110MB)全部打进一个Alien.exe。
首次启动时,程序在%APPDATA%\Alien下解压Chromium,创建数据目录。
客户只需要:下载 → 解压 → 双击exe。
整个交付过程不超过两分钟。
4.3 一机一码安全验证
为了防止软件被随意复制,我做了硬件绑定授权。
程序启动时获取硬盘序列号、MAC地址、主板ID,生成机器码。用户把机器码发给我,我用RSA私钥签名生成license文件。
每次启动验证签名和硬件是否匹配。
虽然不能100%防破解,但已经挡住了99%的“拷贝即用”。
五、血泪踩坑清单
1. 内存泄漏排查了两周。
线上跑了50个店铺,内存从2GB慢慢涨到14GB,然后卡死。查了很久,发现每个浏览器实例退出后,user-data-dir/Cache和Code Cache没有被清理。日积月累,一个店铺的Profile能到5GB。后来加了定时清理:空闲超过1小时的店铺,删除其缓存目录,保留Cookies和LocalStorage。
2. 影刀超时导致进程残留。
影刀流程默认等待元素60秒,跨境网络经常超时。超时后我们杀进程,但Chrome没关。几天后系统里残留几十个chrome.exe。解决方案:在任务finally块里调用psutil.Process(pid).kill(),并加了超时后强制清理的兜底逻辑。
3. 代理IP的质量决定生死。
图便宜买过5元/天的静态代理,用了两天被TEMU全部拉黑。后来换成30元/天的住宅代理池,封店率从15%降到0.5%。这个钱不能省。
4. 运营的误删事故。
有一次运营在环境管理里选中“全部店铺”,然后点了“删除”。一瞬间所有环境配置都没了。我从备份恢复,但损失了当天部分任务记录。之后我加了二次确认弹窗和回收站机制——删除的环境先进入回收站,7天后才真正删除。
写在最后
从一个人写Alien到现在,已经两年。
它从最初的几十行脚本,长成了上万行代码的完整系统。
有人问我:你为什么不直接用现成的指纹浏览器加影刀?
我的回答很简单:自己造轮子不是为了证明自己多厉害,而是为了在每一个细节上拥有控制权。
代理切换需要特殊逻辑?改代码。并发数要动态调整?改配置。平台更新了风控策略?加一层指纹伪装。
所有东西都在自己手里,不用等第三方更新,不用看供应商脸色。
如果你也在做店群自动化,希望这篇文章能帮你少踩几个坑。
技术不复杂,复杂的是对细节的死磕。
作者:林焱
独立开发者,RPA架构师
博客:林焱RPA(全网同名)
转载需授权,喷子绕道
(全文约4700字)
