网鱼AI电商助手  ·  面向闲鱼卖家的 AI 铺货与发布自动化 工作日 09:00–18:00  ·  试用与部署 免费

版本更新记录,每一版都写清代价

更新日志通常只写「新增了什么」,但真正影响使用的是「什么变了、我原来的做法还成立吗」。这页按版本倒序记录每一次改动:这一版做了什么、为什么非改不可、影响到哪一类使用者、已有配置怎么迁移、迁移完怎么验证改对了。版本号从 3.19 到 3.15,其中三条来自线上事故的修正,因此迁移段写得比功能段更长。如果你的部署正在运行,先读对应版本的「怎么迁移」,再读其余部分。

版本倒序迁移方式验证方法事故复盘
桌面显示器与键盘
Changelog · 每一次改动都要能在日志里验证,而不只是写在说明里

01  /  阅读口径

先看影响面,再看功能名

版本号只是索引,真正要判断的是「这一版会不会动到我正在跑的东西」。所以每一条都按同一个结构写:做了什么、为什么做、影响到谁、怎么迁移、怎么验证。五段里后两段最值得先读,因为它们直接决定你要不要停机、要不要重跑基线。

版本之间的关系不是功能堆叠,而是问题顺序。3.15 解决的是「设备能不能连上、流程能不能跑完」;3.16 解决「跑完了但输入不进去、屏幕状态读不到」;3.17 解决「能跑但操作形态太整齐」;3.18 解决「跑了但说不清发了什么」;3.19 解决「说清了但到点发布时内容已经不成立了」。按这个顺序读,你会看出每一版都在补上一版暴露出来的洞,而不是在加新花样。

三类使用者受影响的程度不同。只跑单账号、每天几条的,主要看 3.19 的复检闸门与 3.17 的参数;多设备并行的,3.16 的端口锁与 3.15 的存活判定是必读;多账号代运营的,3.18 的台账口径会改变你对外交付的记录形态,迁移成本最高。

如果只想确认一件事——升级安不安全——直接看每条的「怎么验证」。验证方式全部是可以在本机跑出来的:跑一轮自检看哈希有没有变、构造一条必然被拦的商品看闸门拦不拦、两条通道跑同一份计划看记录数对不对。没有一条验证需要问我们,这是这套系统刻意保持的性质:结论要能从日志里自己读出来。

版本文件的更新方式与部署步骤写在下载与部署里;如果你还不确定自己需要哪几个模块,先看产品功能总览,那里有模块之间的依赖顺序。

关于兼容性,有一条贯穿五个版本的约定:配置文件与流程文件的格式只增不改,新版本必须能读旧文件,旧版本读不了新文件是允许的。这条约定的代价是新字段会一直留着,好处是回滚不需要改配置。所以升级的正确姿势是换程序不换文件,确认稳定之后再按需改文件;反过来先改文件再升级,回滚时就没有可退的版本了。

还有一处容易被误读:版本号不等于功能数量。3.16 与 3.17 都没有加新模块,改的是已有模块的内部口径,但这两版对稳定性的影响比任何一次功能新增都大。判断一版值不值得升,看的是它改的是你正在痛的地方,而不是看它加了几项。

02  /  3.19

发布时刻复检:到点再判一次

这一版改的是闸门的时机。此前合规检查发生在发布计划生成的时候:计划上午生成,闸门上午跑一遍,通过就进队列,到点直接提交。问题出在中间这段时间——上午成立的条件,晚上不一定成立。

具体的失效形态有三种。第一种是同款撞车:上午这个类目下没有别人用同一个标题结构,下午有人先发了,你的标题与主图相似度在当下基线里已经超标。第二种是类目归属变化:平台侧的类目入口调整,上午能归进去的品,晚上归不进去了。第三种是节奏冲突:同一批计划里前后两条挤在同一分钟,单条服务线的每日上限在合并后其实已经超了,但每条单独看都合规。

这一版把判定挪到发布时刻:到点提交之前,用当下的基线把合规闸门完整重跑一遍,不通过就不发。这是 fail-closed——默认拦住,要放行必须给出理由,而不是默认发出、出事再说。改动看起来只是一个时机,实际上要求闸门的每一项都能被单独调用、输入必须来自同一份发布计划、结论要落进日志并带原因码,而不是返回一个布尔值。

