javascript - 如何在 JavaScript 代码中获取 JavaScript 对象?

标签 javascript node.js web-crawler puppeteer

TL;博士

我要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 .但我认为这对解决这个问题没有帮助。

最佳答案

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

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

相反,请尝试 在页面的上下文中进行尽可能多的解析 .你甚至可以作恶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 中重建解析器。只需使用浏览器。在你的情况下,基本上有两种方法可以做到这一点。
  • 在页面中注入(inject)代理函数,伪造一些内置函数(推荐)
  • 使用 JSON.parse 解析客户端 (!) 上的数据, 正则表达式或 eval (仅在确实需要时进行评估)


  • 选项 1:将代理功能注入(inject)页面

    在这种方法中,您正在用自己的“假功能”替换 native 浏览器功能。例子:

    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 的项目插入数组时,会触发特殊条件。要将此函数注入(inject)页面,您可以使用 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 运行不受信任的代码。

    关于javascript - 如何在 JavaScript 代码中获取 JavaScript 对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55567952/

    相关文章:

    javascript - Fancybox box 在我将它上传到 webhost 但在 localhost 上工作后不工作

    mysql - nodejs mysql - 如何在现实项目中实现 pooledConnection?

    ruby - 用于实时/并行 HTTP 爬虫的良好库/平台?

    javascript - 如何使用 Promise 处理错误

    javascript - 无法使用 Node js 为 gcm 创建 notification_key

    Ruby Mechanize 解析元标签

    input - 在Scrapy中获取输入值

    javascript - React 上下文初始状态正在发生变化 - JavaScript/React

    对象中的 JavaScript 函数执行

    javascript - 调用 axios get forEach 中的方法