X-hub

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')

性能优化建议

  1. 缓存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
  1. 批量处理
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
  1. 异步处理
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

注意事项

  1. 安全性考虑
  • 注意JavaScript代码的来源
  • 避免直接执行不可信的代码
  • 做好输入验证和清理
  1. 错误处理
try:
    result = ctx.call('encrypt', data)
except execjs.RuntimeError as e:
    print(f"JavaScript执行错误: {e}")
except Exception as e:
    print(f"其他错误: {e}")
  1. 环境依赖
  • 确保安装了必要的JavaScript运行时
  • 检查Node.js版本兼容性
  • 处理好编码问题

总结

在Python爬虫中调用JavaScript函数是一个常见需求,选择合适的方案需要考虑以下因素:

  1. 性能要求
  2. 环境限制
  3. 维护成本
  4. 团队熟悉度

建议根据实际需求选择合适的方案,对于简单场景可以使用PyExecJS,对于性能要求高的场景可以考虑QuickJS或Node.js集成方案。

参考资源

如果你在实践中遇到问题,欢迎在评论区讨论交流。

评论