影响到谁:所有使用定时发布的账号,尤其是多账号与多服务线的。单账号、手动即时发布的受影响很小,因为生成与提交的间隔本来就短。

怎么迁移:先在测试环境把闸门的检查项逐项单独跑通,确认每一项都能在不依赖计划生成上下文的情况下给出结论;再把定时任务的提交入口改成「先复检、后提交」,复检失败时不要静默跳过,要把计划停在待处理状态并写原因;最后确认原因码能被台账读到,否则复检拦下来你也不知道为什么。

怎么验证:构造一条必然被拦的商品放进计划——比如主图与已有链接几乎一样、或者标题与同款高度重合——到点观察它是否停发,并且日志里有明确原因。反向测试 PASS 指的就是这一类用例:本该失败的确实失败了。如果一条本该被拦的商品顺利发出,说明复检没生效,或者复检读的不是当下基线。闸门各项的判定口径写在商品合规检查,节奏参数写在定时发布与节奏控制。

这一版带来的副作用要说清楚:复检会拦掉一些你上午以为已经过了的内容,发布成功率这个数字会往下掉。它掉的是「本就不该发的」,不是「能发的」。如果你的监控只看成功率,这一版之后要把「被复检拦下」单独算一类,否则会误判成系统退化。

复检还要保证幂等:同一份计划、同一份基线,跑两次得到的结论必须一致。这条要求不高但容易破——只要检查项里有一项读了当前时间、随机数或者上一次的执行结果,结论就会漂。漂的后果是同一批计划里有的被拦、有的没被拦,而你无法解释差别在哪。验证方法很简单:拿一份会被拦的计划连跑三次,三次的原因码要相同,次序也要相同。

另外要接受一点:复检只能在提交这一端做,它管不住提交之后的事。商品发出去了、半小时后被平台清理,这不属于闸门的范围。所以台账里要把「已提交」与「已确认在线」分成两个状态:前者是系统能保证的,后者要靠回查。这一步经常被省略,直到某天发现台账里全是已发布,平台上一件都没有。

03  /  3.19 事故

自检写进生产记录:根因不在自检,在路径

3.19 之前发生过一次事故,值得单独写一条,因为它改变的是纪律而不是功能。事故现象是:生产台账里出现了一批「已发布」的记录,但平台侧并没有对应的商品。追下去发现,这些记录是自检跑出来的——自检为了验证发布链路,会走完整流程并写记录,本该写进隔离目录,实际写进了生产路径。

根因不是自检逻辑写错,而是路径解析的时机。程序启动时先解析了数据目录,再读取环境变量;而隔离目录正是通过环境变量指定的。解析早于生效,环境变量等于没设,自检和老老实实写生产的行为完全一样。这类问题的可怕之处在于它不报错:路径存在、写入成功、日志正常,只是写错了地方。

修复分成四道防护,每一道单独都能拦住同一类问题。第一道是顺序:先读环境变量,再解析任何路径,解析结果要打印在启动日志里,肉眼能看见这次跑的是哪个目录。第二道是备份:写生产数据之前先留 .bak,任何一次写入都可以退回上一份。第三道是标记:自检进程带标记运行时,一旦要写生产数据直接抛错,不写入、不降级、不静默跳过。第四道是比对:整轮自检开始前与结束后,对生产文件做哈希比对,两次数值不一致就判定这轮自检污染了生产数据,整轮作废。

影响到谁:所有在同一个环境里既跑自检又跑生产的人。这是最常见也最危险的部署形态——一台机器,两个用途,靠环境变量区分。

怎么迁移:如果你现在的自检与生产共用目录,先把自检挪到独立目录再升级;如果暂时不能挪,至少确认启动日志里打印的数据目录是隔离目录,并且第三道防护的标记确实生效(手动跑一次自检,观察它是否在写生产时抛错)。

