一个人写了一套店群矩阵自动化软件:我是如何干掉繁琐切号流程的

两百个店铺,三个运营,每天八小时全在切号、登录、对账。
我花三个月写了一套软件,现在一个人点几下鼠标,全自动跑完。
老板问:你一个人顶三个人?我说:不,我顶的是“繁琐”本身。

00 一个深夜的求救:这破号,我切不动了

去年秋天,一个做TikTok店群的老客户半夜发来一张截图——他的Excel表格里密密麻麻列了247个店铺,每个店铺后面跟着不同的代理IP、密码、Cookies有效期。

他说:“林工,我今天从早上九点切到晚上十点,还没切完一半。明天还要重新登录那些过期的。我真的干不动了。”

这不是个例。

店群行业最真实的体力活,从来不是选品、不是投流,而是环境管理

  • 每天手动打开几十个浏览器,挨个登录

  • 切错Cookie导致串号,平台封你一整批

  • 招来的运营干两个月就辞职,因为太枯燥

  • 买来的通用RPA脚本,跑三天就被风控识别

我当时的判断很简单:这不是人的问题,是工具的问题。

市面上的RPA工具,包括影刀、UiPath,都默认你在“一台电脑、一个环境”下运行。
但店群需要的是“一台电脑、一百个平行宇宙”。

通用工具做不到。

于是我决定自己写一个。

从底层开始,不依赖任何低代码平台,纯Python做骨架,PyQt6做脸面,影刀只用来录制那些经常变动的页面操作(方便我远程热更新)。

三个月后,Alien店群自动化管理系统上线。
今天这篇文章,不是产品发布会,而是我把这三个月的坑,一条条刨出来给你看


picture.image

01 环境隔离矩阵:每个店铺都是一个独立的“数字克隆体”

picture.image 第一个要解决的,也是最核心的:怎么让两百个店铺在一台电脑上共存,互不干扰,互不串号?

你可能会说:用无痕模式,或者用不同的Chrome用户。

picture.image 都不够。

真正的隔离,要做到以下几点:

  • 每个店铺有自己的本地存储(Cookies、LocalStorage、IndexedDB)
  • 每个店铺有自己的浏览器指纹(Canvas、WebGL、时区、字体)
  • 每个店铺有自己的代理IP
  • 每个店铺有自己的登录状态,重启电脑也不丢失

picture.image 这就是Alien的 “环境管理中心”

picture.image

软件界面长什么样?

picture.image 我不画大饼,直接说真实界面:

打开Alien,左边是一棵分组树

TikTok美区
  ├── 女装大类
  │     ├── 店铺_001
  │     ├── 店铺_002
  ├── 3C数码
       ├── 店铺_101

点击任何一个分组,右边表格显示该分组下所有店铺的详细状态:

店铺ID代理IP最后登录时间Cookie有效期状态
shop_00145.76.xx.xx:80802026-05-25 13:22还剩6天🟢 正常
shop_00245.76.xx.xx:80812026-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,然后再链接成可执行文件。
这样做的三个好处:

  1. 启动速度快:比PyInstaller打包的版本快30%以上
  2. 反编译困难:逆向工具看到的是一堆C反汇编代码,而不是清晰的Python字节码
  3. 依赖完全内嵌:所有第三方库、chromedriver都打包进去,客户电脑可以没有任何Python环境

同时,Alien内置了离线授权验证

  • 首次启动,读取客户机器的CPU序列号+硬盘序列号,生成机器码
  • 发送给我,我用私钥生成license文件
  • 客户把license放进软件目录,以后每次启动自动校验
  • 支持离线运行,不需要联网(很多店群工作室的内网是隔离的)

交付给客户的,就是一个文件夹,里面有 Alien.exe 和一些配置文件。
双击运行,直接开用。

有个同行后来问我:“你这软件是用什么语言写的?怎么体积这么小,功能还这么全?”
我说:“Python。但你看不出来,因为我不想让你看出来。”


04 写在最后:自动化的本质,不是替代人,是替代繁琐

写Alien这套系统,我自己也走过不少弯路。

最开始我试图用纯RPA脚本解决一切,结果发现脚本越写越复杂,维护成本爆炸。
后来我明白了:RPA适合做“手”,不适合做“脑”。
脑的部分(调度、环境管理、并发控制)用通用编程语言实现,手的部分(点击、输入、抓取)用RPA录制,才是最优雅的组合。

我还记得第一个付费客户用了一个月后,给我发来的消息:
“林工,我这个月运营成本少了8000块,因为不用再请那个专门切号的人了。你那软件的钱,一个月就回本了。”

这句话,比任何技术赞美都让我有成就感。

如果你也是店群从业者,希望这篇文章能让你明白:
你现在的痛苦,不是因为你不够努力,而是因为工具太原始。

如果你也是开发者,希望这篇文章能给你一个思路:
桌面软件的市场远没有死,那些看起来“传统”的行业,恰恰是技术降维打击的蓝海。


我是林焱,一个写代码解决真实问题的独立开发者。
关于Alien的环境隔离、并发调度或Nuitka打包,有任何疑问,欢迎评论区交流。

0
0
0
0
评论
未登录
暂无评论