当前位置: 首页 > news >正文

访问逻辑 - 推心置腹

网址:第三题 访问逻辑 推心置腹 - 猿人学爬虫逆向练习平台

Network 面板:请求都是从哪里发出来的

一、HTML 解析阶段(浏览器自动发,JS 还没运行)

浏览器解析 HTML 文档,读到标签就自动发起网络请求,此时 JS 脚本还没执行

<link href="xxx.css">

  • 类型:stylesheet
  • 触发:HTML 解析器 parser
  • 作用:下载 css 样式文件

<script src="a.js">

  • 类型:script
  • 触发:HTML 解析器 parser
  • 作用:下载 JS 脚本文件(只是把文件拿下来,还没执行里面代码)

<img src="1.jpg">、视频、字体、ico 图标

  • 类型:img/media/font/icon
  • 触发:HTML 解析器 parser

✅共同点:写在 HTML 标签里,浏览器解析 HTML 就自动请求,不是 JS 代码调用才发


二、JS 脚本运行阶段(代码执行时发出请求,逆向重点)

JS 代码跑起来之后,由脚本主动发起网络请求,来源标记都是 script

  1. jQuery:$.ajax()/$.get()/$.post()
  • Network 类型:XHR
  • 就是猿人学页面拿业务数据的接口
  1. 原生浏览器:fetch()
  • Network 类型:fetch
  1. 动态创建标签发起请求
const s = document.createElement('script'); s.src = "xxx.js"; document.body.appendChild(s);
  • 类型:script
  • 区别:不是 HTML 写死的 script 标签,是 JS 代码运行动态插入标签,才去下载 js
  1. 图片预加载、埋点上报:new Image ().src="xxx"
  • 类型:img,JS 代码触发

看【发起程序】面板:可以看到调用栈,追踪到具体是哪个 js 文件、第几行代码触发这个请求


三、其它类型

  1. document.cookie不会产生 network 请求,只是请求头自动带上,是 http 头字段,不是独立请求。
  2. websocket:类型ws,JS new WebSocket () 打开长连接。
  3. 文档主请求(页面本身):类型document,第一次访问页面的 html。
触发方式Network 类型是谁发起
HTML<link>加载 cssstylesheetHTML 解析器 parser
HTML<script src="">下载 js 文件scriptHTML 解析器 parser
JS 动态创建 script 标签加载 jsscriptJS 脚本运行
$.ajax / XMLHttpRequestXHRJS 脚本运行
fetch()fetchJS 脚本运行
<img>图片imgHTML 解析器 parser
JS new Image () 请求图片imgJS 脚本运行
页面 html 本体document浏览器地址栏 / 跳转

总结:

  • document:请求/match/3,拿到 HTML
  • parser 自动下载一批 css (stylesheet)、js (script) 静态资源
  • js 全部下载完毕,浏览器按顺序执行 js 代码
  • 执行混淆 JS,计算出m,赋值给document.cookie
  • 业务代码调用$.ajax(),发出 XHR 接口/api/question/3

业务源码