怎么验证:记录自检前的生产文件哈希,跑一轮完整自检,再取一次哈希,两次必须一致。这条验证不需要看日志、不需要读代码,只看两个数。事故之后我们把这条做成了每轮自检的固定动作,哈希不一致时整轮结果不可信。台账本身的记录口径见发布数据台账。

这一条之所以写在更新日志里,而不是写在内部文档里:只要你的自检与生产共用一个数据目录,你就有同样的问题,只是还没遇到。

事故之后还补了一条纪律:任何一次「全绿」的自检结果,都必须附上哈希比对的两个值,否则不采信。理由是自检全过但污染了生产,比自检报错更危险——报错你会停下来看,全绿你会直接往下走。现在自检的输出里固定带三行:这一轮跑的数据目录、跑之前的哈希、跑之后的哈希。三行对不上就不用往下看了。

04  /  3.18

台账收成单一口径,归属改成算出来的

3.18 之前,两条通道各自记自己的账。手机通道记一份,网页通道记一份,字段名不同、时间戳口径不同、item_id 有时为空。结果是同一条商品在两边各有一条记录,或者两边都没记——因为都以为对方记了。这个问题在单账号时只是看着乱,在多账号代运营时是致命的:客户问「昨天那三条发出去没有」,你答不上来。

这一版把台账收到一个模块里,两条通道写同一本,字段固定为 item_id、标题、描述、价格、时间戳。写入入口只有一个,任何通道都必须经过它,不允许旁路写文件。时间戳统一取提交完成的时刻,不取计划里的预定时刻——这两者经常差几分钟,而差的几分钟恰恰是判断「到底发没发」的关键。

更关键的一处改动是槽位归属。同一份发布计划里,某一条归哪条通道执行,以前是靠人工标记的:生成计划时打个字段说这条走手机、那条走网页。人工标记的问题是它会漂——计划改了、通道挂了、重跑一次,标记还停在原处。3.18 改成算出来的:归属由同一个函数对象根据计划内容与通道可用性计算,不读任何标记字段。同一份输入永远得到同一个归属,重跑不会变。

影响到谁:所有同时使用两条通道的部署;只跑一条通道的,收益是记录变整齐,迁移成本很低。

怎么迁移:历史记录按新口径重导一次,字段对齐,缺 item_id 的标为待补而不是丢掉——丢掉等于承认这段历史不可查。重导之后不要立刻删旧文件,保留一个版本周期。归属字段如果以前是配置项,升级后要删掉,留着会让人以为还能手改。

怎么验证:用同一份发布计划,让两条通道各跑一遍,比对台账记录数与计划条数是否一致、有没有同一条出现两次、有没有 item_id 为空。再跑第二遍同样的计划,确认归属结果与第一遍完全相同——如果变了,说明还有地方在读标记而不是在算。台账字段的完整说明见发布数据台账,网页通道的提交细节见电脑网页版通道。

收成一本账之后,导出的形态也要跟着定。对外交付时导出的不是数据库文件,而是一张按时间排序的表:时间戳、item_id、标题、价格、状态。标题按当时的原文导出,不取当前值——商品后来改标题是常事,导出当前值会让记录与当时的发布对不上。这条看着细,但需要回溯「当时到底怎么写的」时,只有原文能回答。

05  /  3.17

拟人化参数收敛,从「慢一点」变成具体数

3.17 之前,流程里的等待是靠固定睡眠写的:每一步之间停一秒,滑动用直线拖过去,点击落在控件正中。这套写法能跑通,但跑出来的形态有明显特征——间隔永远是整一秒,轨迹是几何直线,落点精确到像素中心。人不会这样操作。

这一版把等待收进一个统一的层,给的是区间而不是常数:点击间隔 0.34 到 1.30 秒,在这个区间内取样而不是取固定值;滑动走 smoothstep 曲线,起止慢、中段快,符合手指加速再减速的形态;落点做 ±4 像素的偏移,不落在控件正中;按压时长 72 到 155 毫秒,覆盖轻点与稍重按压的区间。

为什么是这些数:区间取自真实手动操作时录到的分布,取的是覆盖范围而不是平均值。用平均值等于把所有操作改成同一个节奏,那和固定一秒没有本质区别。这也是这一版最容易被误解的地方——它不是为了「更像人」这个说法,而是为了让间隔的分布不呈现单一峰值。

