Python替代无头浏览器获取HMAC/AES密钥的方案实现
替代无头浏览器高效提取HMAC-Secret和AES-key的解决方案
问题背景
原本通过Python脚本启动无头浏览器运行HTML片段,解析页面获取HMAC-Secret和AES-key,但浏览器启动耗时超过7秒,希望通过在Python内直接执行JavaScript的方式提升效率,最初尝试使用js2py。
js2py尝试中的问题
编写了如下Python代码,通过requests获取目标JS文件后用js2py执行:
import requests import js2py js2 = b""".then((r) => r.text()) .then((b) => { const dfnm = /\x72\x65\x74\x75\x72\x6e\x20\x74\x79\x70\x65\x6f\x66\x20([a-z0-9]+)\[(\d+)\]\.([a-z0-9]+)\x20\x3d\x3d\x3d\x20\x27\x66\x75\x6e\x63\x74\x69\x6f\x6e\x27/gi.exec(b); const krm = /\x27\\x68\x27:[a-z0-9]+\.[a-z0-9]+\((\d+)\),'\\x61':[a-z0-9]+\.[a-z0-9]+\((\d+)\)/gi.exec(b); document.write(JSON.stringify({ HMAC_SECRET: window[dfnm[1]][dfnm[2]][dfnm[3]](krm[1]), AES_KEY: window[dfnm[1]][dfnm[2]][dfnm[3]](krm[2]) })); });""" def get_hmac_aes_keys(): response = requests.get('https://player.akamaized.net/html5player/core/html5-c5-player.js') if response.status_code == 200: js = response.text context = js2py.EvalJs() context.execute(js + js2) get_hmac_aes_keys()
运行后出现两个核心问题:
- 正则表达式中的转义字符引发
SyntaxWarning: invalid escape sequence '\['警告 - 执行时抛出
KeyError: 3,原因是js2py无法模拟完整的浏览器环境,window、document等浏览器专属变量未定义,依赖这些变量的JS逻辑无法正常运行。
最终可行方案:Python调用Node.js+JSDOM
通过Python调用Node.js,结合JSDOM模拟完整浏览器环境,成功高效获取密钥,完整代码如下:
import subprocess import json import os js_code = """ const { JSDOM } = require("jsdom"); const fs = require("fs"); async function simulateBrowser() { const fetch = await import('node-fetch').then(mod => mod.default); const dom = new JSDOM(`<!DOCTYPE html><body></body>`, { url: "https://player.akamaized.net/", resources: "usable", runScripts: "dangerously" }); const window = dom.window; window.fetch = fetch; try { const response = await window.fetch("https://player.akamaized.net/html5player/core/html5-c5-player.js"); const jsCode = await response.text(); const scriptElement = window.document.createElement("script"); scriptElement.textContent = jsCode; window.document.body.appendChild(scriptElement); const inlineScript = ` fetch('https://player.akamaized.net/html5player/core/html5-c5-player.js') .then((r) => r.text()) .then((b) => { const dfnm = /\\\\x72\\\\x65\\\\x74\\\\x75\\\\x72\\\\x6e\\\\x20\\\\x74\\\\x79\\\\x70\\\\x65\\\\x6f\\\\x66\\\\x20([a-z0-9]+)\\\\[(\\\\d+)\\\\]\\\\.([a-z0-9]+)\\\\x20\\\\x3d\\\\x3d\\\\x3d\\\\x20\\\\x27\\\\x66\\\\x75\\\\x6e\\\\x63\\\\x74\\\\x69\\\\x6f\\\\x6e\\\\x27/gi.exec(b); const krm = /\\\\x27\\\\\\\\x68\\\\x27:[a-z0-9]+\\\\.[a-z0-9]+\\\\((\\\\d+)\\\\),'\\\\\\\\x61':[a-z0-9]+\\\\.[a-z0-9]+\\\\((\\\\d+)\\\\)/gi.exec(b); if (!dfnm || !krm) { window.simulationResult = { error: 'Regex did not match any content', dfnm, krm }; return; } window.simulationResult = { HMAC_SECRET: window[dfnm[1]][dfnm[2]][dfnm[3]](krm[1]), AES_KEY: window[dfnm[1]][dfnm[2]][dfnm[3]](krm[2]), }; }) .catch((err) => { window.simulationResult = { error: err.message }; }); `; const inlineScriptElement = window.document.createElement("script"); inlineScriptElement.textContent = inlineScript; window.document.body.appendChild(inlineScriptElement); await new Promise(resolve => setTimeout(resolve, 3000)); if (window.simulationResult) { fs.writeFileSync("output.json", JSON.stringify(window.simulationResult)); } else { fs.writeFileSync("output.json", JSON.stringify({ error: 'Unknown error' })); } } catch (err) { console.error('Error in simulateBrowser:', err); fs.writeFileSync("output.json", JSON.stringify({ error: err.message })); } } simulateBrowser().catch((err) => { console.error('Error in simulateBrowser:', err); fs.writeFileSync("output.json", JSON.stringify({ error: err.message })); }); """ with open("simulate_browser.js", "w") as f: f.write(js_code) result = subprocess.run(["node", "simulate_browser.js"], capture_output=True, text=True) if result.returncode != 0: print("Error executing JavaScript file") print(result.stderr) exit() if not os.path.exists("output.json"): print("output.json file was not created") print("Node.js output:", result.stdout) exit() with open("output.json", "r") as f: output = json.load(f) if "error" in output: print("Error in JavaScript execution:", output["error"]) else: print(output)
方案说明
- 环境模拟:使用JSDOM创建包含
window、document的完整浏览器环境,解决js2py环境简陋的问题 - fetch注入:Node.js原生无
fetch,通过node-fetch包注入到模拟环境中 - 脚本加载:将目标JS文件以
<script>标签形式插入到模拟DOM中,确保其在浏览器环境下执行 - 结果传递:将密钥提取结果写入JSON文件,Python读取该文件获取最终数据
- 效率提升:避免了无头浏览器的启动延迟,整体执行速度远快于原方案
内容的提问来源于stack exchange,提问作者Croques
相关产品推荐
相关产品推荐

