一个人写了一套店群矩阵自动化软件:我是如何把切号这件破事彻底干掉的
2026/5/27 20:25:09 网站建设 项目流程

一个人写了一套店群矩阵自动化软件:我是如何把切号这件破事彻底干掉的

写在最前面:

  1. 禁止评论“这不就是套了个浏览器的壳”。我玻璃心,看到这种评论就把并发bug写进下一版,大家一起卡。
  2. 本文不定点掉落真实翻车记录,比如“为什么我的浏览器半夜集体暴毙”和“内存泄漏排查到怀疑人生”。
> 3. 干店群这行,要么你技术够硬,要么你钱够多请人。我选前者,因为穷。

两年前,一个做拼多多店群的朋友请我喝酒。
他喝到第三瓶啤酒时开始倒苦水:80个店铺,两个运营,每天从早到晚切环境、对订单、上商品。月底一算账,利润刚够发工资。买了影刀脚本,十几个号同时跑就卡成PPT。换了指纹浏览器,环境隔离是好了,但按店铺收费,一个月好几千,肉疼。
他说:林焱,你能不能搞一套东西,让我点一下按钮,所有店铺自己跑?
我当时喝得有点多,拍着桌子说:行。
然后我就开始了漫长的“造轮子”之旅。

拼多多店群自动化上架方案


这套系统后来叫Alien
底层纯Python,界面PyQt6,执行端用影刀RPA。
跑了一年多,200多个店铺同时在线上,22个浏览器窗口并发,从不串号,从不卡死。
今天就把这套系统的核心模块拆开讲,顺便聊聊那些让我深夜emo的坑。

一、店群“体力活”到底有多苦

先还原一个真实的运营日常:
上午9点,小张到岗。打开Excel,里面有80个店铺的账号密码和代理IP。她需要:登录店铺A → 检查订单 → 记录 → 退出 → 清浏览器缓存 → 切换代理 → 登录店铺B → 重复。一个店铺平均3分钟,80个店铺就是4小时。
下午2点,开始批量上架商品。同样的流程再来一遍。中间还要穿插回复买家消息、处理售后、领优惠券。
一天下来,小张的手腕贴着膏药,眼睛布满血丝。月底拿3500底薪加一点提成。
老板也没好到哪去:招两个人,每月成本七八千,还管不住——一个疏忽就封店。
市面上的通用RPA脚本?买了,跑了,被封了。
原因很简单:那些脚本只关心“点哪里”,不关心“我是谁”。所有店铺共用同一个浏览器缓存目录,Cookies串得一塌糊涂。平台风控不是傻子,同一套指纹突然出现几十个店铺的操作,不封你封谁?
我当时的判断是:必须从底层写一套环境隔离引擎,把每个店铺包装成一台独立的“虚拟电脑”。
再用影刀RPA去操作这些“虚拟电脑”。
分工明确:Python管环境和调度,影刀管点击和输入。

二、环境隔离矩阵:Alien的“环境管理中心”

2.1 软件界面长什么样(文字还原)

Alien的“环境管理中心”界面,目标用户是老板和运营,所以必须简单直观。
左侧是一棵分组树,支持三级:平台(拼多多/TEMU/TikTok) → 项目组(国内/美区/欧洲) → 自定义标签(高权重/测款/新店)
右侧是一个卡片式表格,每张卡片就是一个店铺环境。
卡片上显示:

  • 店铺ID(用户自定义)

    • 代理IP(带地理位置国旗)
    • 环境状态(绿色在线/黄色需重新登录/红色异常)
    • 最后操作时间
    • 四个图标按钮:打开环境、编辑配置、运行任务、删除
      顶部有四个大按钮:批量导入新建分组批量打开选中导出报表

TEMU店群如何管理运营?

“批量导入”支持CSV模板,只需要填shop_id, platform, proxy三列。其他指纹参数(分辨率、时区、语言)系统根据shop_id的哈希值自动生成,保证同一店铺每次一致,不同店铺差异足够大。
“手动打开选中环境”是运营最爱。选中任意店铺,点一下按钮,弹出一个完全隔离的Chrome窗口,代理和指纹已经配好。她们可以直接进去处理异常订单或改价格,不用记任何账号密码。

2.2 技术深度:Profile隔离 + 指纹稳定生成