影响到谁:所有走设备端的流程。网页通道不受影响,因为它不产生点击行为。

怎么迁移:把流程文件里手写的固定 sleep 删掉,统一走拟人化层。两层叠加是最糟的状态——流程里等一秒,外层再随机 0.3 到 1.3 秒,实际间隔变成一个更奇怪的双峰分布。迁移时逐条检查流程文件,凡是显式写死的等待都要处理。

怎么验证:看日志里记录的实际间隔与落点偏移,看分布而不是看单条。单条看起来正常没有意义,一条流程跑一百次,间隔如果集中在某个窄区间,说明随机取样没生效。滑动轨迹可以在录制回放里逐帧核对,落点偏移看坐标差是否落在 ±4 像素内。

这一版的边界要说清楚:参数只能改变操作形态,不能改变账号本身的权重、内容质量与类目竞争。把参数调到最像人,也解决不了文案与实际不符的问题。参数取值的完整说明在防封控,设备端的执行链路在手机 APP 自动化通道。

日志这一版也补了字段:每一次点击记录实际间隔、落点坐标与按压时长,滑动记录起止点与曲线类型。这些字段平时没人看,但排查时是唯一的证据——判断「是不是节奏太整齐」靠的就是间隔的分布,而不是感觉。日志保留一个版本周期即可,不长期堆着,否则翻起来更慢。

06  /  3.16

中文输入与设备层:三条通道与一把锁

3.16 解决的是「流程跑完了,但标题是空的」。根因是输入:adb input text 只能打 ASCII,中文打不进去,而且不报错——它把命令执行完,屏幕上什么也没出现。这类失败最难查,因为日志显示成功。

这一版给了三条输入通道,按可用性排序使用:uiautomator2 通道、adbkey 通道、adb_input 通道。其中 uiautomator2 通道已验证可以输入中文,是默认选择;另外两条作为回退,在设备不支持或稳定性不足时使用。回退的设计原则是「换通道而不是整体失败」——输入这一环失败不该让整条流程作废。

同一版还处理了两个设备层的坑。第一个是端口:uiautomator2 一个进程只绑一个 adb server 端口,两个进程同时起会互相抢占,表现为随机一台设备失联。解法是端口锁——起进程之前先拿锁,拿不到就排队,不能并发。第二个是界面读取:uiautomator dump 在 launcher 屏会失败,桌面状态下拿不到层级,流程就卡在第一步。解法是先试 --compressed,失败再退回普通 dump,并且带重试;同时控件定位改为候选列表加 instance 序号,而不是单一选择器——界面上同文案的控件往往不止一个,写死一个选择器,改版一次就断。

影响到谁:所有走设备端的部署,多设备并行时最明显。

怎么迁移:把控件选择器改成候选形式,给每个候选留出 instance 序号;把输入动作改成走统一入口,不要在流程里直接调 adb 命令;多设备部署确认端口锁已生效,起进程的路径上不能有绕过锁的分支。

怎么验证:自检在这一版之后固定跑三组——网页通道 82/82、主路径冒烟 26/26、手机通道 24/24,另外在 3 台设备上各跑 13/13 的引擎自检。这些数字的意义不是「全绿」,而是覆盖到了具体分支:dump 失败后的回退、输入通道的切换、端口锁的排队。多设备并行的完整说明在多设备管理。

候选选择器要按界面上的实际顺序写,而不是按语义写。举个具体的例子:发布页里「图片」这个词可能出现三次——相册标题、已选图片计数、上传按钮,文案一样,层级不同。写死一个选择器,命中的是哪一次完全取决于加载时序;给候选加 instance 序号,才能表达「第三个出现的那个」。序号也不是越多越好,超过三四个说明这块界面本身不稳定,应该改用 dump 先定位再操作。

输入通道的切换要有日志:这一次用的是哪条通道、切换发生在第几步、切换之后有没有成功。没有这三条,输入失败会变成「偶发」,而偶发是无法修的。

07  /  3.15

ADB 通道从零落地:五层各管一件事

