杰瑞科技汇

Python如何解析JavaScript?

核心思想

解析 JavaScript 主要涉及以下几个任务,难度递增:

Python如何解析JavaScript?-图1
(图片来源网络,侵删)
  1. 代码格式化/美化:将压缩的 JS 代码还原成可读的格式。
  2. 静态分析:在不执行代码的情况下,提取信息,如变量名、函数名、函数参数、依赖的库等。
  3. 代码转换/重构:将 ES6+ 代码转译成 ES5 代码,或者修改 AST(抽象语法树)来实现代码混淆或优化。
  4. 执行 JavaScript:在 Python 环境中运行 JS 代码并获取结果。

针对这些任务,我们有不同的工具和库。


使用现成的命令行工具(推荐用于转译和美化)

这是最常用、最可靠的方法,特别是对于代码转译(如 Babel)和格式化(如 Prettier),Python 的 subprocess 模块可以方便地调用这些外部工具。

Babel (代码转译)

Babel 是一个 JavaScript 编译器,主要用于将新版本的 JavaScript (ES6+) 代码转换为向后兼容的 JavaScript 版本(如 ES5)。

步骤:

Python如何解析JavaScript?-图2
(图片来源网络,侵删)
  1. 安装 Node.js 和 npm:Babel 是基于 Node.js 的,所以必须先安装它们。

  2. 全局安装 Babel 命令行工具

    npm install --global @babel/cli @babel/core @babel/preset-env
  3. 创建一个配置文件 .babelrc 在你的项目根目录:

    {
      "presets": ["@babel/preset-env"]
    }
  4. 在 Python 中调用 Babel

    Python如何解析JavaScript?-图3
    (图片来源网络,侵删)

    假设你有一个 input.js 文件,内容是 ES6 代码:

    // input.js
    const add = (a, b) => {
      return a + b;
    };
    console.log(add(1, 2));

    Python 代码可以这样调用 Babel 进行转译:

    import subprocess
    import os
    input_js_path = "input.js"
    output_js_path = "output_es5.js"
    # 确保输入文件存在
    if not os.path.exists(input_js_path):
        print(f"Error: Input file '{input_js_path}' not found.")
        exit()
    try:
        # 构建命令
        # babel input.js --out-file output_es5.js
        command = [
            "babel",
            input_js_path,
            "--out-file",
            output_js_path
        ]
        # 执行命令
        result = subprocess.run(command, capture_output=True, text=True, check=True)
        print("Babel transpilation successful!")
        print(f"Output written to: {output_js_path}")
        # 打印转译后的代码
        with open(output_js_path, 'r') as f:
            print("\nTranspiled Code:")
            print(f.read())
    except FileNotFoundError:
        print("Error: 'babel' command not found. Please install Babel globally.")
    except subprocess.CalledProcessError as e:
        print(f"Babel transpilation failed with error:")
        print(e.stderr)

    转译后的 output_es5.js 文件内容会是类似这样的 ES5 代码:

    "use strict";
    function add(a, b) {
      return a + b;
    }
    console.log(add(1, 2));

Prettier (代码格式化)

Prettier 是一个“有主见的”代码格式化工具,可以统一代码风格。

步骤:

  1. 全局安装 Prettier

    npm install --global prettier
  2. 在 Python 中调用 Prettier

    Python 代码可以这样调用 Prettier 进行格式化:

    import subprocess
    import os
    messy_js_path = "messy.js"
    pretty_js_path = "pretty.js"
    # 假设 messy.js 内容是一行压缩的代码
    messy_js_content = "const add=(a,b)=>{return a+b;};console.log(add(1,2));"
    with open(messy_js_path, 'w') as f:
        f.write(messy_js_content)
    try:
        # 构建命令
        # prettier messy.js --write pretty.js
        command = [
            "prettier",
            messy_js_path,
            "--write",
            pretty_js_path
        ]
        # 执行命令
        subprocess.run(command, check=True)
        print("Prettier formatting successful!")
        print(f"Formatted code written to: {pretty_js_path}")
        # 打印格式化后的代码
        with open(pretty_js_path, 'r') as f:
            print("\nFormatted Code:")
            print(f.read())
    except FileNotFoundError:
        print("Error: 'prettier' command not found. Please install Prettier globally.")
    except subprocess.CalledProcessError as e:
        print(f"Prettier formatting failed with error: {e}")

使用 Python 库进行静态分析 (AST)

如果你想直接在 Python 中分析 JS 代码的结构(提取所有函数定义),而不想依赖外部命令行工具,可以使用纯 Python 的 JS 解析库。

推荐库:esprima (Python 封装)

esprima 是一个广泛使用的 JavaScript 解析器,有 Python 封装,它能将 JS 代码转换成 抽象语法树,AST 是代码结构化的表示,你可以遍历它来获取任何你想要的信息。

