Python调用JavaScript函数:爬虫逆向实战指南
详细介绍在Python爬虫中调用JavaScript函数的多种方法,包括PyExecJS、Node.js集成、QuickJS等方案的实现和对比
Python调用JavaScript函数:爬虫逆向实战指南
在进行JavaScript逆向爬虫时,我们经常需要在Python中调用JavaScript的加密或签名函数。本文将介绍几种常用的方法,帮助你选择最适合的解决方案。
常用方案对比
1. PyExecJS
优点:
- 安装简单,使用方便
- 支持多种JavaScript运行时
- 社区资源丰富
缺点:
- 性能相对较差
- 部分新特性支持不完善
- 依赖外部JavaScript运行时
import execjs
# 准备JavaScript代码
js_code = """
function encrypt(data) {
// 加密逻辑
return data.split('').reverse().join('');
}
"""
# 编译JavaScript代码
ctx = execjs.compile(js_code)
# 调用函数
result = ctx.call('encrypt', 'hello world')
print(result) # 输出: dlrow olleh
2. QuickJS
优点:
- 性能优秀
- 不依赖外部运行时
- 支持ES6+特性
缺点:
- 安装可能需要编译
- 文档相对较少
import quickjs
# 创建运行时和上下文
ctx = quickjs.Context()
# 执行JavaScript代码
ctx.eval("""
function encrypt(data) {
return data.split('').reverse().join('');
}
""")
# 调用函数
result = ctx.eval("encrypt('hello world')")
print(result) # 输出: dlrow olleh
3. Node.js 集成
优点:
- 完整的JavaScript运行环境
- 性能优秀
- 支持所有新特性
缺点:
- 需要安装Node.js
- 进程间通信有一定开销
import subprocess
import json
def call_node_function(js_file, function_name, *args):
# 准备Node.js命令
cmd = [
'node', '-e',
f'const fn = require("./{js_file}"); console.log(JSON.stringify(fn.{function_name}(...{json.dumps(args)})))'
]
# 执行命令并获取结果
result = subprocess.check_output(cmd, text=True)
return json.loads(result)
# 使用示例(假设有一个encrypt.js文件)
result = call_node_function('encrypt.js', 'encrypt', 'hello world')
print(result)
实战案例
1. 处理加密参数
import execjs
import requests
# 加载JavaScript代码
with open('encrypt.js', 'r', encoding='utf-8') as f:
js_code = f.read()
# 编译JavaScript代码
ctx = execjs.compile(js_code)
def get_encrypted_params(data):
# 调用加密函数
encrypted = ctx.call('encrypt', data)
return encrypted
# 发送请求
def make_request():
data = {
'username': 'test',
'timestamp': '1641234567'
}
# 获取加密参数
encrypted_data = get_encrypted_params(data)
# 发送请求
response = requests.post(
'https://api.example.com/data',
json={'data': encrypted_data}
)
return response.json()
2. 处理签名验证
import execjs
import time
import hashlib
class SignGenerator:
def __init__(self, js_file):
with open(js_file, 'r', encoding='utf-8') as f:
self.ctx = execjs.compile(f.read())
def generate_sign(self, params):
# 添加时间戳
params['timestamp'] = str(int(time.time()))
# 调用JavaScript签名函数
sign = self.ctx.call('generateSign', params)
params['sign'] = sign
return params
# 使用示例
signer = SignGenerator('sign.js')
params = {
'id': '12345',
'data': 'test'
}
signed_params = signer.generate_sign(params)
3. 复杂环境模拟
对于需要完整浏览器环境的情况:
import execjs
import requests
class JSEnvironment:
def __init__(self):
# 加载环境模拟代码
with open('browser_env.js', 'r', encoding='utf-8') as f:
env_code = f.read()
# 加载目标JavaScript代码
with open('target.js', 'r', encoding='utf-8') as f:
target_code = f.read()
# 组合并编译代码
self.ctx = execjs.compile(env_code + '\n' + target_code)
def call_function(self, func_name, *args):
try:
return self.ctx.call(func_name, *args)
except execjs.RuntimeError as e:
print(f"执行错误: {e}")
return None
# 使用示例
js_env = JSEnvironment()
result = js_env.call_function('someFunction', 'arg1', 'arg2')
性能优化建议
- 缓存JavaScript上下文
class JSRunner:
_ctx = None
@classmethod
def get_context(cls):
if cls._ctx is None:
with open('script.js', 'r', encoding='utf-8') as f:
cls._ctx = execjs.compile(f.read())
return cls._ctx
- 批量处理
def batch_process(items, batch_size=100):
ctx = execjs.compile(js_code)
results = []
for i in range(0, len(items), batch_size):
batch = items[i:i + batch_size]
# 一次调用处理多个数据
batch_results = ctx.call('batchEncrypt', batch)
results.extend(batch_results)
return results
- 异步处理
import asyncio
import concurrent.futures
async def process_items(items):
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [
loop.run_in_executor(
executor,
execjs.compile(js_code).call,
'encrypt',
item
)
for item in items
]
results = await asyncio.gather(*futures)
return results
注意事项
- 安全性考虑
- 注意JavaScript代码的来源
- 避免直接执行不可信的代码
- 做好输入验证和清理
- 错误处理
try:
result = ctx.call('encrypt', data)
except execjs.RuntimeError as e:
print(f"JavaScript执行错误: {e}")
except Exception as e:
print(f"其他错误: {e}")
- 环境依赖
- 确保安装了必要的JavaScript运行时
- 检查Node.js版本兼容性
- 处理好编码问题
总结
在Python爬虫中调用JavaScript函数是一个常见需求,选择合适的方案需要考虑以下因素:
- 性能要求
- 环境限制
- 维护成本
- 团队熟悉度
建议根据实际需求选择合适的方案,对于简单场景可以使用PyExecJS,对于性能要求高的场景可以考虑QuickJS或Node.js集成方案。
参考资源
如果你在实践中遇到问题,欢迎在评论区讨论交流。
评论