dsh-plugin-shop-accounts · 账号管理
把电商卖家后台的登录态集中管起来:一个店铺 = 一份独立的 Chrome 档案 + 一个固定的调试端口。
登录一次就长期复用,上架脚本直接连那个端口,不用重新登录。
DSH 插件,支持 拼多多 / 淘宝 / 天猫 / 抖音(抖店)/ 京东(京麦)/ 1688 —— 全部是商家端(卖家后台)。
- 版本:0.6.5(变更历史见 CHANGELOG.md)
- 自检:
node test/smoke.mjs → 47 项全过(不启动浏览器、不碰真实账号)
目录
它能干什么
| 能力 | 说明 |
|---|
| 一店一档案 | 每个店铺独立 user-data-dir + 独立 CDP 端口(默认从 9222 起自动找空位),互不串 cookie |
| 真实窗口登录 | 拉起的是有头 Chrome,你正常扫码/密码/滑块;插件后台轮询,判成已登录后点「完成」自动关窗,登录态留在磁盘 |
| 静默检测 | 不开窗口时用无头 Chrome 探一次,判登录态并写回;约 2.6–3.1 秒/店 |
| 定时检测 | 每天定点把全部店铺排成一队依次体检,状态自己刷新,不用人一个个点 |
| 保活 | 判已登录后让页面多停留几秒再复查,把「活跃」做足(站点访问本身就会续期凭证) |
| 版本与更新 | 面板里检测 GitHub 新提交,一键快进合并 + 覆盖安装副本 |
| 多店铺管理 | 搜索、按平台筛选、详情页、单店暂停、拖拽排序排期 |
插件只负责把登录态养在那里:不碰商品、不提交、不改价。
安装
前置:DSH Desktop;本机有 Chrome(没有则用 Edge 兜底)。
A. 从源码目录装(开发时推荐)
dsh plugin --profile <profile名> add file:D:\path\to\dsh-plugin-shop-accounts
B. 手动装(GUI 里没有安装入口时)
- 把整个目录复制到
%DSH_HOME%\profiles\<profile名>\node_modules\dsh-plugin-shop-accounts\;
- 改
%DSH_HOME%\profiles\<profile名>\package.json 两处:
dsh.profile.bundles 数组里加 "dsh-plugin-shop-accounts";
dependencies 里加 "dsh-plugin-shop-accounts": "file:<本目录绝对路径>";
- 重启 DSH Desktop。
%DSH_HOME% 默认是 ~/.dsh。
插件是启动时挂载的:改宿主半身(lib/index.js 等)必须重启;只改客户端半身(lib/client.js)
刷新页面即可 —— 但两种都要先把文件同步进 profile 的 node_modules 副本。
装好之后,「设置」里会出现 账号管理。
五分钟上手
- 设置 → 账号管理 → + 新增店铺:选平台、填店铺名(备注可选,端口留空 = 自动分配)。
- 点 创建并去登录 → 弹出一个真实 Chrome 窗口。
- 在里面登录到卖家端后台(各平台入口见平台表)。
- 回到 DSH,行内状态会自己变成「已登录」→ 点该行的 完成并关闭(窗口自动关掉,登录态留在档案里)。
- 之后这个店铺就一直显示登录态:行内「调试端口」+「复制路径」就是给上架脚本用的两样东西。
- (可选)点 调整排期,勾上「开启定时检测」,让插件每天自动体检一次。
界面说明
列表页
[浏览器设置] [刷新] [+ 新增店铺]
┌ 每天 10:00 起 · 每组 1 个店铺,组间隔 30 分钟 [调整排期] ┐
│ 适用于全部店铺,含已暂停任务;错过时间不补跑。 │
└──────────────────────────────────────────────────────────────┘
[全部 3] [拼多多 2] [淘宝 1] …
[搜索 店铺名、备注、平台或端口]
店铺名 | 平台 | 定时检测 | 登录状态 | 调试端口 | 操作
韩品兰坊 淘宝 开启·10:30 已登录 :9224 [检测] [管理]
- 登录状态:
已登录 / 登录已失效(以前登过、现在掉线 → 需重新登录)/ 未登录(从没登过)/
未检测 / 运行中·待检测 / 等待登录。下面一行是「检测于 …」。
- 定时检测:行内胶囊
开启·HH:MM / 暂停·HH:MM,点一下切换这家店跑不跑;下面的「详细配置」打开排期弹窗。
- 操作:
检测(无头静默复核)、管理(进详情页)、以及按状态出现的 去登录 / 完成并关闭。
行内对话框里的其它动作:打开(把窗口重新拉到工作台)、重扫(清掉本地档案再打开登录页——
站点在档案里留着半死会话、登录页直接跳回后台时用)、关闭、复制路径、编辑、删除(可选连档案目录一起删)。
窗口已经开着的店铺点「去登录/重新登录」,会在已有的浏览器里新开一个登录页标签页,不会再拉第二只
Chrome(Chrome 单实例:同档案再启一次只会把 URL 转给已有窗口;调试端口仍被老实例占着,强行再启会变「点了没反应」)。
店铺详情页(行内「管理」)
平台/店铺名(只读)· 店铺 URL(选填,填了就作为「打开」时的地址)· 账号 · 账号类型 · 登录状态、端口、
上次登录、凭证到期、保活停留。底部:删除账号、重新登录/去登录,以及
检测登录态 / 打开工作台 / 重扫 / 复制档案路径 / 编辑店铺。
浏览器设置
浏览器的可执行文件路径(留空 = 自动找 Chrome,没有就用 Edge)、数据目录,以及 版本与更新 卡片。
定时检测(排期)
列表上方那条横幅就是排期:到点对店铺做一次静默检测(无头 Chrome、不弹窗),结果写回,
列表里的「登录状态」自己就跟着刷新。点 调整排期 设置:
| 字段 | 说明 |
|---|
| 每日起始时间 | 每天从几点开始跑 |
| 组间隔(分钟) | 组与组之间隔多久 |
| 每组店铺数 | 一组跑几个;留空 = 自动(内存 ≤16GB → 1,>16GB → 2) |
| 保活停留(秒) | 判已登录后多停几秒再复查,默认 8,0 = 检测完就关 |
| 开启定时检测 | 总开关(关着时横幅变灰、什么都不跑) |
| 排期预览 | 每个店铺的时间点,按住 ⠿ 拖动调整顺序 |
规则:
- 全店排成一队:每
每组店铺数 个为一组,同一组共用同一个时间点,下一组 = 上一组 + 组间隔 分钟。
- 单店开关只管「跑不跑」,不影响它占的时间点 —— 暂停一家不会让后面的店铺提前。
- 错过不补跑:只有到点后 2 分钟内执行;机器睡了一觉、隔天才开就跳过,不在半夜补一堆检测。
- 同一天同一个时间点只跑一次(记账在
config.schedule.lastSlots,重启 DSH 也不会重复)。
- 跳过两类店:正在等你登录的(
pendingLogin)、浏览器正开着端口上有 CDP 的 —— 不打断你手里的活。
- 宿主每 20 秒看一次排期;界面在排期开着时每 20 秒刷新一次。一轮只起一只无头 Chrome,串行跑。
- 检测失败记在
lastSchedError,行内显示「上次失败」,悬停看原因。
保活(尽量续期)
判定「已登录」之后不会立刻关浏览器,而是让页面再留 保活停留 秒再复查一次 —— 这期间页面自己的
心跳/XHR 会照常发出去,站点就把这次访问当活跃。实测(2026-10-19,拼多多真档案):
| 时刻 | 读到的凭证到期时间 |
|---|
| 11:47:18 那次检测后 | 2027-01-27T03:47:18Z |
| 18 秒后再检测一次 | 2027-01-27T03:47:36Z |
也就是每访问一次工作台,站点就把身份 cookie 重发一遍、到期时间重置成「当时 + 100 天」。
所以「续签」不需要调什么刷新接口,定期访问本身就在续。
两条别被「到期时间」骗了的边界:
- 纸面到期 ≠ 会话可用:掉线的店里 cookie 常常还躺着一整年(淘宝那家掉线时
sgcookie 还有一年),
但服务端会话已经作废 —— 判登录态仍以「站点把不把按回登录页」为准。
- 保活只是把「活跃」做足,不保证永不掉线;掉了照样如实报「登录已失效,请重新登录」。
关窗之后登录态还在不在(会话级 cookie)
淘宝卖家登录靠的 cookie2 / _tb_token_ 是会话级 cookie(档案 cookie 库里 is_persistent=0),
Chrome 默认关窗就丢。这就是「登录完点保存 → 关窗 → 再打开又是登录页」的真因(v0.6.6 起修复):
- 启动参数固定带
--restore-last-session:Chrome 才会把会话级 cookie 落盘、下次启动读回。
实测(shop-accounts-diag/session-cookie-plugin.mjs,走插件自己的启动器):不加 → 关窗就丢;加了 → 留得住。
- 这个开关会顺便恢复上次的标签页,所以静默检测改成「自己开一个干净标签 → 关掉其它页面目标 →
只认这个标签页」,免得拿恢复出来的旧登录页当判定依据。
- 点「保存登录态」时,关窗之后还会再复核一次:复核不过就回
saved:false 并说明原因,
不会再出现「提示保存成功、下一秒却是未登录」。
- 关窗之前还有一道门:按平台的身份 cookie(淘宝 =
unb + sgcookie)逐个核对,
缺就每秒重读、最长等 20 秒;等 5 秒还不齐会主动把窗口导回工作台催一次;等到超时仍不齐 →
不关窗、回 saved:false 并直说要等多久(v0.6.7)。
为什么要等:2026-10-19 抓到的真事 —— 工作台已经能看见店铺数据时,unb 可能还没种下来,
这一点就点保存,关窗带走的是一个还没拿到身份的会话。
cookie 罐本地备份(点窗口红叉也能救回来)
--restore-last-session 管得住「插件自己关窗」,管不住用户直接点窗口红叉:2026-10-19 的真机日志里,
那次关窗后档案从 86 个 cookie 掉到 42 个,unb 直接没了,下次启动就被按回登录页(v0.6.10 起兜底)。
- 只要浏览器里读到「已登录」,就把整罐 cookie 存一份到档案目录的
dsh-session-cookies.json
(<数据目录>/profiles/<账号>/dsh-session-cookies.json,含会话级 cookie 的值,只在本机)。
- 下次启动浏览器(静默检测、定时检测、点「打开」)时,先走 CDP
Network.setCookies 把档案里缺的那些
补回去,再打开页面。只补缺的、不覆盖已有的 —— 档案里已经有的同名 cookie 一定比备份新。
- 这个文件会被「清空档案(重扫)」一并删掉;不想留就手动删,删了不影响功能(只是少了这层兜底)。
- 验证:
shop-accounts-diag/cookie-backup.mjs 8 项全过(存罐子 → 页面里把会话级 cookie 过期掉 →
补罐子 → 会话级 cookie 回来且页面认;档案里的新值不会被旧备份盖掉)。
登录态怎么判
- 登录页最硬:站点把你按回登录页 = 站点不认这个会话,哪怕 cookie 罐里还留着身份 cookie
(服务端过期不会去清浏览器里的 cookie)也判未登录。文案按「这家店以前登过没有」分两种:
登过 → 登录已失效,请重新登录;从没登过 → 未登录,请前往登录页登录。
- 身份 cookie:域名对得上 + 名字命中该平台的身份 cookie + 值不是占位符。匿名 cookie
(
cna、_m_h5_tk、sdtoken…)人人都有,不算凭证。淘宝/天猫要求必须命中 unb / sgcookie
(空档案停在千牛登录页时罐里就已经有 cookie2/_tb_token_);1688 的 __cn_logon__ 未登录时值是
false,所以卡值 —— 只有 =true 才算数。
- URL 弱判据:只有 CDP 读 cookie 失败时才退到它,并如实标成
evidence: 'url'(界面上与「确定」区分)。
静默检测为什么要「等导航稳」
浏览器没开时检测会用无头 Chrome 打开同一份档案探一次。这里有坑(v0.3.0 的真 bug,v0.3.1 修):
把工作台 URL 当启动参数丢给 Chrome,页面目标一上来就停在工作台 URL 上(+377ms 就能看到,CDP +358ms 起来),
而站点甩到登录页是 +632ms 的事。旧实现「看到平台域名就算稳」→ 判的是工作台 URL + 罐里还没被清掉的
PASS_ID/SUB_PASS_ID → 掉线了也报「已登录」;开着窗口时靠实时读 URL 还能发现,关着窗口就永远发现不了。
现在 settlePage() 只认「导航真正结束」:document.readyState === 'complete' 且 URL 连续 1.5s 不变
(资源一直不结束的站点,URL 稳定 4s 也认;总上限 12s),判据用的就是等稳后的那个 URL。
test/live-probe.mjs 用本地页复现了同一时序(先渲染"工作台",400ms 后 JS 跳 /login)把这条回归钉住。
支持的平台
| 平台 | 登录页(新增店铺时打开) | 「打开」落到的工作台 | 登录态认哪个 cookie |
|---|
| 拼多多 | mms.pinduoduo.com/login | 商家后台 / 商品列表 | PASS_ID / SUB_PASS_ID |
| 淘宝 | loginmyseller.taobao.com(千牛) | qn.taobao.com/home.htm | unb / sgcookie(必须命中其一) |
| 天猫 | loginmyseller.taobao.com(千牛) | myseller.taobao.com/home.htm | 同淘宝(一套账号体系) |
| 抖音 | 抖店 fxg.jinritemai.com/login/common | fxg.jinritemai.com/ffa/mshop/homepage/index | passport_assist_user / sessionid / sessionid_ss / passport_uid |
| 京东 | 京麦 passport.shop.jd.com/login/index.action | jm.jd.com/index/index.html | pt_key / pt_pin |
| 1688 | login.1688.com/member/signin.htm | work.1688.com/home/page/index.htm | _nk_ / __cn_logon__=true |
加平台只需在 lib/platforms.mjs 里补一条记录,再跑 node test/live-probe.mjs <新id> 确认空档案登录页
不会被误判成「已登录」。
和上架流程怎么接
上架脚本(例如拼多多发品流程)复用某个账号时,要对上两样:
- 调试端口:行内「调试端口」列显示的那个(脚本侧就是
PDD_PORT 之类);
- 浏览器档案:行内「复制路径」复制到的目录(脚本侧
-Profile <档案目录> 或 chrome_profile)。
行内显示 CDP 可用 说明这家店浏览器正开着 —— 上架脚本直接连这个端口,不要再另起一个 Chrome
(同一份档案被两个实例抢会报错,也不符合「复用已登录 profile」的要求)。
版本与更新
「浏览器设置」里的 版本与更新 卡片:
- 当前版本:现在跑着的这份代码的版本号。
- 更新源目录:本插件的 git 源码仓库(留空 = 自动猜:自己在 git 仓库里就用自己;否则从 profile 的
package.json 里 "dsh-plugin-shop-accounts": "file:D:/..." 反推)。
- 检测更新:
git fetch 后比较三样 —— 跑着的版本 / 源码仓库当前提交的版本 / GitHub origin/main 的版本,
落后几个提交也会显示。只查询,不动文件。
- 更新到最新:
git merge --ff-only origin/main(有未提交改动会拒绝,不替你提交),再把
lib/ package.json README.md test/ 覆盖到 profile 里的安装副本。
只有检测出确实有新版本时才可点 —— 没检测过、检测失败、已是最新,它都是灰的。
覆盖完必须重启 DSH Desktop:宿主启动时就把插件代码读进内存了,换文件不会热更新。
安全边界:只跑 git -C <更新源目录> fetch/merge 和本地文件拷贝,不执行任何远端代码。
数据放在哪
| 东西 | 位置 |
|---|
| 店铺清单 / 配置 | %DSH_HOME%\shop-accounts\accounts.json、config.json |
| 登录态(cookie 等) | 每店一份 Chrome user-data-dir:%DSH_HOME%\shop-accounts\profiles\<账号id>\ |
| 运行期登记(哪个端口起的浏览器) | %DSH_HOME%\shop-accounts\runtime.json |
| 调试端口 | 从 portBase(默认 9222)起自动找空位,一店一端口 |
整个数据目录可以用环境变量 DSH_SHOP_ACCOUNTS_DIR 搬走。
目录结构
lib/platforms.mjs 六个平台的登录页/主页/工作页、身份 cookie、域判据、判登录态
lib/cdp.mjs 极简 CDP 客户端(裸 WebSocket,无第三方依赖)
lib/chrome.mjs 找浏览器、选端口、拉起/关闭/探测
lib/store.mjs accounts.json / config.json / runtime.json 的原子读写与降级
lib/schedule.mjs 排期算法(纯函数:分组、时间点、到点、下次、记账)
lib/index.js 宿主:/shop-accounts/* 路由、自更新、定时心跳
lib/client.js 客户端 bundle:设置页「账号管理」整页 UI
cordis.patch.yml 插件挂载点(id: shop-accounts)
test/smoke.mjs 自检(零依赖、不启动浏览器)
test/live-probe.mjs 真机链路探针(会拉起一个无头 Chrome,临时档案)
自检
node test/smoke.mjs # 47 项:平台判据 / 持久化 / 宿主 HTTP 面 / 排期算法 / 客户端样式与渲染契约
node test/live-probe.mjs pdd # 真机链路:临时档案拉一个无头 Chrome,读 cookie 再关掉 + 「慢跳转」回归
node test/live-probe.mjs all # 六个平台都过一遍(建议一个一个跑,共用档案会串 cookie)
smoke.mjs 用 DSH_SHOP_ACCOUNTS_DIR 把数据目录指到临时目录,不碰真实账号、不启动任何浏览器。
live-probe.mjs 会真的拉起无头 Chrome(临时档案、不弹窗口、不碰店铺档案),验证
「启动 → 调试端口 → Network.getAllCookies(HttpOnly 的唯一正路)→ 正常关闭」这条链路。
已知边界
- 不代登录、不破验证码:扫码/短信/滑块都由你在真实窗口里完成。
- 一份 profile 同一时间只能被一个 Chrome 实例占用(插件先探测端口,不会硬起第二个)。
- 不做多账号并发抢端口:端口按店铺固定分配,冲突时报错而不是静默换端口。
- 只支持上面六家平台(都是商家端);上架/发品页等有真实账号登录后再钉准,不猜地址。
- 定时检测排期跑在 DSH 宿主进程里:DSH 可以最小化,但退出 DSH 就不跑了。
- 无头检测的 UA 是
HeadlessChrome,理论上会被站点识别(目前六家实测正常);真要绕再单独改。
许可
MIT