01 / 通道定位
先说清楚它替代的是哪一段手工
闲鱼的商品发布主要发生在手机客户端。电脑网页版通道走 HTTP API,速度快、可以并行提交,但客户端里有些入口在接口层面并不完全等价:编辑页的字段展示顺序、图片上传之后的裁剪位、草稿状态的保存时机、部分只在 APP 内出现的活动入口。手机 APP 通道解决的就是这一段——用 ADB 驱动一台安卓真机或模拟器,把「打开应用、进入发布、填标题、填描述、选类目、传图、定价、提交」这一串动作写成 YAML 流程,按顺序执行,每一步留下日志。
它的价值不在点得快,而在点得一样。手工发布时,同一个人上午和下午填字段的顺序都会变,漏填包邮、漏选成色、图片顺序颠倒,全靠记忆维持;换成流程文件之后,字段与顺序是写死的,跳过一步就会在日志里显出来,事后能回查是哪一条商品在哪个环节停住。判断自己该不该用这条通道,主要看三件事:
发布动作是否重复
同一套字段、同一批图片反复填,重复才有写流程的收益。如果每次字段都不同、一个季度只发几条,维护流程的成本会高于收益。
是否必须在手机端走完
某些类目在客户端里的字段比接口更完整,或者你的账号只在 APP 里能走通整条发布链路。这种情况下手机通道不是可选项,而是唯一路径。
能否留出一台稳定设备
流程跑到一半断网、锁屏、弹系统更新提示,这一轮就作废。设备要能长期插电、接固定网络、关掉自动更新与锁屏密码弹窗。
如果只有第一条成立,先看电脑网页版通道更划算:两条通道共用同一套选品、文案、闸门与台账,换通道不需要重做内容,两者的分工写在产品功能总览里。反过来说,手机通道也不是「另一套系统」,它读的是同一份发布计划,只是执行端换成了屏幕上的点按。
还有一个容易被忽略的判断点:你打算发布的是「新品」还是「同一批货的多个链接」。前者字段每次不同,流程的价值在于不遗漏必填项;后者是同一份内容复制到不同链接,流程的价值在于顺序与配图完全一致。两种诉求都能满足,但流程的写法不同——前者的分支多,后者更依赖去重与分散。
还有一类场景容易被忽略:同一条商品要在不同账号或不同城市重复上架。这时候流程的价值不在省点击,而在于每一条的配图顺序、成色标注、运费模板完全一致,不会因为换个人操作就出现「这条写包邮、那条不写」的差异。判断这条通道值不值得接,最后可以落到一句话:你的发布动作里,有多少是可以被写成「先判断、再执行」的?这个比例越高,接进来的收益越明显;如果绝大多数步骤都要临场决定,那先别接,先把发布规范写出来。
另外要接受一件事:这条通道不会让发布变得「快很多」,它的收益是可核对。原来靠人记的字段顺序、配图顺序、运费模板,现在写在一个文件里;原来出错只能事后发现,现在提交前有断言、有闸门。如果你的目标是「今天多发一批」,那么要管的其实是节奏,不是通道。
02 / 环境准备
设备与环境:先排除三类会反复失败的配置
手机通道的失败,多数不是流程写错,而是运行环境没弄干净。上线前必须把三类问题先排掉:设备本身的状态、ADB 服务怎么寻址、多个进程怎么分端口。这三类的共同点是:出问题时表现得很像流程 bug(某一步一直超时),但改流程永远改不好。
真机还是模拟器
真机最接近真实客户端环境,代价是电量、网络、系统弹窗都要人管;模拟器可快照回滚、批量复制,但部分客户端能力(摄像头、定位、机型信息)与真机不同。惯常做法是:主发布路径用真机,回归测试与批量压测用模拟器,两边的流程文件保持同一份。
驱动必须跑在 Windows 侧
已知的工程坑是 WSL 到 Windows 的 TCP 不通。如果驱动跑在 WSL 里,ADB 连不上 Windows 侧的设备服务端,表现为「设备在,但命令一直超时」。所以驱动进程放在 Windows 侧执行,不要绕一层。
ADB 服务端显式寻址
不要依赖默认端口碰运气。服务端地址与端口在配置里写死,命令带超时封顶,避免一条卡住的命令拖死整轮流程。判断设备是否存活只认一次 shell 往返的结果,不看进程列表里有没有那个名字。
一个设备一条端口,加端口锁
uiautomator2 一个进程只绑一个 adb server 端口,多设备并行时必须一机一端口。同一端口被两个进程同时打开会互相踢掉,所以还要加端口锁,拿不到锁就排队等,不要重试抢。
下面这五条是上线前的固定检查项。它们看起来琐碎,但每一条都对应过真实的「偶发失败」:
- 设备固定固定机型、固定充电位、固定网络,别每天换一台手机测同一份流程
- 系统弹窗清干净关闭系统更新、应用商店自动升级、锁屏密码与指纹弹窗
- 权限一次给全存储、通知、悬浮窗、输入法切换权限,缺一项会在某个步骤突然卡住
- 路径先于环境变量配置里的数据目录用绝对路径,不要依赖启动之后再设置的环境变量
- 写生产数据前留备份任何会改动正式台账的操作,先落一份 .bak 再动手
设备数量超过一台之后,端口分配与设备编号要成体系地管,做法见多设备并行管理;在只有一台设备的时候,先用一台把全流程跑顺,是更快的一条路。
环境还有一个隐性条件:时间。设备时间与运营电脑的时间不一致,会让发布时间戳、台账记录与平台显示的时间对不上。排查时人会先怀疑流程写错了,实际只差了几分钟。所有设备统一对时,并且让流程启动前先读一次设备时间写进日志,这一条在跨设备并行时尤其重要,否则几条记录谁先谁后都说不清。
03 / 流程设计
YAML 流程怎么分段,一段只做一件事
流程文件的写法决定它好不好维护。经验规则是一屏 3 到 6 步,一步只做一个动作,每步之前先确认目标在不在。把所有点击塞进一个长步骤里,失败时只知道「这一步错了」,不知道错在第几下。流程动作是可枚举的,用法有明确边界:
| 动作 | 作用 | 什么时候用 |
|---|
| by_any | 给一组候选选择器,任一命中即通过 | 按钮文案在版本之间会变,或同一功能有两种控件类型 |
| if_enabled | 控件可点击才点 | 提交按钮在图片未传完时是灰的,先判后点 |
| if_exists | 存在就执行,不存在跳过 | 关掉版本更新提示、网络异常弹窗这类偶发遮挡 |
| require | 必须存在,否则判失败 | 发布页的标题输入框、价格输入框 |
| abort | 主动中止并记账 | 闸门没过、类目选错、关键字段为空 |
| reset | 回到干净起点 | 上一轮中途失败,避免带着半屏表单进下一轮 |
| dump | 导出当前界面结构 | 定位失败时抓现场,用来改选择器 |
| shot | 截图留证 | 提交结果页、异常弹窗 |
分段建议按页面切:进入发布页、填基本信息、选类目与成色、传图与排序、定价与运费、提交与回执。每段结束加一个断言,比如「标题框的内容长度与源文本一致」「图片数量等于计划数量」「价格与计划价一致」。分段的另一个好处是失败可以定点重试:只有传图那段失败,就只重跑那一段,不必从打开应用开始。
有一种常见误区是把断言写成截图人工看。截图只能证明流程跑过,不能证明字段填对了;断言必须读控件的实际文本或实际数量。反过来也不要断言过度——如果每一步都断言,一处界面文案变化就会连着报十几条失败,日志反而看不清到底哪一环出了问题。断言只放在「错了会出事故」的位置:标题、价格、类目、图片数量、提交结果。
流程文件要能被人读懂,也要能被重复使用。变量、类目、文案都应该从外部读取(发布计划、商品池),流程只负责动作与断言;如果流程里写死了某条商品的标题,这份文件就只能跑一次。判断标准很简单:换一条商品,需要改流程文件吗?需要改,说明职责分错了;不需要改,才是可复用的流程。
流程还要考虑幂等:某一段失败重试时,必须能判断这一步到底做没做过。比如传图这一段,重试前先读一次已有图片数量,已经传了 4 张就从第 5 张开始,而不是从头再传一遍,否则最后一张链接上会出现重复图,数量超限被闸门挡住,人还以为是闸门误判。
04 / 中文输入
中文输入是第一个坑,三条通道各有边界
ADB 自带的文本输入打不出中文,原因是它只按 ASCII 键位发送,中文会变成空白或者一串问号。这一点不解决,后面所有步骤都没有意义——标题框是空的,提交流程照样能走完,但发出去的商品没有标题。系统里准备了三条输入通道,按场景选:
uiautomator2 通道
这是主力通道,中文已经实测可以完整输入,长短文本都稳。默认先用它,除非设备上出现输入法冲突。
adbkey 通道
模拟按键输入法逐字敲。设备上装了第三方输入法、u2 输入被吞的时候切到这条;代价是长文本慢,遇到生僻字可能漏。
adb_input 通道
通过输入法广播接收整段文本。适合一次贴入长描述,前提是设备端已装好配套输入法并切为当前输入法。
三条通道都要配同一个验证动作:输入完成后,把输入框里的实际文本读出来,与源文本逐字比对,不一致就中止,不要带着错文本提交。这条断言比截图有用得多,因为它能抓到「输入框只进去前几个字」「换行被截断」「末尾空格被吞」这些真实故障。
另外两种常见失败要提前处理:输入框拿到了焦点但软键盘没弹出来(先点一次输入区域再发文本),以及长文本粘贴被剪贴板权限拦住(改用逐段输入,每段之间加一个短确认)。描述里如果有分段换行,换行符在三条通道里的处理并不一致,需要在描述段单独测一次,而不是假设它能通用。
文案本身怎么写、怎么规避绝对化用语与重复表述,属于另一个环节,见AI 商品文案改写。这里只保证一件事:写好的那段中文,能原样落进客户端的输入框里。
中文输入还有一个细节:候选词。用按键模拟逐字敲的时候,同音字会被候选词替换,标题里的型号、编号尤其容易出错,而且错得毫无提示。所以凡是含型号、字母与数字混排的字段,输入之后必须逐字比对,不能只看长度——长度相同但内容不同的错误,是这套流程里最难被发现的一类。
输入通道的切换要写在流程配置里,而不是每次手动改。同一批设备里如果有两台必须用按键通道,配置就要按设备编号区分,而不是全局改成一条——全局改会把原本正常的那几台也拖慢。
05 / 控件定位
控件定位:界面改版是常态,不是事故
客户端更新一次,控件层级、文案、控件类型都可能变。流程能不能活下来,取决于选择器怎么组织。这里有四个必须做的事,顺序也就是排查顺序:
dump 要带重试
设备端 dump 偶发返回空或者连接被重置,一次失败就判流程失败会制造大量假警报。做法是重试若干次,仍失败才放弃,并把最后一次的原始结构落盘。
launcher 屏先试压缩模式
已知现象:在 launcher 屏上直接 dump 会失败。处理顺序是先试压缩模式,不成就退回普通 dump,两条都失败再判环境问题,而不是判流程问题。
选择器给候选,用序号消歧
同一句文案在页面上可能出现多处(比如两个「发布」)。每个选择器配 instance 序号定位到具体那一个;同类控件用一组候选,任一命中即可,避免为一种控件类型写死一条流程。
改版只改一处
把选择器集中在流程文件的一小段,界面改版时只动这里。如果选择器散落在几十个步骤里,一次改版就要通读全文——这也是流程要分段的实际理由。
怎么判断定位稳不稳:连续跑三轮全部通过才算稳;偶发失败要记频率和当时的界面结构,频率高的先怀疑选择器太具体(依赖某个版本独有的文案),而不是怀疑设备。反过来,如果失败了却一直重试成功,说明选择器已经脆了,趁还能跑的时候改成候选式,别等到彻底断掉。
还有一种情况值得单独说:选择器命中了,但点下去没反应。原因通常不是定位错,而是控件在视觉上存在、实际被遮挡(弹窗、遮罩、软键盘),或者点击坐标落在了不可点区域。这类问题靠 dump 结构看不出来,要用 shot 留一张现场图,再对着图判断遮挡层是什么,然后为它加一条 if_exists 关闭动作。
选择器应该跟着语义走,而不是跟着界面走。它描述的是「价格输入框」这个角色,不是「页面上第 7 个输入框」这个位置。前者在界面微调之后仍然有效,后者一次调整就全断。语义化的选择器第一次写起来慢一点,但改版时省下的是整段时间,也不会因为换了一台分辨率不同的设备就失效。
跨机型的时候还有一个坑:同一份流程在两台分辨率不同的设备上,同一个控件的位置会变,但结构里的层级关系不变。这也是选择器必须按语义写的直接理由——按坐标写的流程换个机型就全错,按结构写的一直有效。多设备并行时,流程文件应当只有一份。
06 / 操作节奏
拟人化参数:每个取值都有具体理由
机械化的操作有很明显的特征:点击间隔是一模一样的常数、每次都落在同一个像素、按下与抬起之间没有停留。引擎在这一层做了四件事,参数都是实测收敛出来的:
| 参数 | 取值 | 解决什么 |
|---|
| 点击间隔 | 0.34 到 1.30 秒之间随机 | 固定间隔是最容易被识别的特征,随机区间让节奏不呈现周期 |
| 滑动曲线 | smoothstep | 线性滑动在起止处速度突变,缓入缓出更接近手指 |
| 落点 | 目标坐标正负 4 像素 | 每次点在同一像素上属于机器特征,微抖动是正常人的手 |
| 按压时长 | 72 到 155 毫秒 | 瞬时点击几乎没有按下过程,短按需要有真实停留 |
需要说清楚的是,这一层的作用是让操作不呈现机器特征,不是账号安全的全部。账号安不安全,更取决于发布节奏上限、商品是否合规、同款链接是否堆在同一账号上——这些属于另一种控制,见防封控与账号安全。
另一个方向上的误区是把间隔拉到很长,觉得越慢越安全。一条商品如果卡十分钟,设备占用时间变长、失败重试窗口变短,实际稳定性反而下降。参数应该停在「不像周期」而不是「尽可能慢」的位置。还有一点:随机区间要真的随机,不要用固定序列循环,否则连续几十次操作之后仍会出现肉眼可辨的节律。
滑动这一项最容易被忽略。商品详情页经常需要下滑才能看到提交按钮,如果滑动速度恒定、距离每次相同,这一段就是整条流程里最像机器的片段。smoothstep 解决的是速度曲线,距离本身也应该按目标位置算,而不是每次滑固定距离。
参数不是越细越好。把每一次点击都单独算一遍随机,代价是流程不可重放:出了问题没法复现同一段操作。惯常做法是随机源可固定——调试时用固定种子复现,正式跑用真随机。这一点直接决定你能不能在事后重演一次失败,也是排查「偶发」问题时的唯一抓手。
记录参数时把「间隔区间」和「落点抖动」分开记。合并成一个「拟人化开关」,出问题时就无法判断是哪一项调过头了,只能整体回退。
这四项参数在配置里应该各自独立可调,并且写进注释说明理由。改的时候一次只动一项,动完跑一轮看断言是否仍然全过,才有办法知道是哪一项带来的变化。
07 / 发布闸门
提交前的复检闸门,默认是拦住而不是放行
流程能点完,不代表这条商品该发。发布计划可能是几小时前排的,这中间商品改了价、链接被下过、同类目已经在售同款。所以到点提交之前要用当下的数据重跑一遍闸门,不通过就不发,这个策略叫 fail-closed——失败时的默认动作是停止,不是继续。
合规
标题与描述里的绝对化用语、功效宣称、侵权表述,逐条比对规则库,命中就拦。规则怎么分层见下面的检查清单与合规页。
商品指纹去重
按标题、描述、价格、主图算指纹,与台账里已有的记录比对,重复的直接不发,避免同一件货在两个链接上互相抢流量。
分散度
类目、发布时间、标题、主图相似度要拉开。同一批商品挤在同一时段、同一类目、同一套图,是最明显的批量特征。
结构约束
同款链接在一个账号内不超过 3 条;单条链接配图 3 到 9 张。少图信息不足,多图容易混入重复图。
除了商品侧的闸门,还有平台健康闸:平台侧出现异常(接口报错率升高、页面结构大面积变化)时暂停整批发布,不硬发。硬发的代价是拿一批真实商品去试探平台状态,试探失败留下的是账号记录,不是日志。
闸门的位置要放在提交动作的前一步,而不是流程开始时。放在开始时会漏掉排期之后发生的所有变化,也漏掉图片上传过程中才发现的问题(比如某张图上传失败导致只有 2 张,低于下限)。规则库的组织方式与每条规则判什么,见商品合规检查。
- 闸门在最后一步提交前跑,不用排队时的旧数据
- 不通过就 abort不降级、不「先发出去再说」,失败记进台账
- 平台异常整批暂停不是只跳过当前这一条
- 规则库可追溯每条拦截要能说出命中了哪条规则
闸门不通过之后的处理也要写清楚:是丢掉这条商品,还是退回队列等下一轮?退回队列更合理,但要带上退回原因与重试次数上限。否则一条永远不过闸的商品会被反复排进计划、占满槽位,把真正能发的商品挤出去,表现出来的现象是「计划排得很满,实际一条没发」。
闸门的结果要写回台账,不只是拦住就完了。记录里保留「哪条规则、哪个时刻、什么数据」,下次同一批商品再排进来时,能直接看出是规则变严了还是商品本身变了。没有这一步,闸门就只是一道黑箱,人会开始绕过它。
08 / 台账与证据
台账、证据,以及那些踩过一次的坑
两条通道写同一本台账,口径只有一处,记录里包含 item_id、标题、描述、价格与时间戳。共用一本台账的意义在于:换通道发布、补发、核账时看到的是同一份事实,不会出现「手机发的在一张表、网页发的在另一张表」这种对不上的情况。具体的表结构与看板用法见发布数据台账。
失败要留现场
定位失败时导出界面结构,提交异常时截图。日志里只有「第 5 步失败」,事后无从判断是改版还是网络,有了 dump 才能改选择器。
自检不能碰生产
曾经出现过自检污染生产发布记录的问题,根因是路径解析早于环境变量生效,自检写到了正式数据文件上。现在的做法是:写生产数据前留 .bak,自检进程带标记时写生产直接抛错,整轮自检前后对生产文件做哈希比对。
自检要有具体数字
当前手机通道自检 24 项全过,引擎自检在三台设备上各 13 项全过。数字的意义是回归基线,每次改动后跑一遍,掉一项就说明改坏了什么。
- 端口不抢多设备并行时一机一端口,加锁排队,不重试硬抢
- 驱动在 Windows 侧不要放进 WSL,TCP 不通会表现为满屏超时
- dump 有降级路径先压缩模式,再普通模式,最后才判失败
- 断言读文本标题、价格、图片数量都读控件值比对,不靠截图
- 每轮结束 reset中途失败也要回到干净起点,避免半屏表单进下一轮
把这五条做扎实,这条通道就能长期跑;哪一条松了,故障会以「偶发失败」的形式出现,而偶发故障最难查——它不留下稳定复现的路径,只留下一条条对不上的台账记录。发布节奏怎么排、每天发几条、间隔怎么定,属于另一层控制,见定时发布与节奏控制。
台账要能反查一次发布的完整过程:谁排的、哪条通道执行的、闸门放行的时间、提交返回的 item_id、失败时停在哪一步。缺了其中任何一项,事后复盘就只能靠记忆。字段宁多不缺,但每条记录只能有一处来源,不要两个模块各写一半,否则早晚出现两份互相矛盾的事实。
这也解释了为什么台账要放在通道之外:通道是可以换的,台账不能跟着换。哪条通道写的记录,字段口径都应当一致,否则换一次执行方式,历史数据就对不上了。