案例地址:土地市场网
郑重声明:本文只做学习研究分享,若有人用文章技术做出不法行为与本人无关,如有侵权联系作者立即删除
找加密参数
打开供应计划页面,F12 抓包,数据是这个接口给的:
POST https://api.landchina.com/tGyjh/plan/list
请求头里除了正常的 UA、Referer,还多了一个 Hash,值是 64 个十六进制字符。64 位正好是 SHA256 的长度,先记下这个直觉------目标就是这个 Hash 怎么来的。
定位加密位置
给这个接口下个断点(Sources 里勾 XHR/Fetch 断点,填 plan/list),刷新页面,请求会停在"发出去之前"。看调用栈:
0. <anonymous> chunk-vendors...js
1. xhr chunk-vendors...js
2. er chunk-vendors...js
栈顶全是 chunk-vendors 里的东西,也就是 axios 自己发请求的那段代码。这说明 Hash 不是在这一步算的,而是在请求真正发出之前,就被 axios 的"请求拦截器"塞进 headers 了。所以别在栈里硬跟,直接去拦截器找。
拦截器既然跟 UA 有关,那它一定会读 userAgent。全局搜 userAgent,在 app.js 里命中这段:
let o = n(2966), a = e.url.split("/");
console.log(navigator.userAgent + (new Date).getDate() + a[a.length - 1]);
t["Hash"] = o(navigator.userAgent + (new Date).getDate() + a[a.length - 1]);
翻译成人话:
a = e.url.split("/")把接口地址按/切开,a[a.length-1]就是最后一段,这里是list;(new Date).getDate()是今天的"几号"(1~31,没有前导零);- 把 UA + 几号 + list 三段拼一起当"种子",交给
o(n(2966)就是 crypto-js 里的 SHA256)算一下,结果塞进请求头Hash。
顺便说一句,开发者忘了删那句 console.log(种子)------等于把答案直接打印给你看。这种漏网细节遇到了别客气,顺着它验证最省事。
复现加密逻辑
先别信它一面之词,本地按这个规则拼种子算一遍,跟抓包看到的 Hash 对一下:
import hashlib
UA = "Mozilla/5.0 ... Chrome/155.0.0.0 Safari/537.36"
seed = UA + "21" + "list" # 今天 21 号
print(hashlib.sha256(seed.encode()).hexdigest())
# 182aafdba960ff4188487bf65b633f110baef14adf9dcab07249e17b9c2c6dc7
跟请求头里的 Hash 一毛一样,标准 SHA256,确认无误。完整复现:
import hashlib
from datetime import datetime
import requests
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36")
API_URL = "https://api.landchina.com/tGyjh/plan/list"
def build_hash(url):
# UA + 当日几号 + URL末段 → SHA256
seed = UA + str(datetime.now().day) + url.split("/")[-1]
return hashlib.sha256(seed.encode("utf-8")).hexdigest()
def fetch_plan_list(page_num=1, page_size=10):
headers = {"Hash": build_hash(API_URL),
"Origin": "https://www.landchina.com",
"Referer": "https://www.landchina.com/",
"User-Agent": UA}
payload = {"pageNum": page_num, "pageSize": page_size, "startDate": "", "endDate": ""}
r = requests.post(API_URL, headers=headers, json=payload, timeout=10)
r.raise_for_status()
return r.json()
if __name__ == '__main__':
data = fetch_plan_list(page_num=2, page_size=10)
body = data.get("data") or {}
print("code:", data.get("code"), "| msg:", data.get("msg"))
print("总数:", body.get("total"), "| 本页:", len(body.get("list") or []))
for item in (body.get("list") or [])[:3]:
print(" -", item.get("biaoti"), "|", item.get("xzqFullName"))
结果
本机刚跑的:
code: 200 | msg: 操作成功
总数: 1414 | 本页: 10
- 法库县2026年度国有建设用地供应计划 | 辽宁省沈阳市法库县
- 沈阳市2026年度国有建设用地供应计划 | 辽宁省沈阳市本级
- 康平县2026年度国有建设用地供应计划 | 辽宁省沈阳市康平县
踩坑
- UA 必须和你请求头里真正发出去的那串一字不差,差一个字符 Hash 就对不上;
- 日期只取"几号"(1~31),不是完整日期,而且用的是电脑本地时间,过了零点或换了时区会变;
- 最后一段是
list,换别的接口时把新地址传进build_hash就行,规则不用改。
小结
这篇不难,就是个"请求头藏了个 SHA256"。套路记住两点:看到请求头里莫名多出一串固定长度的十六进制,先猜 hash(UA + 时间 + 接口名) 这种拼接;跟栈发现栈顶都是 axios 自己的代码时,别硬跟,去请求拦截器里找。如有问题请及时提出,加油加油。