目标:采集得卡「商家展位列表 / 在售商品列表」,供业务侧统计 新增 / 商家账号 / 商品售卖进度。 核心难点:所有接口带
signature请求头签名校验,部分接口需 JWT 登录态。 结论:signature 已 100% 还原(无加壳、纯 Java),登录+续签闭环打通,可长期无人值守。 日期:2026/08/02 | Python 3.12.10
得卡DECA_1.0.0.apk(appVersion 1.0.0)https://api.decalive.comUser-Agent: okhttp/5.3.2)signature(32 位十六进制,双重 MD5)+ currentTime(13 位毫秒时间戳)。Authorization: Bearer <JWT>,access token 有效期 15 分钟。| 用途 | 方法 & 路径 | 是否需登录 | 请求体 |
|---|---|---|---|
| 商家展位列表 | POST /api/v1/app/merchant/exhibition/list |
否 | {"page":N} |
| 在售商品列表 | POST /api/v1/app/groupbuy/merchant/on-sale-list |
是 | {"merchantUserId","page","pageSize":20} |
| 密码登录 | POST /api/v1/app/auth/password/login |
否 | {"countryCode","password","phone"} |
| Token 续签 | POST /api/v1/app/auth/token/refresh |
否 | {"refreshToken"} |
signature/currentTime/body 样本,用于最终验证)。--no-res),产物 16044 个 java 文件,classes.dex/classes2.dex 均为真实业务码,无壳。mi/w.java(组装全部公共头 + signature)顺藤摸瓜到签名计算核心 mf/c0.java。| 类 / 方法 | 作用 |
|---|---|
mi.w.f(Context, url) |
头部装配器,组装公共头 + signature |
mf.c0.a(Map) |
签名计算核心:生成 currentTime + 双重 MD5 |
mf.c0.b(String) |
MD5 → 小写 hex |
mf.b0 |
每个参数 entry 渲染为 key+value(无分隔符)的 lambda |
eg.p |
Retrofit 认证接口(login / sms / wechat / aliyun / token refresh) |
mf/c0.a() 读出签名逻辑——参数排序 + 双重 MD5 + 固定 salt,salt 是明文常量。= 无分隔符、只取时间戳后 6 位)。eg/*.java 发现 token/refresh + refreshToken 存储 → 续签闭环成立。plain = 参数按 key 升序,每项 "key"+"value" 直接拼接(无分隔符),剔除 value 为 null 的项
inner = md5(plain) # 32 位小写 hex
signature = md5( inner + currentTime[-6:] + "biu_card_nbclass" )
mf/c0.java):biu_card_nbclasscurrentTime:13 位毫秒时间戳,同一个值同时放进 currentTime 头;签名只用其后 6 位。plain="" → inner=md5(""),无特殊分支。import hashlib, time
SALT = "biu_card_nbclass"
def md5(s): return hashlib.md5(s.encode("utf-8")).hexdigest()
def make_signature(params: dict, current_time: str) -> str:
clean = {k: v for k, v in params.items() if v is not None}
plain = "".join(f"{k}{clean[k]}" for k in sorted(clean))
return md5(md5(plain) + current_time[-6:] + SALT)
# current_time = str(int(time.time()*1000)),同一值放进 currentTime 头
| plain | currentTime | signature |
|---|---|---|
page1 |
1785662750434 | c0670b1219 |
page2 |
1785662751198 | 0dea97b713 |
merchantUserId593731690page1pageSize20 |
1785664667848 | 5416ca7036 |
countryCode86passwordpass2022phone19521500850 |
1785665137653 | 81464e9bf4 |
password/login 响应 data:accessToken / refreshToken / expiresIn(秒,900=15分钟) / userId 等。token/refresh body {"refreshToken":...},返回同结构新 token。Content-Length:10 对应 {"page":1}(无空格)。发送用 json.dumps(..., separators=(",",":")),否则 Content-Length 与实际不符。签名本身只用参数串,与 body 序列化格式无关。UnicodeEncodeError。运行加 PYTHONIOENCODING=utf-8 PYTHONUTF8=1,日志文件本身写 UTF-8 无碍。100.64.0.25 需网络/白名单就绪,TCP 通但 MySQL 握手断时,先排查 VPN/授权白名单,非代码问题。jadx-ai-mcp 插件会导致 jadx 启动崩溃,反编译前需临时清空插件配置。activeGroupbuyCount=0:即当前无在售,属正常;采商品时对每个商家都会发一次接口确认。wxauto 已下架:PyPI 上原 wxauto 已被作者移除(pip install wxauto 报 from versions: none),现改推 wxauto4(对应微信 4.0 客户端,免费)/ wxautox4(付费 plus)。安装 pip install wxauto4;import 名即 wxauto4,用 WeChat().SendFiles(filepath, who, exact) 发文件。若 PC 微信仍是 3.9 旧版,则改用 wxautox。发送依赖微信窗口可见的 UI 自动化,无人值守时需保证微信一直登录、窗口不最小化。sign = hash( 排序参数串 + 时间戳(部分位) + 固定 salt ) 是移动端最常见范式。定位思路统一:先找组装请求头的拦截器/装配器,再找里面调用的 hash 工具方法,salt 常为明文常量。mf.b0.a 声明抛 UnsupportedEncodingException,暗示内部可能对 value 做 URLEncoder.encode(v,"UTF-8")(空格→+)。当前样本均无特殊字符无法区分;若日后遇到搜索关键词等含空格/中文的参数,需对 value 先编码再拼。c0 会把嵌套对象递归转 TreeMap、数组保序后按 toString 拼接。当前接口均为扁平 body,遇到嵌套 body 时需另取样本校准渲染格式。| 文件 | 说明 |
|---|---|
deca_daily_spider.py |
常驻每日爬虫(登录→商家→在售商品+每日快照;采完自动出图并发微信) |
deca_report.py |
统计报表出图(KPI + 今日新增商家 + 各商家在售/售卖进度),生成 reports/ 下 PNG,并按开关自动发微信 |
deca_wechat.py |
通过 wxauto4 把图片发到 PC 微信好友/群 |
schema.sql |
三张表 DDL:deca_shop_record / deca_product_record / deca_product_daily_record |
stats.sql |
统计查询 SQL(新增 / 商家账号 / 售卖进度趋势) |
init_db.py |
一键建表 |
requirements.txt |
依赖清单(版本实测) |
部署:
pip install -r requirements.txt(wxauto4 若受 requires-python 限制加 --ignore-requires-python);本地公共库 charley-utils 需 editable 安装(见 requirements 尾注)。python init_db.pypython deca_daily_spider.py(每天 09:00 / 15:00 及启动各采集一轮,采完自动生成报表并发微信)。报表与微信推送:
reports/deca_report_YYYYMMDD_HHMMSS.png,命名到秒,一天多张互不覆盖。deca_report.WECHAT_TO(默认 backup)。python deca_wechat.py "reports/xxx.png" backup。deca_report.SEND_WECHAT 置为 False。登录与 token(避开验证码):
password/login 受阿里云验证码2.0 风控,脚本无法自动过验证码;改走 token/refresh 续期(无验证码)。refreshToken 粘入项目根目录 token.json 的 refresh 字段:{"access":null,"refresh":"<粘这里>","exp":0}。token.json;refreshToken 失效(过期/被踢)时日志会报警,需重新抓包更新。POST api/v1/app/groupbuy/detail(body {"code":商品code},需 token),仅对今日新增商品拉取,存入 deca_product_record 的 publish_at/sale_start_at/sale_end_at。商家入驻时间接口无,新增按入库时间判定。