<script> (function ($) { // ==========全局常量配置========== var API="/api/question/3", // 真正拿业务数据的接口地址 TP=5, // totalPage 总页数,固定5页 PS=10, // pageSize 每页10条 p=1; // 当前页码,初始第1页 // jQuery元素缓存,提前拿到页面上DOM节点,避免反复查询DOM var $r=$("#pgxPager"), // 分页外层容器 $l=$("#pgxList"), // 数据列表容器 $pg=$("#pgxPages"), // 页码按钮容器 $pre=$("#pgxPrev"), // 上一页按钮 $nxt=$("#pgxNext"), // 下一页按钮 $kw=$("#pgxKw"), // 搜索输入框 $s=$("#pgxStatus"), // 状态文字(就绪/请求中/加载完成) $dbg=$("#pgxDebug"); // debug调试文本框,展示完整请求url /** * ui() 更新分页按钮状态 * 控制上一页、下一页是否禁用,高亮当前页码 */ function ui(){ // 第一页,禁用上一页;最后一页,禁用下一页 $pre.prop("disabled", p<=1); $nxt.prop("disabled", p>=TP); // 遍历页码按钮,给当前页加上高亮class $pg.find(".pgx-page").each(function(){ $(this).toggleClass("is-active", +$(this).data("page")===p); }); } /** * loading() 加载状态开关 * on=true:请求中,UI置灰,按钮全部禁用,防止重复点击 * on=false:结束请求,恢复页面交互 */ function loading(on){ $s.text(on?"请求中...":"就绪"); $r.css("opacity", on?0.92:1); $pre.add($nxt).add($pg.find(".pgx-page")).prop("disabled", on); } /** * render() 渲染页面数据 * a:接口返回的数字数组 */ function render(a){ a=(a||[]).slice(0,PS); // 最多截取PS=10条 // 如果有数据,循环渲染数字span;没有就显示暂无数据 $l.html('<div class="pgx-line">'+(a.length ? a.map(n=>'<span class="pgx-num">'+n+'</span>').join("") : '<span class="pgx-empty">暂无数据</span>')+'</div>'); } /** * req(np) 核心请求函数,切换页码就执行这个 * np:newPage,要跳转的目标页码 */ function req(np){ // 页码边界限制:最小1,最大TP=5 p=Math.min(TP,Math.max(1,np)); ui(); // 更新分页UI // window.request() !!反爬钩子,混淆JS挂载的全局函数,用来做前置校验、更新window.match3 window.request&&window.request(); // 组装GET请求参数 d var d={ page:p, // 当前页码 pageSize:PS, // 每页条数 kw:$.trim($kw.val()||""), // 搜索框关键词,去除前后空格 m:window.match3 // ⭐重点加密参数,来自外部混淆JS赋值的全局变量window.match3 }; loading(true); // 开启加载状态 $dbg&&$dbg.text("GET "+API+"?"+$.param(d)); // debug框打印完整请求url,$.param把对象转url查询字符串 render(Array(10).fill('加载中...')); // 页面先渲染10个“加载中…”占位 // ✅迷惑同步接口!!重点 $.ajax({url: "/api2/3", async: false,}) // ✅真正拿数据接口,async:false 同步阻塞ajax(浏览器会卡住等待接口返回) $.ajax({ url:API, type:"GET", dataType:"json", data:d, timeout:1e4, // 超时10000毫秒=10秒 async: false, // 同步请求!浏览器等待接口返回才往下走,逆向重点 success:function(res){ render(res&&res.data); // 成功,把返回data数组渲染页面 $s.text("加载完成"); }, error:function(){ failedAlert("接口异常,请使用chrome浏览器开启无痕模式,若错误持续请联系管理员") $s.text("接口异常,请使用chrome浏览器开启无痕模式,若错误持续请联系管理员"); }, complete:function(){ loading(false); // 无论成功失败,结束loading ui(); } }); } // =========绑定页面点击事件========== $pre.on("click", ()=>p>1&&req(p-1)); // 上一页:页码大于1,调用req $nxt.on("click", ()=>p<TP&&req(p+1)); // 下一页:页码小于5,调用req $pg.on("click",".pgx-page", function(){ req(+$(this).data("page")); }); //点击数字页码 $kw.on("keydown", e=>e.key==="Enter"&&req(1)); //搜索框回车,回到第1页查询 ui(); req(1); // 页面初始化,直接执行req(1),加载第一页数据 })(window.jQuery);

第一次请求会产生cookie,第二次请求携带cookie可以请求到正确的数据。

  • 执行req()函数,先执行第一句同步 ajax:/api2/3

    • 浏览器发出 GET 请求访问/api2/3
    • 服务器收到这个请求,在响应头返回Set‑Cookie,种下校验 Cookie
    • async:false同步阻塞:JS 代码停在这里,必须等待这个请求完整响应、浏览器保存 Cookie 完毕,才往下执行
  • /api2/3请求完成,浏览器已经把服务端下发的 Cookie 存入浏览器 Cookie 存储。

  • 紧接着执行第二个 ajax,请求真正业务接口/api/question/3

    • 浏览器 HTTP 请求头自动把本机该域名下所有 Cookie 全部带上(刚刚 /api2/3 种下的 cookie 就在里面)
    • 后端校验两件东西: 请求头里面由/api2/3种下的 cookie

模拟后端源码

from flask import Flask, request, make_response, jsonify import hashlib import uuid app = Flask(__name__) # 模拟数据库:存储 cookie令牌 和前端提交的m值的对应关系 # key:cookie_value, value:前端合法m标记 token_store = dict() def md5(s: str) -> str: return hashlib.md5(s.encode()).hexdigest() @app.route("/api2/3") def api2_3(): """ 迷惑前置接口 浏览器先请求这个接口,后端Set‑Cookie下发校验token async:false同步请求,前端不处理返回内容 """ # 生成随机校验token,通过Set‑Cookie给到浏览器 check_token = str(uuid.uuid4()) token_store[check_token] = {"valid": False} resp = make_response("", 200) # 后端下发Cookie,浏览器自动保存;后续请求自动带上 resp.set_cookie("yuanren_check", check_token) return resp @app.route("/api/question/3") def question3(): """ 真正业务接口,双重校验 校验1:请求头Cookie里面的 yuanren_check 是否存在,来自/api2/3下发 校验2:url查询参数 m 是否合法(模拟window.match3加密校验) """ # 1.取出浏览器带过来的cookie client_cookie = request.cookies.get("yuanren_check") if not client_cookie or client_cookie not in token_store: # 没有调用/api2/3,没有拿到后端下发cookie,校验失败 return jsonify({"data": ["接口异常,请使用chrome浏览器开启无痕模式..."]}) # 2.获取url参数 m m_param = request.args.get("m", "") page = request.args.get("page", 1) # =========模拟校验m,真实环境这里是后端复现前端加密逻辑校验m========= # 真实场景:后端拿到时间戳,复现同样加密算法,比对m;这里简化演示 if not m_param: return jsonify({"data": ["接口异常,请使用chrome浏览器开启无痕模式..."]}) # ✅双重校验全部通过,返回业务数据 mock_data = [11111,22222,33333,44444,55555,66666,77777,88888,99999,100000] return jsonify({"data": mock_data}) if __name__ == '__main__': app.run(port=5000, debug=True)
import requests session = requests.Session() # 第一步:访问前置接口,session自动接收Set‑Cookie保存 session.get("http://127.0.0.1:5000/api2/3") # 第二步:业务请求,session自动携带上面拿到的cookie,传入m参数 resp = session.get( "http://127.0.0.1:5000/api/question/3", params={"page":1,"pageSize":10,"m":"前端算出来的match3值"} ) print(resp.json())

set_cookie和cookie

模拟流程:

1. 客户端第一次访问/api2/3,服务器返回Set‑Cookie:check_token=abc888,命令客户端存下这条 cookie

2. 客户端(session)自动收下保存这条 cookie

3. 再访问业务接口/api/question/3,客户端自动在请求头带上Cookie: check_token=abc888

4. 后端读取请求头 Cookie 做校验

1)服务端代码(flask)

from flask import Flask, request, make_response, jsonify app = Flask(__name__) @app.route("/api2/3") def api2_3(): """前置接口,返回Set‑Cookie,种下校验cookie""" resp = make_response("", 200) # ========== Set‑Cookie:服务器下发指令,让浏览器存cookie ========== resp.set_cookie("check_token", value="abc888", path="/") return resp @app.route("/api/question/3") def question3(): """业务接口,读取请求头里面的Cookie做校验""" # ========== request.cookies 拿到【请求头Cookie】,是客户端带过来的 ========== check = request.cookies.get("check_token") m_arg = request.args.get("m") if check != "abc888" or not m_arg: return jsonify({"data":["校验失败"]}) return jsonify({"data":[100,200,300,400]}) if __name__ == "__main__": app.run(port=5000,debug=True)

2)客户端 requests 模拟浏览器行为