3.15 是这条通道的第一个可用版本。做法不是找现成方案拼,而是从零写,原因是设备端的失败形态太杂:连不上、连上了不通、通了但读到的是上一次的界面、界面读到了但点不中、点中了但输入不进去。每一种都需要独立的判定与重试,拼不起来。

最终分成五层,每层只管一件事。adb 层管连接:超时封顶,不能无限等;server 显式寻址,不靠默认;设备存活只认 shell 往返——进程在、端口开着都不算活,只有一条命令能来回才算。uia 层管界面:设备端 dump 带重试,选择器给候选与 instance 序号。humanize 层管操作形态,参数在 3.17 收敛。device 层管输入通道,三条通道的编排在 3.16 补齐。flow 层管流程:YAML 描述动作,引擎负责顺序、条件与中断。

YAML 的动作集刻意做小:by_any 按候选顺序找控件,if_enabled 与 if_exists 做条件分支,abort 主动终止,reset 回到已知状态,require 声明前置条件不满足就停,dump 与 shot 留证据。不做循环、不做变量运算,是因为流程一旦复杂到需要编程,维护成本会比手工操作更高,出错也查不出来。

这一版最大的坑不在设备,在环境:驱动跑在 WSL 里时,到 Windows 侧的 TCP 不通,adb 连不上设备。试过转发、试过改绑定地址,最终结论是不要绕——驱动必须跑在 Windows 侧,和 adb server 在同一侧。这条结论写进部署说明,因为它的现象(连不上)和很多别的原因长得一样,容易被误判成设备问题。

影响到谁:所有新接入的部署。升级本身不改变已跑通的配置。

怎么迁移:按五层的边界检查自己的流程文件——如果流程里直接写了 adb 命令,说明绕过了 uia 与 humanize 两层,这部分要收回;如果 YAML 里出现大量重复的等待与点击,说明该拆成分步动作。

怎么验证:先跑引擎自检(每台设备 13/13),再跑手机通道自检(24/24),最后跑主路径冒烟(26/26)。三组都过再上真实商品。部署步骤与运行环境要求见下载与部署,通道本身的设计说明见手机 APP 自动化通道。

超时封顶与存活判定是这一版最不显眼但最省事的两处。没有封顶时,一个卡住的命令会把整条流程挂在那里,看起来像设备死机;有了封顶,卡住的步骤会带着超时退出,流程继续往下走或者按 abort 收口。存活判定改成只认 shell 往返,是因为进程在、端口开着这两种状态都可能出现在设备其实已经掉线的时候,唯独一条命令来回能证明它还在。

require 这个动作值得单独用一次:它声明前置条件,不满足就停,而不是继续往下点。典型用法是进发布页之前先 require 当前在首页,不在就 abort——后面所有步骤都假定从首页出发,一旦起点错了,点下去的结果是随机的。

08  /  迁移

升级的四步与三条不能绕的限制

把五个版本的迁移动作收成一套通用步骤,顺序不要改。第一步,备份:生产台账、流程文件、配置文件各留一份带版本的副本,副本与线上文件不在同一个目录。第二步,隔离自检:在独立路径跑一轮完整自检,跑前跑后取生产文件哈希,两次数值必须一致。第三步,低风险试发:挑一条最不重要的商品走完整链路,从计划生成到提交到台账记录,全部看过一遍。第四步,放开:确认前三步都成立再恢复全量发布。

回滚的做法是换文件,不是改参数。流程文件与配置都带版本号,回滚就是把上一份文件换回来,然后重跑自检。改参数回滚的问题在于改到哪一步算回到原状说不清,而换文件是可验证的。

第一条限制:平台界面改版会打断控件定位。控件候选与 instance 序号能提高存活率,但不能保证永不断。改版之后流程会在某一步停住,这时看 dump 与截图定位,不要靠猜。第二条限制:设备端依赖具体机型与系统版本。同一套流程在不同机型上的表现可能不同,多设备部署要按机型分组验证,不能一台过就全过。第三条限制:系统对发布结果不做承诺。发出去了会不会被平台清理、账号权重如何变化,这些不在系统能保证的范围内,一切都以实际执行日志为准。