核心原理:Chromium的--user-data-dir参数。
每个店铺启动时,指定一个完全独立的用户数据目录。这个目录里包含该店铺所有的Cookies、LocalStorage、缓存、插件状态。只要目录不共享,两个店铺之间就没有任何数据交叉。
但仅靠目录隔离不够。平台还会收集屏幕大小、时区、语言、WebGL信息等。如果两个店铺这些特征完全一样,仍然容易被关联。

所以需要为每个店铺生成一套“指纹”,并在启动浏览器时通过命令行参数注入。
下面是我写的EnvironmentManager核心类(真实工程代码,已脱敏):

importosimportjsonimporthashlibimportrandomfrompathlibimportPathfromtypingimportOptionalclassAlienEnvironment:DATA_ROOT=Path(os.environ.get("ALIEN_DATA","./AlienEnv"))def__init__(self,shop_id:str,platform:str):self.shop_id=shop_id self.platform=platform self.env_dir=self.DATA_ROOT/platform/shop_id self.profile_dir=self.env_dir/"chromium_data"self.config_file=self.env_dir/"fingerprint.json"defcreate(self,proxy:str,group:str="default")->str:"""创建全新的店铺环境,返回profile目录路径"""self.env_dir.mkdir(parents=True,exist_ok=True)self.profile_dir.mkdir(exist_ok=True)# 生成指纹配置fingerprint=self._generate_fingerprint(proxy,group)withopen(self.config_file,"w")asf:json.dump(fingerprint,f,indent=2)# 预建Chromium需要的子目录(self.profile_dir/"Cache").mkdir(exist_ok=True)(self.profile_dir/"Local Storage").mkdir(exist_ok=True)(self.profile_dir/"Session Storage").mkdir(exist_ok=True)returnstr(self.profile_dir)def_generate_fingerprint(self,proxy:str,group:str)->dict:"""基于shop_id稳定生成指纹,保证同一店铺每次相同,不同店铺差异化"""seed_str=f"{self.platform}:{self.shop_id}:{group}:alien_salt"seed=int(hashlib.md5(seed_str.encode()).hexdigest()[:8],16)rng=random.Random(seed)# 分辨率池resolutions=[(1920,1080),(1366,768),(1440,900),(1536,864),(2560,1440)]# 时区池(根据平台倾向)timezone_map={"pdd":["Asia/Shanghai","Asia/Chongqing"],"temu":["America/New_York","America/Los_Angeles","Europe/London"],"tiktok":["America/New_York","Europe/London","Australia/Sydney"]}timezones=timezone_map.get(self.platform,["UTC"])# 语言池lang_map={"pdd":["zh-CN","zh-CN"],"temu":["en-US","en-GB","en-CA"],"tiktok":["en-US","en-GB"]}langs=lang_map.get(self.platform,["en-US"])return{"proxy":proxy,"group":group,"screen_width":rng.choice(resolutions)[0],"screen_height":rng.choice(resolutions)[1],"timezone":rng.choice(timezones),"language":rng.choice(langs),"platform_os":rng.choice(["Win32","MacIntel"]),"webgl_vendor":rng.choice(["Google Inc.","Intel Inc.","NVIDIA"]),"hardware_cores":rng.choice([2,4,8]),"do_not_track":rng.choice([True,False])}defload_config(self)->Optional[dict]:ifnotself.config_file.exists():returnNonewithopen(self.config_file,"r")asf:returnjson.load(f)defupdate_proxy(self,new_proxy:str):config=self.load_config()ifconfig:config["proxy"]=new_proxywithopen(self.config_file,"w")asf:json.dump(config,f,indent=2)defdelete(self):importshutilifself.env_dir.exists():shutil.rmtree(self.env_dir)``` 有了这个管理器,启动浏览器就简单了:读取指纹配置,组装Chrome命令行参数。 ```pythondeflaunch_browser(env:AlienEnvironment):config=env.load_config()ifnotconfig:raiseException("环境未创建")cmd=["chrome.exe",f"--user-data-dir={env.profile_dir}",f"--proxy-server={config['proxy']}",f"--window-size={config['screen_width']},{config['screen_height']}",f"--lang={config['language']}","--remote-debugging-port=0","--disable-blink-features=AutomationControlled","--no-first-run"]# 启动进程并获取调试端口...``` 注意 `--remote-debugging-port=0` 让Chromium自动分配空闲端口,我们从中抓取,供影刀RPA连接。---## 三、自动化调度编排:从“手工切号”到“一键全自动”环境隔离做好之后,第二步是让任务批量、并发地跑起来。 Alien的“自动化编排流”模块,是一个可视化任务调度引擎。### 3.1 拖拽组合业务流程界面左侧是动作库,包含:-基础类:打开/关闭环境、等待、条件判断、循环--登录类:自动登录、刷新Cookies--拼多多类:上架商品、领券、发货、获取订单--TEMU类:批量上架、调价、邀评--TikTok类:浏览、点赞、关注、发布视频 运营可以把动作拖到右侧画布上,用连线串联。 例如一个“拼多多日常维护”流程:
[打开环境] → [自动登录] → [获取未读消息] → [回复模板消息] → [领取优惠券] → [关闭环境] ```

流程保存后,可以分配给单个店铺、一个分组或全部店铺。

3.2 多对多匹配与智能调度

调度器是Alien的心脏。
它需要解决几个矛盾:

  • 一台机器同时只能跑有限个浏览器窗口(我们实测22个是极限)
    • 同一个店铺同一时间只能执行一个任务(否则会冲突)
    • 不同店铺可以任意并发
    • 窗口要自动平铺,不重叠
      下面是一段调度器的核心代码(生产环境简化版):
importthreadingimportqueueimporttimefromconcurrent.futuresimportThreadPoolExecutorclassAlienScheduler:def__init__(self,max_concurrent=22):self.max_concurrent=max_concurrent self.task_queue=queue.Queue()self.active_slots=0self.slot_lock=threading.Lock()self.shop_running={}# shop_id -> boolself.shop_lock=threading.Lock()self.executor=ThreadPoolExecutor(max_workers=max_concurrent)defsubmit(self,shop_id,flow_file,params=None):self.task_queue.put((shop_id,flow_file,params))def_worker(self):whileTrue:shop_id,flow_file,params=self.task_queue.get()# 等待该店铺没有正在运行的任务whileTrue:withself.shop_lock:ifnotself.shop_running.get(shop_id,False):self.shop_running[shop_id]=Truebreaktime.sleep(0.5)# 等待有空闲并发槽位whileTrue:withself.slot_lock:ifself.active_slots<self.max_concurrent:self.active_slots+=1breaktime.sleep(0.5)self.executor.submit(self._run_task,shop_id,flow_file,params)def_run_task(self,shop_id,flow_file,params):try:# 确保浏览器已启动(或复用已有实例)debug_port=self._ensure_browser(shop_id)# 调用影刀RPAself._call_yingdao(flow_file,shop_id,debug_port,params)finally:withself.slot_lock:self.active_slots-=1withself.shop_lock:self.shop_running[shop_id]=Falseself.task_queue.task_done()def_ensure_browser(self,shop_id):# 检查该店铺是否有运行中的浏览器实例# 如果没有,调用环境管理器启动新实例并返回调试端口passdef_call_yingdao(self,flow_file,shop_id,debug_port,params):importsubprocess,json cmd=["影刀RPA.exe","-run",flow_file,"-param",f"shop_id={shop_id}","-param",f"debug_port={debug_port}","-param",f"params={json.dumps(params)}"]subprocess.run(cmd,capture_output=True,timeout=600,check=True)defstart(self):for_inrange(self.max_concurrent):threading.Thread(target=self._worker,daemon=True).start()``` 这个调度器上线后,我们在一台32核64G的机器上跑过200个店铺的批量上架任务,22个窗口同时运行,CPU占用70%,内存占用45%,连续跑了三天没有崩溃。>踩坑:最开始没有加“店铺级串行”互斥锁,结果同一个店铺的两个任务(比如上架和领券)同时操作浏览器,导致页面状态错乱,商品被重复上架。加上`shop_running`字典后问题解决。### 3.3 智能平铺的实现为了让22个窗口不堆叠,我在启动每个浏览器后调用Windows API移动窗口位置。 ```pythonimportwin32guiimportwin32condeftile_browser_windows(hwnd_list,cols=5):screen_width=1920screen_height=1080tile_width=screen_width//cols rows=(len(hwnd_list)+cols-1)//cols tile_height=screen_height//rowsifrows>0elsescreen_heightforidx,hwndinenumerate(hwnd_list):row=idx//cols col=idx%cols x=col*tile_width y=row*tile_height win32gui.SetWindowPos(hwnd,None,x,y,tile_width,tile_height,win32con.SWP_NOZORDER)``` 运营第一次看到整整齐齐的22个窗口时,说:**“终于不用在任务栏里大海捞针了。”**---## 四、底层工程封装:让客户双击exe就能用如果我把上面的Python代码发给客户,他们只会说“这什么黑乎乎的东西”。 所以我做了三件事,让Alien看起来像正经商业软件。### 4.1 PyQt6极简交互面板我花了几周学PyQt6,然后写了完整的GUI。-**仪表盘**:用pyqtgraph绘制实时曲线,显示并发窗口数、任务吞吐量、内存占用。--**环境管理**:QTreeView做分组树,QTableView做卡片式表格,支持拖拽分组、右键菜单。--**流程编排**:基于QGraphicsView实现拖拽式画布,节点用QGraphicsRectItem自定义。--**日志监控**:QTextEdit配合颜色高亮,实时滚动,支持按店铺ID过滤。 整体采用暗黑主题,按钮有悬停效果,图标用FontAwesome。客户第一次打开时说:“这软件得卖好几千吧?”### 4.2 双击即用的exe打包客户不需要安装Python、Chrome、影刀客户端。 我用PyInstaller将整个项目打包成一个`Alien.exe`,内嵌了:-Python解释器和所有依赖库--一个便携版Chromium(约110MB,启动时解压到临时目录)--影刀RPA的免安装运行时(需要用户自行购买正版授权,但运行时文件我打包了) 客户下载压缩包,解压,双击exe,等待几秒初始化,就能看到主界面。### 4.3 独立的安全验证为了防止破解,我实现了一机一码授权。 程序启动时,收集硬盘序列号、网卡MAC地址、主板ID,通过SHA256生成机器码。 用户将机器码发给我,我用RSA私钥签名生成license文件。 程序每次启动验证license签名和机器码是否匹配,失败则拒绝运行。 虽然不能100%防破解,但已经挡住了99%的“复制粘贴即用”行为。---## 五、那些让我深夜emo的踩坑记录**1.内存泄漏排查了一周。**当时线上环境跑了几十个号,内存从启动时的2GB慢慢涨到12GB,然后系统开始卡顿。查了很久,发现是每个浏览器实例退出后,`user-data-dir`下的`Cache`和`Code Cache`没有被清理,日积月累导致磁盘空间被占满,同时内存中还有一些Python对象没有释放。解决方案:在调度器的资源回收函数中,对空闲超过1小时的店铺,删除其缓存目录并调用`gc.collect()`。**2.影刀RPA的超时陷阱。**影刀默认等待元素出现的超时是60秒,但跨境网络延迟高,有时候页面加载要90秒。导致很多任务莫名失败。后来我在每个影刀流程的开头将全局超时设为180秒,并在关键步骤加了截图保存,失败时能快速定位。**3.代理IP的质量决定生死。**图便宜买过5/天的静态代理,用了三天被拼多多全部标记。后来换成住宅代理池,成本翻了三倍,但封店率从12%降到了0.8%。这个钱不能省。**4.运营的一次误操作。**有一次运营在“环境管理”里选中了“全部店铺”,然后点了“删除”。瞬间所有环境配置都没了。我赶紧从备份恢复,但损失了当天的部分任务记录。之后我加了二次确认弹窗和回收站机制,删除的环境先移到“回收站”,7天后才真正删除。---## 写在最后从一个人写Alien到现在,已经过去一年半。 它从最初的几十行脚本,长成了上万行代码的完整系统。 有人问我:你为什么不直接用现成的指纹浏览器加影刀? 我的回答是:**自己造轮子不是为了炫耀,而是为了在每一个细节上拥有控制权。**代理切换需要自定义逻辑?改代码。并发窗口数要动态调整?改配置。某个平台更新了风控策略?加一层指纹伪装。 所有的一切都在自己手里,不用等第三方更新,不用看供应商脸色。 如果你也在做店群自动化,希望这篇文章能给你一些启发。 技术不复杂,复杂的是对细节的死磕。>作者:林焱>>独立开发者,RPA架构师>>博客:林焱RPA(全网同名)>>转载需授权,喷子绕道 (全文约4800字)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询