import requests BASE = "http://127.0.0.1:5000" # 必须同一个Session!等价浏览器的cookie仓库 session = requests.Session() print("=====第一次请求 /api2/3(对应浏览器async:false同步ajax)=====") r1 = session.get(f"{BASE}/api2/3") # r1.headers 里面能看到服务端返回的 Set‑Cookie(响应头) print("响应头 Set‑Cookie:", r1.headers.get("set‑cookie")) # 此时session内部已经自动保存 check_token=abc888,不需要手动处理! print("\n=====第二次请求业务接口 /api/question/3 =====") params = {"page":1,"m":"xxx_match3_value"} r2 = session.get(f"{BASE}/api/question/3", params=params) # 打印本次发给服务器的 请求头 Cookie print("本次请求发送出去的Cookie:", r2.request.headers.get("cookie")) print("业务接口返回:", r2.json())

ajax请求

同步 AJAX

// ===================== 同步请求 async = false ===================== function syncApi2_3() { const xhr = new XMLHttpRequest(); // 第三个参数 false → 同步阻塞!JS代码停在这里不动 xhr.open("GET", "/api2/3", false); // 请求头(可选) xhr.setRequestHeader("Accept", "*/*"); xhr.send(null); // GET请求 send传null // ⚠️ 同步模式:代码执行到send(),直接卡住! // 1.浏览器发出http请求 // 2.等待服务器返回响应 // 3.浏览器解析响应头 Set‑Cookie,写入浏览器Cookie存储(内核自动做,JS代码读不到Set‑Cookie) // ✅全部完成,send()才返回,才会执行下面这行 console.log("api2/3 请求结束,我们完全丢弃返回内容,不使用 xhr.responseText"); // 不处理返回结果,只利用【http往返 + 浏览器保存Set‑Cookie】这个副作用 } // -------- 完整业务流程(match3真实逻辑) -------- function req() { // 第一步:同步前置接口,阻塞!等待Set‑Cookie落地到浏览器cookie库 syncApi2_3(); // 上面同步请求全部完成后,才走到这里! // 此时浏览器已经存好了 /api2/3 返回Set‑Cookie种下的校验cookie // 第二步:发起业务接口请求 const bizXhr = new XMLHttpRequest(); bizXhr.open("GET", "/api/question/3", true); // 业务接口用异步 bizXhr.onload = function(){ if(bizXhr.status === 200){ console.log("业务返回数据:", JSON.parse(bizXhr.responseText)); } } // url参数带上 m,对应 window.match3 const params = new URLSearchParams({ page:1, pageSize:10, m: window.match3 }) bizXhr.send(params); } // 调用 req();

