# 得卡 DECA 账号池落地文档 > 落地日期:2026/09/09 | 运行环境:Python 3.12.10 > 代码位置:`common/account_pool.py`(账号池)、`common/deca_account_ddl.sql`(建表)、`common/deca_sold_core.py`(采集核心 + step3 接入) > 事实台账:`HANDOFF.md`(新窗口接手先读它,进度以其为准) --- ## 1. 背景与目标 原采集把所有登录态集中在**单账号 + 单 `token.json`**:一个得卡账号扛全部带 token 请求(约 4 万/天,几乎全压夜间 20:00~06:00)。一旦这个账号被风控击穿,整条采集链全停(2026/09/07 即因此停摆)。 账号池目标:**多账号入池、按任务随机取号、异常自动切号、把请求量摊平到多账号 + 各账号专属静态 IP**,从根上消除「单账号单点」风险,降低单号风控面。 --- ## 2. 整体架构 ``` 采集脚本(buy_record/sold/team/onsale_alert/在售报告) │ do_request(need_auth=True) ▼ deca_sold_core(签名 / 请求 / step3 账号池接入) │ USE_ACCOUNT_POOL=True 时 ▼ AccountPool(common/account_pool.py) │ acquire_random → ensure_access → 该号专属 proxy_url ▼ deca_account_record 表(多账号:token / 状态 / 专属 IP / 租约) ``` - **默认关**:`USE_ACCOUNT_POOL=False`,采集走老单 `token.json`;采集入口调 `core.init_account_pool(pool)` 才切到账号池。 - 每个常驻任务进程持一个 `AccountPool` 实例,进程内单例。 --- ## 3. 数据模型:`deca_account_record` DDL 见 `common/deca_account_ddl.sql`,已在 `100.64.0.25/crawler` 建好(20 列)。要点: - 自增 `id` 物理主键;`phone` 单独建 `UNIQUE`(业务唯一标识);时间字段固定 `gmt_create_time`/`gmt_modified_time`。 - 每号独立 `access_token`/`refresh_token`/`token_exp`。 - `proxy_url`:该号绑定的**专属静态出口 IP**(1 号 1 IP 永久绑定,必须静态独享,勿用隧道)。 - `status` 三态:`healthy`(可用)/ `cooling`(软风控冷却)/ `dead`(登录态死,需人工补货)。 - `owner_pid`/`lease_until`:进程独占租约(进程崩溃后租约到期自动可再租,不死占)。 - `fail_count`/`cooldown_until`/`last_used_at`/`last_success_at`/`last_error`:状态机与排障字段。 --- ## 4. 账号供给:短信验证码登录 =「登录即注册」 **得卡无独立注册接口**——新手机号首次短信验证码登录即自动开户。故供给(注册新号 / 补货已死号)统一走短信登录。 接口(抓包 `抓包.txt`,签名与 `make_signature` 一致,已核验命中): | 接口 | 路径 | body | 说明 | |---|---|---|---| | 发验证码 | `/api/v1/app/auth/sms/send` | `{countryCode, phone}` | `need_auth=False` | | 短信登录 | `/api/v1/app/auth/sms/login` | `{countryCode, phone, code}` | `need_auth=False`,返回 accessToken/refreshToken/expiresIn/userId | `AccountPool` 方法: - `sms_send(phone)`:发验证码(登录/注册第一步)。 - `sms_login(phone, code, proxy_url=None, password=None)`:短信登录并入库。新号 `INSERT`、已存在号(补货)`UPDATE`(`ON DUPLICATE KEY`,`proxy_url` 用 `COALESCE` 不覆盖已绑定 IP),成功即置 `healthy`、清租约。 - `register(phone, code, ...)`:等价 `sms_login`(两步:先 `sms_send` 收码,再带 code 调用)。 > 密码登录 `_login()` 保留作极端兜底,**线上默认不调**(撞阿里云滑块,无人值守过不去)。短信登录不撞滑块,是补货/开户主路径。 --- ## 5. 取号策略:每批随机(默认)+ 独占(保留) - `acquire_random(lease_sec=300)`:从 `healthy` 池 `RAND()` 随机挑一个未被占的号,带「仍空闲」条件 `UPDATE` 认领(防并发抢占),打**短租约**。**每批随机**主路径。 - `acquire()`:进程级独占(按 `last_used_at` 升序负载均衡),整运行期独占一个号。保留可选。 - `switch()` / `release()`:换号 / 归还租约。 **为什么每批随机**:把带 token 请求量摊平到所有号,单号日请求量大降、风控面更小。只要**每个号的请求始终走它自己的 `proxy_url`**,「账号↔IP 绑定」不破——随机的是「派哪个号上」,不是「一个号在多 IP 间跳」。 **短租约的作用**:批期间对该号的续期串行化,避免多进程同时选中同号并发踩废「一次性轮换」的 `refreshToken`。 --- ## 6. Token 续期与判死(实测) - `ensure_access(log)`:access 未临期直接返回缓存;临期用该号 `refresh` 续签并回写 DB(access/refresh/token_exp/fail_count)。**线上只 refresh、不自动密码登录**。 - **续期一次性轮换**:续签成功即发新 refresh、旧的立即作废。多进程共用 token.json 时代靠 `.lock` 串行化;账号池每号独立,靠短租约 + `acquire_random` 抢占避免并发。 ### 判死码(2026/09/09 实测,测试号 19521500850 直连) refresh 失效时服务端返回: ``` code = 10010 msg = 当前账号长期未登录,请重新登录 ``` 涵盖「refresh 被轮换 / 过期 / 长期未登录」。写进 `account_pool.DEAD_CODES = {10010}`: - `ensure_access` 续签命中 `10010` → `report_failure(dead=True)`,直接判 `dead` 摘池(线上不自动登录); - 其他未知码 → 按软失败累计(可能限流/网络,不急着判死),达 `MAX_FAIL(3)` 转 `cooling`。 --- ## 7. 保活决策:不加主动保活(2026/09/09 定) - **关键区分**:access 15 分钟过期,用时续签即可;真正决定账号存活的是 refresh 会不会因「长期不用」失效。 - **依据**:既往经验——sold 任务每天仅跑 1 次(间隔 ~24h)一直能正常续签 → refresh 失效阈值 **> 24h**(很可能数天)。 - **结论**:随机取号下只要一个号「至少每天被用到一次」就不会饿死;20 号 + 每批随机,单号日内被选中概率高。**不加定时保活**,靠既有容错兜底:**用时续签 + 续签命中 10010 自动判 dead 切号 + 企微通知人工补货**。比定时保活更省更简单。 --- ## 8. step3:采集接入(`deca_sold_core` core 侧改造) 把 `need_auth` 请求的取 token 从「单 token.json」切到「账号池」。**2026/09/09 完成并端到端验证通过**(测试号跑通商家列表 code=0)。 新增函数(`deca_sold_core.py`): - `init_account_pool(pool, task_tag="core", rotate_every=20)`:**启用入口**,采集脚本启动调一次,置 `USE_ACCOUNT_POOL=True`。 - `rotate_account(log)`:批边界主动随机换号(如每商家开始)。 - `release_account()`:归还当前号。 - `_pool_auth_and_proxy(log)`:取「当前号有效 access + 该号专属 proxies」,每 `rotate_every` 个请求自动随机换号,续签失败自动换号重试。 `do_request`/`do_get` 的 `need_auth` 分支:启用后走账号池,请求走该号**专属 `proxy_url`**(为空=直连,IP 填了自动生效);未启用走老 `ensure_token`。 **每批随机的实现**:默认每 `rotate_every=20` 个 `need_auth` 请求自动随机换一次号,**无需改采集脚本**;也可在明确批边界调 `rotate_account()` 强制换。 ### 采集脚本如何启用(上线时做) 在各采集入口 `main` 开头加一行: ```python core.init_account_pool(pool, task_tag="buy_record:881226408") # 不加则维持老单 token.json ``` --- ## 9. 长效 IP 选型(2026/09/09 实测) **核心原则**:对得卡(国内卡片交易平台,用户全在国内),**IP 地理位置(国内)比 IP 类型(住宅 vs 机房)更重要**——异地(尤其海外)IP 是最强风控信号之一。 | 候选 | 地理 | 类型 | 独占性 | 对得卡 | |---|---|---|---|---| | 123proxy 北美(实测 `76.46.19.42` 美国·辛辛那提·Charter 住宅) | 🔴 美国 | 🟢 住宅 | 独享 | 异地风控高危 + 延迟 3.9s,**不推荐** | | 快代理独享版 | 🟢 国内 | 🟡 机房 | 完全独享 | 地理对、类型略可疑,可用 | | 快代理共享版 | 🟢 国内 | 🟡 机房 | 与少数客户共用 | 有连坐风险,概率可控 | | 理想 | 🟢 国内 | 🟢 住宅/静态 | 独享 | 最优 | **决策进展**:先买快代理**共享版 1 个月**试(成本低,稳则用、不稳再上独享池)。 - ⚠️ **购买前必须向客服确认「共享版分配的 IP 是固定不变的吗」**——固定才能绑定;动态轮换(≈隧道)不可用。 - 落地:拿到 IP 后**先只绑测试号 id=1 的 `proxy_url` 跑几天**,观察 ①得卡认不认该 IP ②号会不会判死(10010=连坐信号);稳则扩量。 - 持续监控信号:**「同一 IP 上多个号集体判死」= 该 IP 被关联**,立即换独享池。 --- ## 9.5 自动补货(接码平台爱接码 i-sms.app,框架已落地) 账号池巡检可用号数,不足阈值时**全自动经接码平台注册补充**(登录即注册),无需人工收码。 - **接码平台**:爱接码 `https://www.i-sms.app`,鉴权 `X-API-KEY`(环境变量 `ISMS_API_KEY`)。 - **客户端**:`common/isms_client.py`(`search_projects`/`get_number`/`get_sms`/`poll_sms`/`release_number`/`get_user_info`)。 - **补货方法**(`AccountPool`): - `count_healthy()`:统计可用号数。 - `register_one_via_isms(isms, project, proxy_url, ...)`:单号全自动注册(接码取号→得卡 `sms_send`→接码 `poll_sms` 收码→得卡 `sms_login` 入库→`release_number`)。 - `auto_replenish(target_min, target_max, free_proxy_urls, keyword, ascription, api_key)`:巡检补货,`healthy < target_min` 时补到 `target_max`;每号随机停顿 30~90s 错峰防连坐。 **补货流程**: ``` search_projects("得卡") → 选项目 get_number(项目, ascription=2实体卡) → 手机号 + orderId [得卡] sms_send(手机号) → 得卡发验证码 poll_sms(orderId) 轮询(每5s,≤180s) → 收到验证码 [得卡] sms_login(手机号, 验证码, proxy_url) → 入库 healthy release_number(orderId) ``` **配置(2026/09/09)**:`POOL_TARGET_SIZE=20`(池子目标 20 个账号);`ISMS_ASCRIPTION=None`(卡类型**不限制**,`isms_client` 会剔除 None 参数不发送,由平台按可用号分配);每号随机停顿 30~90s 错峰,避免一批新号被批量连坐识别。 **API Key**:`ISMS_API_KEY` 常量写在 `common/account_pool.py`(主公定放代码,非 git 仓库);敏感,勿外传/勿提交公开仓库。 **硬约束**:自动注册能出无限号,但**每号绑 1 个 IP,池子规模上限 = 买的 IP 数**(20 账号≈20 个 IP,或自己独享 IP 下 1 IP : 2~3 号低密度共用)。`auto_replenish` 的 `free_proxy_urls` 用尽即停。 **已实测确认(2026/09/09)**:得卡接码项目关键词 `ISMS_PROJECT_KEYWORD="优豪卡"`→项目「优豪卡科技」(`project_id=119481`);Key 有效、余额=3(补号需充值);`isms_client` 必带浏览器 UA(否则被爱接码 WAF 拦 403,已内置)。 **待资源才能真跑**:空闲专属静态 IP 列表(快代理共享版+静态型 20 IP,¥1000/月)填 `free_proxy_urls`。接口端点/参数码表见项目 memory `isms-api-reference`。 --- ## 10. 实测记录汇总(2026/09/09) - 短信登录接口签名 `send`/`login` 两样本哈希与 `make_signature` 完全命中。 - 短信登录=注册端到端跑通:`sms_send`→`sms_login`(入库)→`acquire_random`→`ensure_access`(续签 + refresh 正确轮换 + 回写 DB)→`release`。 - 判死码 `10010` 实测确认(refresh 失效返回)。 - step3 端到端跑通:启用账号池后 `do_request(need_auth=True)` 取商家列表,code=0、10 条。 - IP:123proxy 北美 IP 连通得卡但异地风控高危/延迟高,弃用。**快代理共享版+静态型 20 个国内 IP**实测得卡认(洛阳 IP 发 need_auth 请求 code=0)。 - **自动补货全链路试点成功**:接码取号 `17047146344` → 得卡发码 → 接码收到验证码短信 → `sms_login` 注册入库 + 绑 IP。接码「优豪卡」号确实能收得卡短信。 - 配置集中 `common/settings.py`;一键补号脚本 `spiders/replenish_accounts.py`(`--target`/`--batch`);`auto_replenish` 带连续失败熔断(5 次)。 - 账号池运维:测试号 19521500850 为主公自用(不进池);表 `TRUNCATE` 重置后 17047146344 为 id=1。 --- ## 11. 上线步骤(TODO,按顺序) 1. **买 IP**:快代理共享版 1 个月(先确认固定 IP)。 2. **绑测试号验证**:把 IP 填进 `deca_account_record` id=1 的 `proxy_url`,跑几天观察得卡认不认、是否判死。 3. **录号**:验证 OK 后,提供真实号,逐个 `sms_send` + 收码 + `sms_login` 入库 + 绑各自 `proxy_url`(1 号 1 IP,或自己独享 IP 低密度多号 1:2~3)。 4. **启用采集**:各采集入口加 `core.init_account_pool(pool, task_tag=...)`;小流量观察。 5. **购买记录分片**:`buy_record_spider` 支持按商家分片 + 错相位(待办)。 6. **清理**:账号池验证 OK 后清理 `token.json`/`token.json.lock` 及 core 里基于 CWD 读 token.json 的逻辑。 --- ## 12. 注意事项 / 踩坑 - **跨目录同账号踩废 token**:账号池落地前,新目录脚本只做 `py_compile`,不真跑真实生产账号的续期——否则会作废旧目录任务正在用的 refresh(得卡 refresh 一次性轮换)。测试号 19521500850 是独立号,用它真跑与旧目录零冲突。 - **proxy_url 必须静态独享**:隧道代理每次换 IP,破坏账号↔IP 绑定,等于白搭。 - **接码平台自动补货框架已落地**(见 9.5):既定方向是全自动注册补货。风控上务必用**实体卡**、错峰注册;受 IP 数量硬约束。主力可先用真实号跑通,资源齐了再启用巡检补货。 - **共享 IP 稳定性会漂移**:共用者变化会改变 IP 干净度,「今天稳」不代表长期稳,需持续监控集体判死信号。