安装:

pip install esprima

示例:提取所有函数名和参数

import esprima
import json
js_code = """
function greet(name) {
  console.log("Hello, " + name);
}
const multiply = (x, y) => {
  return x * y;
};
// 这是一个调用,不是定义
greet("World");
"""
try:
    # 解析 JS 代码,生成 AST
    # syntax 是一个字典,包含了 AST 的根节点
    # program 是根节点,body 是一个包含所有顶级语句的列表
    syntax = esprima.parseScript(js_code)
    # 为了方便查看,可以打印 AST (json.dumps 可以美化输出)
    # print(json.dumps(syntax, indent=2))
    # 遍历 AST 来提取信息
    # 我们只关心函数声明和函数表达式
    for node in syntax['body']:
        # 函数声明 (function myFunc() {})
        if node['type'] == 'FunctionDeclaration':
            func_name = node['id']['name']
            params = [p['name'] for p in node['params']]
            print(f"Found Function Declaration: '{func_name}' with params: {params}")
        # 函数表达式 (const myFunc = function() {} 或 const myFunc = () => {})
        elif node['type'] == 'VariableDeclaration':
            for declaration in node['declarations']:
                if declaration['init'] and declaration['init']['type'] in ['FunctionExpression', 'ArrowFunctionExpression']:
                    func_name = declaration['id']['name']
                    params = [p['name'] for p in declaration['init']['params']]
                    func_type = declaration['init']['type']
                    print(f"Found {func_type}: '{func_name}' with params: {params}")
except esprima.Error as e:
    print(f"JS Parsing Error: {e}")

输出:

Found Function Declaration: 'greet' with params: ['name']
Found ArrowFunctionExpression: 'multiply' with params: ['x', 'y']

通过这种方式,你可以进行更复杂的静态分析,比如查找未使用的变量、分析代码依赖关系等。


执行 JavaScript 代码

如果你需要真正地运行 JS 代码并获取其返回值,可以使用 Python 的 JS 运行时环境。

推荐库:PyExecJS

PyExecJS 是一个多后端的 JS 执行器,它不自带 JS 引擎,而是调用你系统上已有的 JS 环境(如 Node.js、PyV8 等)。

安装:

pip install PyExecJS

示例:执行 JS 函数并获取结果

import execjs
# 1. 准备你的 JavaScript 代码
js_code = """
// 定义一个函数
function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}
// 导出一个函数,让 Python 可以调用
// PyExecJS 会寻找 module.exports 或全局变量
exports.calculate = function(n) {
    return fibonacci(n);
};
"""
# 2. 创建一个运行时环境(它会自动寻找可用的 JS 环境,如 Node.js)
# 如果环境变量 `EXECJS_RUNTIME` 已设置,会使用指定的环境
ctx = execjs.compile(js_code)
# 3. 调用 JS 函数并传入参数
# ctx.call("全局函数名", 参数)
# ctx.call("模块名.函数名", 参数)
try:
    result = ctx.call("calculate", 10)
    print(f"The 10th Fibonacci number is: {result}") # 应该输出 55
    # 另一个例子:执行一段代码并获取最后的结果
    another_result = ctx.eval("2 + 2 * 3")
    print(f"The result of '2 + 2 * 3' is: {another_result}") # 应该输出 8
except execjs.ProgramError as e:
    print(f"JavaScript execution error: {e}")
except execjs.RuntimeUnavailableError as e:
    print(f"No JS runtime available. Please install Node.js or another JS engine. Error: {e}")

重要提示PyExecJS 的性能远不如原生代码,对于计算密集型任务,它会很慢,它最适合用于调用一些已有的、逻辑不复杂的 JS 库接口。


总结与选择

方法 工具/库 主要用途 优点 缺点
命令行工具 Babel, Prettier 代码转译、格式化 功能强大、稳定、生态成熟 依赖 Node.js 环境,需要额外安装
静态分析 esprima (Python) 提取代码结构、变量、函数等 纯 Python 实现,无需 JS 环境 功能局限于解析,无法执行代码
代码执行 PyExecJS 运行 JS 代码并获取结果 灵活,能利用现有 JS 库 性能较差,不适合计算密集型任务

如何选择?

  • 如果你的目标是转换或美化 JS 代码首选方法一(Babel/Prettier + subprocess),这是行业标准,最可靠。
  • 如果你的目标是分析 JS 代码的结构(比如写一个静态分析工具)使用方法二(esprima),它能让你在 Python 中深度探索代码。
  • 如果你的目标是运行一小段 JS 代码并获取结果(比如调用一个 JS 库的简单函数)使用方法三(PyExecJS),但要非常注意其性能瓶颈。

对于大多数开发场景,方法一和方法二的组合已经能够覆盖绝大部分需求。

分享:
扫描分享到社交APP
上一篇
下一篇