简介:本资源聚焦瑞数5代(rs5)动态反爬机制的实战破解,面向中高级Python爬虫开发者与Web安全研究者,解决rs5环境补全、动态Cookie生成及URL后缀算法还原等核心难点。压缩包共7个文件,含5个JavaScript脚本(涵盖rs5调试器、环境模拟与后缀生成逻辑)和2个Python脚本(基于pyexecjs调用JS执行、封装调试流程),总大小881KB,结构紧凑、即拿即用。已有1080人学习下载,体现其在真实业务场景(如房地产行业数据采集)中的高实用性。读者可直接复用JS环境补全逻辑、Python调用封装模板及调试器源码,快速集成到自有爬虫项目中;同时通过debugger_bak与dev系列脚本对比,深入理解rs5多版本环境变量差异与算法演进路径,掌握从逆向分析到稳定落地的完整技术链路。
1. 瑞数5代(rs5)不是“加密”而是环境校验:补环境+后缀算法才是真实对抗焦点
很多刚接触瑞数反爬的开发者会误以为 rs5 是一套“JS 加密算法”,花大量时间逆向混淆后的eval或atob链,结果发现即使还原出原始逻辑,请求仍被拦截——因为 rs5 的核心防御不在“算什么”,而在“谁在算”。它通过深度检测浏览器运行时环境(如window属性完整性、navigator对象真实性、document构造函数可写性、Function.prototype.toString是否被篡改)构建可信度评分;再结合动态生成的后缀参数(非固定 token,而是由当前页面 DOM 结构、JS 执行上下文、时间戳、随机 salt 共同参与计算),形成双重校验闭环。这意味着:单纯用 requests 模拟 headers 无效,仅 patch 个别 JS 函数也不够,必须让自动化环境具备“类真实浏览器”的行为一致性。本文面向已掌握 Selenium/Playwright 基础、正卡在 rs5 请求 403 或 503 的 Python 爬虫工程师,聚焦可落地的补环境策略与后缀算法复现路径,不讲概念堆砌,只拆解你调试器里真正能看到的原型链篡改点、toString劫持位置、以及__rsc参数生成时的 DOM 依赖项。
2. 补环境三要素:原型链修复、函数 toString 恢复、DOM 属性注入
瑞数5代对环境的校验远超常规反爬,其检测脚本会在页面加载早期执行多轮探测,重点检查三类对象状态:window的不可枚举属性是否缺失、navigator的 getter 是否被重写、document的createElement等方法是否指向原生实现。若任一环节异常,后续后缀计算直接跳过,返回空值或伪造值,导致服务端校验失败。补环境不是“打补丁”,而是重建浏览器对象模型的可信基线。
2.1 识别被篡改的原型链:从Object.prototype到HTMLDocument.prototype
瑞数常通过Object.defineProperty隐藏关键属性(如window.__SENTRY__、navigator.permissions),或劫持Object.getPrototypeOf返回伪造原型。实际调试中,可在控制台执行:
Object.getOwnPropertyNames(window).filter(k => k.startsWith('__') || k.includes('sentry')) // 输出类似:['__RS5__', '__rsc', '__SENTRY__']这些属性名即为瑞数注入的校验标记。更隐蔽的是对HTMLDocument.prototype的篡改:瑞数会重写document.createElement,使其在创建特定标签(如<script>、<iframe>)时注入校验逻辑。验证方式是:
document.createElement.toString() // 若返回 "function createElement() { [native code] }" 则正常;若含自定义逻辑则已被劫持补环境关键动作:在 Puppeteer/Playwright 启动后、加载目标页前,注入以下 JS 片段重置原型链:
// 注入时机:page.addInitScript() const resetPrototype = () => { // 恢复 window 原型链 Object.setPrototypeOf(window, Window.prototype); // 恢复 document 原型链 Object.setPrototypeOf(document, HTMLDocument.prototype); // 恢复 navigator 原型链 Object.setPrototypeOf(navigator, Navigator.prototype); // 删除瑞数注入的私有属性 delete window.__RS5__; delete window.__rsc; delete window.__SENTRY__; }; resetPrototype();提示:此脚本必须在页面任何 JS 执行前注入(使用
page.addInitScript()而非page.evaluate()),否则瑞数检测脚本已运行完毕,补救无效。
2.2 恢复 Function.prototype.toString 的原生行为
瑞数通过篡改Function.prototype.toString来检测代码注入痕迹。正常情况下该函数返回"function xxx() { [native code] }",但被篡改后可能返回空字符串、固定字符串或抛出错误。检测方式:
const fn = () => {}; console.log(fn.toString()); // 若输出非 "[native code]" 格式,则被劫持补环境关键动作:在addInitScript中同步恢复toString:
// 继续在 resetPrototype 后添加 const originalToString = Function.prototype.toString; Object.defineProperty(Function.prototype, 'toString', { value: originalToString, configurable: false, writable: false, enumerable: false });注意:
configurable: false是必须的,瑞数会检查该属性是否可配置;若设为true,后续校验仍会失败。
2.3 注入缺失的 DOM 属性与方法
瑞数校验常依赖document上的非常规属性,如document.hidden、document.visibilityState、document.hasFocus()返回值。Headless 浏览器默认这些值为undefined或false,需主动注入:
// 在 init script 中补充 Object.defineProperty(document, 'hidden', { value: false, writable: false }); Object.defineProperty(document, 'visibilityState', { value: 'visible', writable: false }); document.hasFocus = () => true; // 补充 createElement 的原生引用 document.createElement = HTMLDocument.prototype.createElement.bind(document);表格:瑞数5代高频校验的 DOM 属性及推荐注入值
| 属性/方法 | 正常值 | 注入方式 | 校验目的 |
|---|---|---|---|
document.hidden | false | Object.defineProperty(document, 'hidden', {value: false}) | 检测页面是否处于后台 |
document.visibilityState | 'visible' | Object.defineProperty(document, 'visibilityState', {value: 'visible'}) | 防止 visibility API 欺骗 |
navigator.webdriver | undefined | Object.defineProperty(navigator, 'webdriver', {get: () => undefined}) | 绕过 WebDriver 检测 |
window.outerWidth/outerHeight | 接近innerWidth/innerHeight | Object.defineProperty(window, 'outerWidth', {value: window.innerWidth}) | 检测窗口尺寸异常 |
3. 后缀算法解析:从 DOM 结构哈希到动态 salt 生成
瑞数5代的后缀参数(通常名为__rsc)并非静态 token,而是由当前页面 DOM 快照 + 时间戳 + 随机 salt 三次哈希生成。其核心逻辑在rs5.js中,但关键输入源来自页面实时状态,因此必须在目标页完全加载后、发起请求前计算。
3.1 定位后缀生成入口:window.__rsc的赋值时机
在 Chrome DevTools 的 Sources 面板中,全局搜索__rsc =或window.__rsc =,通常会定位到类似代码:
window.__rsc = (function() { const t = Date.now(); const e = document.documentElement.outerHTML.substring(0, 1000); // 截取 DOM 前1000字符 const n = Math.random().toString(36).substr(2, 8); // 8位随机salt return md5(t + e + n); // 实际为自定义哈希,非标准md5 })();注意:document.documentElement.outerHTML是关键输入,意味着每次 DOM 变化(如 AJAX 渲染新内容)都会导致后缀变更。因此,必须在目标数据渲染完成后再计算__rsc,而非页面加载完成时。
3.2 Python 端复现哈希逻辑:避开 JS 引擎调用的三种方案
直接在 Python 中复现瑞数的自定义哈希(常为多次 XOR + 字符串旋转)极容易因 JS 运行时差异失败。更可靠的做法是:在浏览器内执行计算,Python 仅接收结果。以下是三种主流方案对比:
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Playwright evaluate | page.evaluate("() => window.__rsc") | 无需额外依赖,与页面环境完全一致 | 依赖__rsc已存在,若未生成则报错 | 页面加载后立即可用 |
| 注入计算函数 | page.addScriptTag(content: "window.genRsc = () => {...}") | 可控性强,支持传参定制 | 需手动维护 JS 逻辑更新 | DOM 动态变化后需重新计算 |
| CDP 调用 Runtime.evaluate | page._channel.send("Runtime.evaluate", {...}) | 最底层,绕过 Playwright 封装 | API 不稳定,Playwright 版本升级易失效 | 高级用户调试 |
推荐方案(Playwright + 动态计算):
from playwright.sync_api import sync_playwright import time def get_rsc_suffix(page): # 等待目标数据元素出现(如商品列表容器) page.wait_for_selector("#product-list", state="visible", timeout=10000) # 在页面上下文中执行后缀生成(确保 DOM 已就绪) rsc = page.evaluate("""() => { // 复制瑞数原始逻辑,此处为简化示意 const t = Date.now(); const e = document.documentElement.outerHTML.substring(0, 1000); const n = Math.random().toString(36).substr(2, 8); // 实际需替换为瑞数使用的哈希函数(如自定义 rotateXOR) return btoa(t + e + n).substring(0, 16); // 示例,非真实算法 }""") return rsc with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://target-site.com/list") rsc_value = get_rsc_suffix(page) # 获取后缀 # 发起带后缀的请求 response = page.goto(f"https://target-site.com/api/data?__rsc={rsc_value}")说明:
page.evaluate内的 JS 代码必须严格复现瑞数原始逻辑中的哈希步骤。若瑞数使用了CryptoJS.SHA256等库,需提前注入该库;若为自定义位运算,则需在 Python 中用ctypes或bitarray精确复现——但优先选择在浏览器内计算,避免跨语言精度误差。
3.3 后缀参数的生命周期管理:何时刷新、何时复用
__rsc并非一次生成永久有效。瑞数服务端会校验其时间戳成分(Date.now()),若请求时间与生成时间相差超过 30 秒,直接拒绝。因此:
- 禁止缓存
__rsc值超过 25 秒 - 每个请求必须独立生成
__rsc(不能多个请求共用同一后缀) - AJAX 分页时,每页需重新计算(因 DOM 结构变化)
验证方式:抓包对比两次请求的__rsc值,若相同且间隔 >25s,必失败。
4. Python 爬虫集成:Playwright 自动化流程与异常处理
将补环境与后缀生成整合进完整爬虫流程,需解决三个实际问题:如何保证初始化脚本执行顺序、如何捕获瑞数拦截响应、如何优雅重试。以下为生产级代码骨架。
4.1 初始化脚本的加载顺序与依赖管理
Playwright 的add_init_script会注入到每个 frame,但瑞数检测脚本常位于主 frame 的<script>标签中。必须确保补环境脚本在瑞数脚本执行前生效。正确顺序:
def setup_rs5_environment(page): # Step 1: 注入原型链修复脚本(最早执行) page.add_init_script(""" Object.setPrototypeOf(window, Window.prototype); Object.setPrototypeOf(document, HTMLDocument.prototype); Object.setPrototypeOf(navigator, Navigator.prototype); delete window.__RS5__; delete window.__rsc; """) # Step 2: 注入 toString 恢复(紧随其后) page.add_init_script(""" const original = Function.prototype.toString; Object.defineProperty(Function.prototype, 'toString', { value: original, configurable: false, writable: false, enumerable: false }); """) # Step 3: 注入 DOM 属性(最后,因依赖前两步) page.add_init_script(""" Object.defineProperty(document, 'hidden', {value: false}); Object.defineProperty(document, 'visibilityState', {value: 'visible'}); Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); document.hasFocus = () => true; """) # 使用示例 with sync_playwright() as p: browser = p.chromium.launch(headless=True, args=["--disable-blink-features=AutomationControlled"]) page = browser.new_page() setup_rs5_environment(page) # 必须在 goto 前调用 page.goto("https://target.com")注意:
args=["--disable-blink-features=AutomationControlled"]是必需的,否则navigator.webdriver为true,瑞数直接拦截。
4.2 拦截响应识别与自动重试机制
瑞数拦截返回码不固定,常见为403 Forbidden、503 Service Unavailable或200但响应体含{"code":403,"msg":"invalid rsc"}。需在请求后主动校验:
def safe_fetch_data(page, url): for attempt in range(3): try: # 生成新后缀 rsc = page.evaluate(generate_rsc_js) # generate_rsc_js 为前述 JS 字符串 full_url = f"{url}?__rsc={rsc}" # 发起请求并等待响应 response = page.goto(full_url, wait_until="networkidle", timeout=15000) # 检查响应内容 content = response.text() if '"code":403' in content or '"msg":"invalid rsc"' in content: raise ValueError("Invalid __rsc detected") if response.status != 200: raise ValueError(f"HTTP {response.status}") return content except Exception as e: print(f"Attempt {attempt + 1} failed: {e}") if attempt < 2: time.sleep(1) # 退避 continue else: raise e # 调用 data = safe_fetch_data(page, "https://api.target.com/items")4.3 瑞数6代兼容性提示:DOM 快照粒度升级
最新网络热词中频繁提及“瑞数6”,其核心变化是将 DOM 校验从outerHTML升级为document.querySelectorAll('*').length + document.styleSheets.length + performance.memory.totalJSHeapSize的组合哈希。这意味着:
- 仅截取
outerHTML前1000字符已不足 - 必须获取全部样式表数量与 JS 堆内存快照
performance.memory在无痕模式下不可用,需启用--enable-precise-memory-info
对应 Python 侧需扩展generate_rsc_js:
const domHash = document.querySelectorAll('*').length + document.styleSheets.length + (performance.memory ? performance.memory.totalJSHeapSize : 0); return customHash(Date.now() + domHash + Math.random());5. 关键调试技巧:快速定位补环境失效点与后缀生成偏差
当请求仍被拦截时,90% 的问题集中在两个环节:补环境未生效,或后缀计算与瑞数服务端不一致。以下技巧可将排查时间从小时级压缩至分钟级。
5.1 三步验证补环境是否成功
在 Playwright 中启用devtools=True,手动打开控制台执行以下命令,逐项验证:
检查原型链:
Object.getPrototypeOf(window) === Window.prototype // 应返回 true Object.getPrototypeOf(document) === HTMLDocument.prototype // 应返回 true检查 toString 行为:
const testFn = () => {}; testFn.toString() === "function () { [native code] }" // 应返回 true检查 DOM 属性:
document.hidden // 应为 false(而非 undefined) navigator.webdriver // 应为 undefined(而非 true)
若任一为false,说明对应补环境脚本未执行或被覆盖。
5.2 后缀偏差比对:导出浏览器端与 Python 端计算中间值
在page.evaluate中打印各输入变量,与 Python 本地日志比对:
# 在 generate_rsc_js 中添加调试输出 page.evaluate("""() => { const t = Date.now(); const e = document.documentElement.outerHTML.substring(0, 1000); const n = Math.random().toString(36).substr(2, 8); console.log('RS5_DEBUG:', {t, e_len: e.length, n}); // 输出到浏览器控制台 return customHash(t + e + n); }""")同时在 Python 中记录相同变量:
print(f"Python_DEBUG: t={int(time.time() * 1000)}, e_len=?, n=?") # e_len 需通过 page.content() 获取比对t值(应相差 <100ms)、n值(若不同则随机数种子不一致)、e_len(若差异大,说明 DOM 截取时机不同)。
5.3 瑞数无限 debug 的绕过:禁用 source map 与断点注入
网络热词“瑞数的无限debug怎么去掉”实指瑞数在rs5.js中插入大量debugger语句,导致自动化工具卡死。解决方案不是删除 debugger(会触发校验),而是禁用调试器介入:
# Playwright 启动时禁用 JS 调试 context = browser.new_context( java_script_enabled=True, # 关键:禁用 devtools 相关功能 bypass_csp=True, ignore_https_errors=True ) page = context.new_page() # 注入脚本屏蔽 debugger page.add_init_script("debugger = function(){};")注意:
debugger = function(){}必须在所有其他脚本前注入,否则瑞数会重写该赋值。
最终,一个能稳定过瑞数5代的 Python 爬虫,其核心不在于“破解算法”,而在于精确模拟浏览器环境的行为契约——这要求你像前端工程师一样理解原型链、像安全研究员一样分析 DOM 操作、像运维工程师一样管理请求生命周期。
本文还有配套的精品资源,点击获取