异步 AJAX

// ===================== 异步请求 async = true(默认) ===================== function asyncDemo(){ const xhr = new XMLHttpRequest(); xhr.open("GET","/api2/3", true); // true=异步,不会阻塞代码 xhr.onload = function(){ // 只有接口返回之后,才会进入这个回调函数 console.log("异步:接口返回了", xhr.responseText); } xhr.send(); // send() 立刻返回!不会等待接口!代码直接往下执行! console.log("send执行完毕,不等接口返回就跑到这里!"); }

jQuery $.ajax 写法

// 同步版本 match3源码风格 function req(){ // 前置同步ajax $.ajax({ url:"/api2/3", type:"GET", async: false, // 重点!false = 同步阻塞 success:function(res){ // 这里的回调同步模式下也会执行,但是代码不拿res,直接忽略 } }) // 上面同步请求全部完成,才运行下面业务请求 $.ajax({ url:"/api/question/3", type:"GET", async:true, data:{ page:1, pageSize:10, m: window.match3 }, success:function(resp){ console.log("业务数据", resp) } }) }
参数说明
open(method, url, async)async:false同步;true异步 (默认)
send(body)GET 请求传null;POST 传请求体
onload / onreadystatechange异步模式:接口回来之后触发回调;同步模式回调几乎没用,send 完数据已经就绪
响应头Set‑CookieJS 代码无法读取!浏览器内核自动解析并存入 Cookie 存储
请求头Cookie浏览器自动组装该域名下全部本地 Cookie,ajax 发出请求自动带上,JS 不能直接读写这个请求头

