两百个店铺,三个运营,每天八小时全在切号、登录、对账。
我花三个月写了一套软件,现在一个人点几下鼠标,全自动跑完。
老板问:你一个人顶三个人?我说:不,我顶的是“繁琐”本身。
00 一个深夜的求救:这破号,我切不动了
去年秋天,一个做TikTok店群的老客户半夜发来一张截图——他的Excel表格里密密麻麻列了247个店铺,每个店铺后面跟着不同的代理IP、密码、Cookies有效期。
他说:“林工,我今天从早上九点切到晚上十点,还没切完一半。明天还要重新登录那些过期的。我真的干不动了。”
这不是个例。
店群行业最真实的体力活,从来不是选品、不是投流,而是环境管理:
-
每天手动打开几十个浏览器,挨个登录
-
切错Cookie导致串号,平台封你一整批
-
招来的运营干两个月就辞职,因为太枯燥
-
买来的通用RPA脚本,跑三天就被风控识别
我当时的判断很简单:这不是人的问题,是工具的问题。
市面上的RPA工具,包括影刀、UiPath,都默认你在“一台电脑、一个环境”下运行。
但店群需要的是“一台电脑、一百个平行宇宙”。
通用工具做不到。
于是我决定自己写一个。
从底层开始,不依赖任何低代码平台,纯Python做骨架,PyQt6做脸面,影刀只用来录制那些经常变动的页面操作(方便我远程热更新)。
三个月后,Alien店群自动化管理系统上线。
今天这篇文章,不是产品发布会,而是我把这三个月的坑,一条条刨出来给你看。
01 环境隔离矩阵:每个店铺都是一个独立的“数字克隆体”
第一个要解决的,也是最核心的:怎么让两百个店铺在一台电脑上共存,互不干扰,互不串号?
你可能会说:用无痕模式,或者用不同的Chrome用户。
都不够。
真正的隔离,要做到以下几点:
- 每个店铺有自己的本地存储(Cookies、LocalStorage、IndexedDB)
- 每个店铺有自己的浏览器指纹(Canvas、WebGL、时区、字体)
- 每个店铺有自己的代理IP
- 每个店铺有自己的登录状态,重启电脑也不丢失
这就是Alien的 “环境管理中心” 。
软件界面长什么样?
我不画大饼,直接说真实界面:
打开Alien,左边是一棵分组树:
TikTok美区
├── 女装大类
│ ├── 店铺_001
│ ├── 店铺_002
├── 3C数码
├── 店铺_101
点击任何一个分组,右边表格显示该分组下所有店铺的详细状态:
| 店铺ID | 代理IP | 最后登录时间 | Cookie有效期 | 状态 |
|---|---|---|---|---|
| shop_001 | 45.76.xx.xx:8080 | 2026-05-25 13:22 | 还剩6天 | 🟢 正常 |
| shop_002 | 45.76.xx.xx:8081 | 2026-05-24 09:15 | 已过期 | 🔴 需重新登录 |
最实用的是顶部的批量操作栏:
- 批量导入:拖一个Excel进来,自动生成200个店铺的环境配置
- 一键打开选中环境:勾选10个店铺,点一下,10个独立浏览器窗口同时弹出,每个都已经登录好了
- 导出环境报告:导出所有店铺的代理、指纹、最后活动时间
一个真实客户的原话:
“以前我每天早上要花两个小时登录所有店铺,现在点一下‘批量登录’按钮,去吃个早餐回来,全好了。”
底层技术:每个店铺一个独立Profile
核心代码不复杂,但每行都是踩坑换来的:
import os
import json
import hashlib
import random
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
class AlienProfile:
"""单个店铺的独立环境配置"""
PROFILES_ROOT = "./alien_profiles" # 所有环境存放在这个目录下
def __init__(self, shop_id: str, proxy: str):
self.shop_id = shop_id
self.proxy = proxy
self.profile_dir = os.path.join(self.PROFILES_ROOT, f"shop_{shop_id}")
# 第一次初始化时创建目录
os.makedirs(self.profile_dir, exist_ok=True)
def _get_fingerprint(self):
"""获取该店铺的固定指纹配置,而不是每次随机"""
fp_path = os.path.join(self.profile_dir, "fingerprint.json")
if os.path.exists(fp_path):
with open(fp_path, 'r') as f:
return json.load(f)
# 首次创建:生成一套稳定的伪装指纹并保存
fingerprint = self._generate_stable_fingerprint()
with open(fp_path, 'w') as f:
json.dump(fingerprint, f)
return fingerprint
def launch(self):
"""启动完全隔离的浏览器实例"""
opts = Options()
# 核心:独立用户数据目录,Cookies、插件、历史全隔离
opts.add_argument(f"--user-data-dir={self.profile_dir}")
# 独立代理
opts.add_argument(f"--proxy-server={self.proxy}")
# 隐藏自动化特征
opts.add_argument("--disable-blink-features=AutomationControlled")
# 固定窗口大小(避免被视口指纹识别)
opts.add_argument("--window-size=1280,720")
driver = webdriver.Chrome(options=opts)
# 注入指纹伪装
fp = self._get_fingerprint()
stealth_js = self._build_stealth_script(fp)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": stealth_js
})
return driver
def _generate_stable_fingerprint(self):
"""生成一套固定的指纹,每次登录都一样,更真实"""
# 用shop_id的哈希决定指纹特征,保证同一店铺每次相同
h = hashlib.md5(self.shop_id.encode()).hexdigest()[:8]
return {
"canvas_hash": f"canvas_{h}",
"webgl_vendor": random.choice(["Intel Inc.", "NVIDIA Corporation"]),
"platform": "Win32",
"timezone_offset": -480 # 固定为美西时间
}
注意一个细节:_generate_stable_fingerprint 里,我用 hashlib.md5(shop_id) 来生成指纹特征,而不是完全随机。
为什么?
因为真实的用户指纹是稳定的。如果你每次打开同一个店铺,指纹都不一样,反而会被风控标记为异常。
这一点,我是被TikTok封了二十多个号之后才悟出来的。
业务感:分组合规管理
光有技术还不够。店群老板要的是管理感。
所以Alien的环境管理中心支持:
- 分组标签:可以给店铺打上“高风险”“新注册”“需换IP”等标签
- 合规检查:一键检测所有店铺的代理是否可连接、Cookie是否即将过期
- 手动打开选中环境:支持按住Ctrl多选,然后一次性打开所有选中的店铺窗口。每个窗口独立IP、独立指纹、独立缓存,彼此完全看不见对方。
有一次客户说:“我同时开了15个店铺窗口,每个窗口登录的是不同的账号,在同一个平台上操作,居然一个都没被封。”
我说:“这才对。因为平台眼里,这是15个不同的人,在不同地方,用不同电脑上网。”
02 自动化编排流:让任务在正确的店铺上跑
环境隔离好了,下一步是自动化执行任务。
店群的日常操作其实很固定:
- 早上:所有店铺登录一次,刷新Cookie
- 上午:批量上架50个新品
- 下午:检查订单,标记已发货
- 晚上:对账,统计当天的销售额
以前这些事,运营要一个一个店铺手动做。
现在,Alien把这些任务变成了可编排的流程。
可视化拖拽:像玩游戏一样配置自动化
Alien的 “自动化编排流” 界面,是一个流程图编辑器。
左侧是动作库:
- 登录动作(输入账号密码、处理二次验证)
- 上架动作(填写标题、价格、库存、上传图片)
- 订单动作(拉取订单、标记发货)
- 数据动作(导出报表、截图)
中间是画布:你可以把动作拖进来,用线连接起来,形成一个流程。
右侧是环境选择:你可以勾选“全部店铺”,也可以只勾选“女装组”或者“昨天出过单的店铺”。
然后点“运行”。
Alien会自动为每个选中的店铺,执行一遍你画好的流程。
任务与环境的“多对多匹配”
背后的技术难点在于:如何高效、稳定地为100个店铺并发执行任务?
这涉及到任务调度、资源控制、异常恢复。
下面这段代码是Alien调度器的核心骨架(当时为了调试并发死锁,我熬了两个通宵):
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
class AlienScheduler:
"""任务调度器:将一组店铺和环境组合,并发执行一个流程"""
def __init__(self, max_workers=22):
self.max_workers = max_workers
self.semaphore = threading.Semaphore(max_workers)
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.running_tasks = {}
def run_flow_on_envs(self, flow_func, env_list):
"""对一组环境执行同一个流程"""
futures = []
for env in env_list:
# 每个环境提交一个独立任务
future = self.executor.submit(self._safe_run, env, flow_func)
futures.append(future)
self.running_tasks[future] = env.shop_id
# 等待所有完成,并收集结果
results = {}
for future in as_completed(futures):
shop_id = self.running_tasks.pop(future)
try:
result = future.result()
results[shop_id] = ("success", result)
except Exception as e:
results[shop_id] = ("failed", str(e))
return results
def _safe_run(self, env, flow_func):
"""带资源回收的任务执行包装"""
driver = None
try:
# 获取该店铺的浏览器实例(懒加载,复用已有连接)
driver = env.get_or_create_driver()
# 执行用户定义的流程
return flow_func(driver, env.shop_id)
finally:
# 关键:一定要释放资源,否则内存泄露
# 注意:这里不关闭driver,而是放回池中复用。真正关闭在环境管理器里。
pass
上面代码的 max_workers=22 不是随便写的。
我做过压力测试:32G内存的Windows机器,同时跑22个Chrome窗口,CPU占用75%,内存占用约18G,系统依然流畅。
如果开到25个,内存会缓慢增长到22G以上,然后触发页面文件交换,整个系统开始卡顿。
开到28个,半小时内必然有窗口崩溃。
这个数字,是我反复测试了三天才定下来的。
真实踩坑:资源没释放,内存两分钟爆一次
第一次上线时,我犯了一个经典错误:没有正确管理driver的生命周期。
当时我把每个店铺的driver存在一个全局字典里,任务跑完也不关闭。
结果跑了50个店铺后,内存占用从2G飙到15G,两分钟后就崩了。
查日志发现,每个driver背后都是一个完整的Chrome进程,每个进程吃掉150~200MB内存。
50个进程就是10G。
解决方案是懒加载 + 闲置回收:
- 只有当任务需要时才创建driver
- 任务结束后不立即关闭,而是放进缓存池,保留5分钟
- 5分钟内再次用到同一个店铺,直接复用,省去登录时间
- 超过5分钟没有活动,自动关闭释放内存
这个优化做完后,同样的50个店铺并发,内存稳定在6G左右,再也没有崩过。
03 底层封装:为什么我的客户觉得“这软件是正规军”?
技术再好,如果交付给客户的是一个 .py 文件,让他自己去装Python、装依赖、配环境变量——
他只会回你一句:“太麻烦了,我不要了。”
抛弃黑框框:我用PyQt6做了个像模像样的桌面软件
很多Python开发者不屑于做界面,觉得命令行就够了。
但对于店群老板来说,一个带进度条、带日志窗口、带配置面板的GUI,才是“专业”的象征。
Alien的主界面是用PyQt6写的,包含:
- 顶部菜单栏:文件、环境、任务、帮助
- 左侧导航:环境管理、任务编排、运行日志、系统设置
- 中央区域:根据左侧选择显示不同面板
- 底部状态栏:实时显示当前并发数、内存占用、队列长度
所有的耗时操作(比如批量登录100个店铺)都放在 QThread 后台线程中,界面永远不会卡死。
进度条会实时更新:已完成 23/100。
有一个客户说:“你这软件,看着就跟那些卖几千块的商业软件一样。”
我说:“因为本质它就是。”
Nuitka黑盒打包:双击就能用,谁也偷不走代码
我不想让客户去配置Python环境,也不想把自己的源代码直接暴露给同行。
所以我选择了 Nuitka 进行编译打包。
相比于PyInstaller,Nuitka会先把Python代码编译成C,然后再链接成可执行文件。
这样做的三个好处:
- 启动速度快:比PyInstaller打包的版本快30%以上
- 反编译困难:逆向工具看到的是一堆C反汇编代码,而不是清晰的Python字节码
- 依赖完全内嵌:所有第三方库、chromedriver都打包进去,客户电脑可以没有任何Python环境
同时,Alien内置了离线授权验证:
- 首次启动,读取客户机器的CPU序列号+硬盘序列号,生成机器码
- 发送给我,我用私钥生成license文件
- 客户把license放进软件目录,以后每次启动自动校验
- 支持离线运行,不需要联网(很多店群工作室的内网是隔离的)
交付给客户的,就是一个文件夹,里面有 Alien.exe 和一些配置文件。
双击运行,直接开用。
有个同行后来问我:“你这软件是用什么语言写的?怎么体积这么小,功能还这么全?”
我说:“Python。但你看不出来,因为我不想让你看出来。”
04 写在最后:自动化的本质,不是替代人,是替代繁琐
写Alien这套系统,我自己也走过不少弯路。
最开始我试图用纯RPA脚本解决一切,结果发现脚本越写越复杂,维护成本爆炸。
后来我明白了:RPA适合做“手”,不适合做“脑”。
脑的部分(调度、环境管理、并发控制)用通用编程语言实现,手的部分(点击、输入、抓取)用RPA录制,才是最优雅的组合。
我还记得第一个付费客户用了一个月后,给我发来的消息:
“林工,我这个月运营成本少了8000块,因为不用再请那个专门切号的人了。你那软件的钱,一个月就回本了。”
这句话,比任何技术赞美都让我有成就感。
如果你也是店群从业者,希望这篇文章能让你明白:
你现在的痛苦,不是因为你不够努力,而是因为工具太原始。
如果你也是开发者,希望这篇文章能给你一个思路:
桌面软件的市场远没有死,那些看起来“传统”的行业,恰恰是技术降维打击的蓝海。
我是林焱,一个写代码解决真实问题的独立开发者。
关于Alien的环境隔离、并发调度或Nuitka打包,有任何疑问,欢迎评论区交流。
