落地日期:2026/09/09 | 运行环境:Python 3.12.10 代码位置:
common/account_pool.py(账号池)、common/deca_account_ddl.sql(建表)、common/deca_sold_core.py(采集核心 + step3 接入) 事实台账:HANDOFF.md(新窗口接手先读它,进度以其为准)
原采集把所有登录态集中在单账号 + 单 token.json:一个得卡账号扛全部带 token 请求(约 4 万/天,几乎全压夜间 20:00~06:00)。一旦这个账号被风控击穿,整条采集链全停(2026/09/07 即因此停摆)。
账号池目标:多账号入池、按任务随机取号、异常自动切号、把请求量摊平到多账号 + 各账号专属静态 IP,从根上消除「单账号单点」风险,降低单号风控面。
采集脚本(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 实例,进程内单例。deca_account_recordDDL 见 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:状态机与排障字段。得卡无独立注册接口——新手机号首次短信验证码登录即自动开户。故供给(注册新号 / 补货已死号)统一走短信登录。
接口(抓包 抓包.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()保留作极端兜底,线上默认不调(撞阿里云滑块,无人值守过不去)。短信登录不撞滑块,是补货/开户主路径。
acquire_random(lease_sec=300):从 healthy 池 RAND() 随机挑一个未被占的号,带「仍空闲」条件 UPDATE 认领(防并发抢占),打短租约。每批随机主路径。acquire():进程级独占(按 last_used_at 升序负载均衡),整运行期独占一个号。保留可选。switch() / release():换号 / 归还租约。为什么每批随机:把带 token 请求量摊平到所有号,单号日请求量大降、风控面更小。只要每个号的请求始终走它自己的 proxy_url,「账号↔IP 绑定」不破——随机的是「派哪个号上」,不是「一个号在多 IP 间跳」。
短租约的作用:批期间对该号的续期串行化,避免多进程同时选中同号并发踩废「一次性轮换」的 refreshToken。
ensure_access(log):access 未临期直接返回缓存;临期用该号 refresh 续签并回写 DB(access/refresh/token_exp/fail_count)。线上只 refresh、不自动密码登录。.lock 串行化;账号池每号独立,靠短租约 + acquire_random 抢占避免并发。refresh 失效时服务端返回:
code = 10010
msg = 当前账号长期未登录,请重新登录
涵盖「refresh 被轮换 / 过期 / 长期未登录」。写进 account_pool.DEAD_CODES = {10010}:
ensure_access 续签命中 10010 → report_failure(dead=True),直接判 dead 摘池(线上不自动登录);MAX_FAIL(3) 转 cooling。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 开头加一行:
core.init_account_pool(pool, task_tag="buy_record:881226408") # 不加则维持老单 token.json
核心原则:对得卡(国内卡片交易平台,用户全在国内),IP 地理位置(国内)比 IP 类型(住宅 vs 机房)更重要——异地(尤其海外)IP 是最强风控信号之一。
| 候选 | 地理 | 类型 | 独占性 | 对得卡 |
|---|---|---|---|---|
123proxy 北美(实测 76.46.19.42 美国·辛辛那提·Charter 住宅) |
🔴 美国 | 🟢 住宅 | 独享 | 异地风控高危 + 延迟 3.9s,不推荐 |
| 快代理独享版 | 🟢 国内 | 🟡 机房 | 完全独享 | 地理对、类型略可疑,可用 |
| 快代理共享版 | 🟢 国内 | 🟡 机房 | 与少数客户共用 | 有连坐风险,概率可控 |
| 理想 | 🟢 国内 | 🟢 住宅/静态 | 独享 | 最优 |
决策进展:先买快代理共享版 1 个月试(成本低,稳则用、不稳再上独享池)。
proxy_url 跑几天,观察 ①得卡认不认该 IP ②号会不会判死(10010=连坐信号);稳则扩量。账号池巡检可用号数,不足阈值时全自动经接码平台注册补充(登录即注册),无需人工收码。
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。
send/login 两样本哈希与 make_signature 完全命中。sms_send→sms_login(入库)→acquire_random→ensure_access(续签 + refresh 正确轮换 + 回写 DB)→release。10010 实测确认(refresh 失效返回)。do_request(need_auth=True) 取商家列表,code=0、10 条。17047146344 → 得卡发码 → 接码收到验证码短信 → sms_login 注册入库 + 绑 IP。接码「优豪卡」号确实能收得卡短信。common/settings.py;一键补号脚本 spiders/replenish_accounts.py(--target/--batch);auto_replenish 带连续失败熔断(5 次)。TRUNCATE 重置后 17047146344 为 id=1。deca_account_record id=1 的 proxy_url,跑几天观察得卡认不认、是否判死。sms_send + 收码 + sms_login 入库 + 绑各自 proxy_url(1 号 1 IP,或自己独享 IP 低密度多号 1:2~3)。core.init_account_pool(pool, task_tag=...);小流量观察。buy_record_spider 支持按商家分片 + 错相位(待办)。token.json/token.json.lock 及 core 里基于 CWD 读 token.json 的逻辑。py_compile,不真跑真实生产账号的续期——否则会作废旧目录任务正在用的 refresh(得卡 refresh 一次性轮换)。测试号 19521500850 是独立号,用它真跑与旧目录零冲突。