|
|
2 hete | |
|---|---|---|
| .. | ||
| README.md | 2 hete | |
| YamlLoader.py | 1 hónapja | |
| application.yml | 1 hónapja | |
| deca_on_sale_daily_spider.py | 1 hónapja | |
| deca_on_sale_report.py | 2 hete | |
| deca_wechat.py | 1 hónapja | |
| km_spdier.py | 3 hete | |
| mysql_pool.py | 1 hónapja | |
| requirements.txt | 1 hónapja | |
目标:采集得卡「商家展位列表 / 在售商品列表」,供业务侧统计 新增 / 商家账号 / 商品售卖进度。 核心难点:所有接口带
signature请求头签名校验,部分接口需 JWT 登录态。 结论:signature 已 100% 还原(无加壳、纯 Java),登录+续签闭环打通,可长期无人值守。 日期:2026/08/02 | Python 3.12.10⚠️ 2026/09/05 接口变更:站方把免 token 的全站在售
home/search砍成「首屏推荐」(只返前 20 条、page≥2空、pageSize>20报10001),merchant/exhibition/list商家列表也改为需 token 且只返首屏 30 家。 影响:原「空 query 翻页拿全量在售」失效,在售全量只能靠逐商家groupbuy/merchant/on-sale-list(需 token)枚举(配合库内已存商家 ID);groupbuy/detail详情仍免 token。 据此改为一机一账号部署:购买记录(服务器 B,账号18040545025)、提醒+在售报告(电脑 C,账号18086100242)、已售+随机团(电脑 A,主号不动)。token.json现支持内嵌phone/password/countryCode,密码登录兜底按本机账号走、互不顶号。详见根README.md第一节与项目记忆。
得卡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 |
是(2026/09/05 起需 token,且只返首屏 30 家) | {"page":N} |
| 在售商品列表 | POST /api/v1/app/groupbuy/merchant/on-sale-list |
是(在售全量唯一入口) | {"merchantUserId","page","pageSize":20} |
| 全站在售(首屏) | POST /api/v1/app/home/search |
否(2026/09/05 起被砍成首屏 20、不可翻页,已弃用于全量采集) | {"query":"","saleStatus":0,"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。2026/09/05 起推荐直接写本机账号凭据,程序首次会自动密码登录补 access/refresh:{"phone":"18040545025","password":"147258369@ww","countryCode":"86"}(deca_sold_core.load_token/save_token/login 会读取并保留这些字段,兜底登录按本机账号走、便于人工分辨该机用哪个号)。旧格式 {"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。商家入驻时间接口无,新增按入库时间判定。