还有一条不属于技术限制但更常被忽略:这套系统不承诺绕过平台规则。合规闸门拦下的内容,改标题重发不会变得合规,只会换一个原因被拦。闸门的检查项与判定口径见商品合规检查。

下一版的方向集中在两处:一是把复检闸门的原因码做得更细,现在只能区分大类,定位还要人工看日志;二是把多设备的分组验证做成固定流程,减少按机型逐台手测的成本。两处都不改变现有配置,升级不需要额外迁移。风险边界与责任划分的常见问法在常见问题里。

升级的时间窗口也值得挑一下。不要在发布窗口内升级——09:00 到 21:59 之间任何一次重启都可能让在途计划停在半路,而半路停下比干脆不发更难处理。稳妥的窗口是发布窗口结束之后、次日开始之前,并且确保这个时间段内没有排队的计划。如果计划是跨天排的,先把队列清空再升。

升级会不会影响正在跑的发布计划?
会,但影响面取决于版本。3.19 之后定时任务在提交前会重跑合规闸门,被拦下的计划会停在待处理状态而不是直接发出,所以升级后第一天的发布成功率通常会下降,这是预期行为。3.18 会改变台账的记录口径,历史记录需要按新字段重导一次。3.16 与 3.17 只影响流程文件里的等待与输入写法,不影响已经在跑的计划本身。稳妥的做法是先备份、在隔离路径跑一轮自检、挑一条低风险商品试发,再恢复全量。
自检为什么不能和生产用同一个数据目录?
因为自检会走完整发布链路并写记录。如果它与生产共用目录,这些记录就会混进生产台账,让「到底发没发」失去答案。曾经出现过一次这类事故,根因是程序先解析路径再读取环境变量,隔离目录的环境变量等于没生效。现在有四道防护:先读环境变量再解析路径、写生产前留备份、自检进程带标记时写生产直接抛错、整轮自检前后对生产文件做哈希比对。只要哈希不变,这一轮自检就没有污染生产。
拟人化参数是不是调得越慢越安全?
不是。参数的目的是让间隔与轨迹呈现分布,而不是让每个动作都变慢。点击间隔 0.34 到 1.30 秒之间取样,取的是覆盖范围;如果把下限也抬高,间隔会集中到一个窄区间,反而更像机器。真正需要避免的是单一峰值,也就是所有操作都停同一个时长。另外不要在流程文件里再手写固定等待,外层已经做了随机,两层叠加会得到一个更难解释的分布。
两条通道的记录为什么以前会对不上?
因为 3.18 之前两条通道各自记自己的账,字段名不同、时间戳口径不同、item_id 有时为空。同一条商品在两边各有一条记录,或者两边都没记。现在两条通道写同一本台账,字段固定为 item_id、标题、描述、价格、时间戳,写入入口只有一个。同时,某一条商品归哪条通道执行改为由同一个函数计算,不读人工标记,所以同一份计划重跑不会改变归属。
驱动能不能放在 WSL 里跑?
不建议。实测 WSL 到 Windows 侧的 TCP 不通,adb 连不上设备。试过端口转发与改绑定地址,都没有稳定结果。现在的结论是驱动必须跑在 Windows 侧,与 adb server 在同一侧。这条限制写进部署说明,是因为它的现象只是「连不上」,很容易被误判成设备故障或线材问题,从而在错误的方向上排查很久。

升级判断

升级的理由应该是问题变了,不是版本号变了

版本号往上走不等于你该跟着走,判断标准在下面这句。

什么时候值得升级

如果你现在的问题已经不在「发不出去」这一层——比如你说不清哪条发了、哪条被拦、为什么被拦——那么 3.18 与 3.19 的改动直接对应你的问题。如果你只是看到有新版本,而当前部署没有新的失败形态,等一个版本周期再升也来得及。三种情况建议尽快升级:多账号却说不出记录归属、定时发布出现过「到点内容已失效」、自检与生产共用数据目录。

先跑通一条真实发布,再谈铺量

把账号、设备、商品素材交给我们的部署流程,先跑通一条真实发布并留下可核对的日志,确认没问题再谈规模化。