最终代码

import requests import json url = "https://match.yuanrenxue.cn/" session = requests.session() session.headers = { "sec-ch-ua-platform": "\"Windows\"", "x-requested-with": "XMLHttpRequest", "user-agent": 'yuanrenxue', "accept": "application/json, text/javascript, */*; q=0.01", "sec-ch-ua": "\"Not/A)Brand\";v=\"99\", \"Chromium\";v=\"148\"", "sec-ch-ua-mobile": "?0", "sec-fetch-site": "same-origin", "sec-fetch-mode": "cors", "sec-fetch-dest": "empty", "referer": "https://match.yuanrenxue.cn/match/3", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "zh-CN,zh;q=0.9", "cookie": "sessionid=ho9vnm6xj4tu49sls5l2xhghod0ekog8", "priority": "u=0, i" } def send1(): url1 = url + "api2/3" res = session.get(url1) print(res) def send2(page): url2 = url + "api/question/3" params = { 'page': page, 'pageSize': 10, "kw": "" } res = session.get(url2, params=params) # print(res) data = json.loads(res.text).get("data") print(i, data) sum = 0 for item in data: sum += item return sum if __name__ == '__main__': sum = 0 for i in range(1,6): send1() sum += send2(i) print(sum)

http://www.jsqmd.com/news/1402886/

相关文章:

  • PADS Layout安全间距检查报错:从原理到实战的完整排查指南
  • Excel多级联动菜单:从数据验证到INDIRECT函数的完整实现指南
  • 【环境配置】Windows 配置 SSH 免密登录 Ubuntu服务器
  • Python包发布全流程指南:从项目打包到PyPI上架
  • 机器学习中的范数:从L1、L2到L2,1,理解正则化与稀疏性的核心原理
  • 开放式耳机哪个牌子值得买?十款热门开放式耳机测评,别只看价格选耳机!
  • 15行代码实现AI智能体权限控制:OpenClaw核心原理与工程实践
  • 从Coze到Dify:AI Agent低代码开发与私有化部署全流程实战
  • 云端部署 MiniMax H3 ComfyUI:环境验收、模型目录与故障排查
  • 开源AI Agent框架OpenClaw部署指南:从Docker实战到企业级应用
  • 布芭软装中VESCOM品牌产品分析
  • 大模型长文本处理:稀疏注意力与滑动窗口技术对比与实战
  • PyCharm运行与调试配置全解析:从环境搭建到高效调试
  • 从PyCharm迁移到VSCode:打造高效Python开发环境的完整指南
  • 民族电网:助力双碳,西部绿色能源支撑全国低碳转型
  • AI Coding 时代,我们缺的不是更强的模型,而是让 Agent 站稳的「地形」
  • 【二维数组按第一个元素排序】
  • Mac应用无法打开?Gatekeeper安全机制与解决方案全解析
  • 2026随身WiFi行业观察:飞猫M1差异化竞争优势全解析
  • ip实验:
  • 厦门市怎么挑选靠谱的防水补漏维修团队_卫生间漏水施工方好坏分辨技巧,居民选购参考思路 - 雨婺虹修缮
  • 【2027最新毕设实战】基于Spring Boot的农业信息管理系统的设计与实现 (附源码资料)毕设最新选题推荐,网站项目,java项目,文档讲解
  • 人工智能训练师(三级/高级工)考试指南
  • 基于微信小程序的智能停车场管理系统毕业设计项目源码
  • 以太网转 CAN 网关下行控制技术选型分析 —— 基于捷宸电子 (IPCSUN) DNET460 的系统性实测验证
  • 解决AutoCAD双击DWG文件报错“找不到.acad.exe”的注册表修复指南
  • 笔记本Wi-Fi故障排查全攻略:从物理开关到驱动冲突的解决之道
  • OpenClaw开源机器人框架:架构、社区治理与可持续实践深度解析
  • 曲率感知零阶优化:大模型测试时适应的内存高效方案
  • OpenClaw技能加载机制深度解析:从loadSkillsFromDir看AI Agent可扩展性设计