【问题标题】:How to get JavaScript object in JavaScript code?如何在 JavaScript 代码中获取 JavaScript 对象?
【发布时间】:2019-08-29 06:42:43
【问题描述】:

TL;DR

我想要 parseParameter 像下面的代码那样解析 JSON。 someCrawledJSCode 是爬取的 JavaScript 代码。

const data = parseParameter(someCrawledJSCode);
console.log(data);  // data1: {...}

问题

我正在用 puppeteer 抓取一些 JavaScript 代码,我想从中提取一个 JSON 对象,但我不知道如何解析给定的 JavaScript 代码。

抓取的 JavaScript 代码示例:

const somecode = 'somevalue';
arr.push({
  data1: {
    prices: [{
      prop1: 'hi',
      prop2: 'hello',
    },
    {
      prop1: 'foo',
      prop2: 'bar',
    }]
  }
});

在这段代码中,我想得到prices 数组(或data1)。

我做了什么

我尝试将代码解析为 JSON,但它不起作用。所以我搜索了解析工具,得到了Esprima。但我认为这对解决这个问题没有帮助。

【问题讨论】:

  • 有一个可用的解决方案,但它非常不安全,尤其是当您通过爬网从外部源获取 js 时。您可以尝试使用eval
  • 我想过eval,但我要在服务器端运行这段代码,这太危险了。所以我想得到另一个解决方案。感谢您的回复。

标签: javascript node.js web-crawler puppeteer


【解决方案1】:

简答:不要(重新)在 Node.js 中构建解析器,而是使用浏览器

如果您仍然使用 puppeteer 进行爬网,我强烈建议您不要在 Node.js 中评估或解析爬网数据。当您使用 puppeteer 时,您已经拥有了一个浏览器,其中包含用于在另一个进程中运行的 JavaScript 代码的出色沙箱。为什么要冒这种隔离风险并在 Node.js 脚本中“重建”解析器?如果您的 Node.js 脚本中断,您的整个脚本都会失败。在最坏的情况下,当您尝试在主线程中运行不受信任的代码时,您甚至可能会使您的机器面临严重的风险。

相反,请尝试在页面上下文中进行尽可能多的解析。你甚至可以在那里打一个 evil eval 电话。有可能发生最坏的情况吗?您的浏览器挂起或崩溃。

示例

想象一下下面的 HTML 页面(非常简化)。您正在尝试读取推入数组的文本。您拥有的唯一信息是有一个附加属性id 设置为target-data

<html>
<body>
  <!--- ... -->
  <script>
    var arr = [];
    // some complex code...
    arr.push({
      id: 'not-interesting-data',
      data: 'some data you do not want to crawl',
    });
    // more complex code here...
    arr.push({
      id: 'target-data',
      data: 'THIS IS THE DATA YOU WANT TO CRAWL', // <---- You want to get this text
    });
    // more code...
    arr.push({
      id: 'some-irrelevant-data',
      data: 'again, you do not want to crawl this',
    });
  </script>
  <!--- ... -->
</body>
</html>

错误代码

这是一个简单的示例,您的代码现在可能是什么样子:

await page.goto('http://...');
const crawledJsCode = await page.evaluate(() => document.querySelector('script').innerHTML);

在此示例中,脚本从页面中提取 JavaScript 代码。现在我们有了页面中的 JavaScript 代码,我们“只”需要解析它,对吧?好吧,这是错误的方法。不要尝试在 Node.js 中重建解析器。只需使用浏览器。在你的情况下,基本上有两种方法可以做到这一点。

  1. 在页面中注入代理函数,伪造一些内置函数(推荐)
  2. 使用 JSON.parse、正则表达式或 eval 解析客户端 (!) 上的数据(仅在确实需要时进行评估)

选项1:将代理函数注入页面

在这种方法中,您正在用自己的“假函数”替换本机浏览器函数。示例:

const originalPush = Array.prototype.push;
Array.prototype.push = function (item) {
    if (item && item.id === 'target-data') {
        const data = item.data; // This is the data we are trying to crawl
        window.exposedDataFoundFunction(data); // send this data back to Node.js
    }
    originalPush.apply(this, arguments);
}

这段代码用我们自己的函数替换了原来的Array.prototype.push函数。一切正常,但是当将具有我们目标 id 的项目推入数组时,会触发特殊条件。要将此功能注入页面,您可以使用page.evaluateOnNewDocument。要从 Node.js 接收数据,您必须通过 page.exposeFunction 向浏览器公开一个函数:

// called via window.dataFound from within the fake Array.prototype.push function
await page.exposeFunction('exposedDataFoundFunction', data => {
    // handle the data in Node.js
});

现在,页面代码的复杂程度并不重要,它是发生在某个异步处理程序中还是页面是否更改了周围的代码。只要目标数据将数据推入数组中,我们就会得到它。

您可以使用这种方法进行大量抓取。检查数据的处理方式,并将处理数据的低级函数替换为您自己的代理版本。

选项 2:解析数据

让我们假设第一种方法由于某种原因不起作用。数据在某个脚本标签中,但您无法通过伪函数获取。

然后您应该解析数据,但不是在您的 Node.js 环境中。在页面上下文中执行此操作。您可以运行正则表达式或使用JSON.parse。但是在将数据返回到 Node.js 之前执行此操作。这种方法的好处是,如果您的代码由于某种原因使您的环境崩溃,那么崩溃的不是您的主脚本,而是只是您的浏览器。

给出一些示例代码。我们不再运行原始“错误代码”示例中的代码,而是将其更改为:

const crawledJsCode = await page.evaluate(() => {
    const code = document.querySelector('script').innerHTML; // instead of returning this
    const match = code.match(/some tricky regex which extracts the data you want/); // we run our regex in the browser
    return match; // and only return the results
});

这只会返回我们需要的部分代码,然后可以在 Node.js 中进一步处理。


无论您选择哪种方法,这两种方法都比在主线程中运行未知代码更好、更安全。如果您绝对必须在 Node.js 环境中处理数据,请使用正则表达式,如 trincot 的答案所示。您应该从不使用 eval 运行不受信任的代码。

【讨论】:

  • 感谢新方法的建议。我通过使用evaluate 并返回arr 解决了这个问题。
【解决方案2】:

我认为使用像 Esprima 这样的 AST 生成器或其他 AST 工具是阅读和使用源代码的最简单方法。

老实说,如果您知道如何运行 Esprima,并从源代码生成“抽象语法树”,您会发现读取生成的表示您刚刚解析的代码的树结构非常容易和简单,并且您会发现阅读信息非常容易,并将其转换为您想要的任何内容。

起初可能看起来令人生畏,但老实说,事实并非如此。您会感到惊讶:像 Esprima 这样的 AST 工具的制作目的与您尝试做的类似,目的是让工作变得简单。

AST 工具源于对如何阅读和操作源代码的多年研究,因此我强烈推荐它们。

试试看!

为了帮助您了解各种 AST 的样子,您可以查看 https://astexplorer.net。它对于了解各种工具的 AST 树结构的外观非常有用。

哦,最后一件事!为了遍历 AST 树,您可以使用 https://github.com/estools/estraverse 之类的东西。它会让生活变得轻松。

【讨论】:

    【解决方案3】:

    刮擦会很丑。通过对您尝试解析的字符串的一些假设,您可以:

    1. 提取推入数组的部分
    2. 将该字符串转换为有效的 JSON:

      • 用双引号替换字符串文字的定界单引号;
      • 不带引号的属性名称用双引号括起来;
      • 删除最后一个属性后面的逗号

    要可靠地做到这一点,您必须编写一个与 JSON 解析器一样复杂的解析器,但有一些假设,它可能可以简化为:

    // Sample data
    var someCrawledJSCode = `
    const somecode = 'somevalue';
    arr.push({
      data1: {
        prices: [{
          prop1: 'hi',
          prop2: 'hello',
        },
        {
          prop1: 'foo',
          prop2: 'bar',
        }]
      }
    });`;
    
    
    var obj;
    var notJson = someCrawledJSCode.replace(/\.push\(([^]*?)\)/, (_, notJson) => {
        // Try to turn the string into valid JSON:
        // 1. string literals should not be enclosed in single, but double quotes
        // 2. property names should be enclosed in double quotes
        // 3. there should be no trailing comma after the last property
        var json = notJson.replace(/'((\\.|[^\\'])*)'/g, '"$1"')
                          .replace(/(\w+):/g, '"$1":')
                          .replace(/,\s*}/g, "}");
        obj = JSON.parse(json);
    });
    console.log(obj);

    事情仍然可能出错,但至少你没有使用eval。例如,如果您有一个内容匹配(\w+): 的字符串文字,那么上面的内容将更改该字符串。当然可以使解析更可靠...

    【讨论】:

    • 修复了捕获组的问题。
    猜你喜欢
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    相关资源
    最近更新 更多