为了账号安全,请及时绑定邮箱和手机立即绑定

通过 JS 应用多个 RegExp .match() 时如何阻止页面死亡?

通过 JS 应用多个 RegExp .match() 时如何阻止页面死亡?

慕姐4208626 2023-01-06 09:35:36

想要从页面中挑选出所有的 JSON 块。代码是:


for (el of document.querySelectorAll(['div[class*="json"]', 'script[type="text/javascript"]', 'script[type="application/json"]'])) {

    if (el.innerHTML) {

        let matches = el.innerHTML.match(/({(?:\s|\n)*["'](?:\s|\n|.)*?["']:(?:\s|\n)*["'[{](?:\s|\n|.)+?})(?:;|$)/g)

        console.log(matches)

    }

}

通常,它在页面上有 2-5 个元素。问题是,在这样的查询之后,页面就停止响应(甚至 Chrome 开发工具搜索也不起作用)。


我假设查询可能会导致一些负载(而 CPU 未显示),所以问题是:可能是什么问题以及如何优化表达式以降低此负载?


PS 如果操作需要时间也没关系,所以也许有一种方法可以等待每个操作完成后再开始另一个操作,以分配负载?


查看完整描述

4 回答

?
慕妹3242003

TA贡献1546条经验 获得超6个赞

正如其他答案所指出的那样,这是一个复杂的正则表达式,可以针对网页源代码的大部分执行。一种可能的解决方法包括使用 Promises 或 Web Worker 来利用浏览器的异步功能来解冻 UI,但我认为您对具体解决此问题不感兴趣。似乎您正在尝试抓取网络数据,因此在此过程中 UI 是否被冻结都不会产生影响。

我的建议是分而治之。让我们分别处理每个选择器和它们。

script[type="application/json"]

这似乎很简单。您可能只需要获取其内部内容,瞧,您就有了一个 JSON。

div[class*="json"]

我相信这是一种非标准的方式来指定网页的初始状态。它可能会落入与上述相同的解析器。您可能只需要获取其内部文本并尝试将其解析为 JSON。

script[type="text/javascript"]

这是最棘手的部分,因为我们不再处理 JSON,而是处理可能包含或不包含 JSON 数据的可执行 JavaScript。对于这个,您可以使用简化的正则表达式,但我会更进一步并提出其他建议。

您可以检查 JavaScript 对象并尝试将它们转换为 JSON。这可以通过内置 API 或使用 JavaScript 解析器轻松完成(例如,如果您使用的是 Scrapy 之类的东西,则可以使用 js2py)。我不确定这项任务的性能,但我相信它会比复杂的正则表达式更快,可能值得一试。

它适用于类似的情况,var initialState = { ... };但在尝试处理像 . 这样的内联值时可能会带来一些挑战hypedFramework.init({ ... })。在后一种情况下,您可能需要一些 JavaScript 解析来隔离这些值。但这仍然是可能的。快速浏览一下https://esprima.org/demo/parse.html,看看它是如何从函数参数中提取对象表达式的。


查看完整回答
反对 回复 2023-01-06
?
HUX布斯

TA贡献1612条经验 获得超6个赞

正则表达式运行时是非多项式的,这意味着对于复杂的模式,它可能需要一段时间!!你有两个选择;要么在主线程之外运行正则表达式,以确保页面保持响应,要么找到一种更有效的方法来实现您尝试使用正则表达式做的事情,或者至少找到一个更好的(CPU 密集度较低的)正则表达式;


对于第一个选择,您可以使用web workers,这是一个干净的解决方案,或者您可以使用 kinda hacky workaround 并使用setTimeout()或使用 promise;但我强烈建议您使用网络工作者,如果它的浏览器支持适合您的用例(谁关心 IE?)


下面是一个Promise利用 CPU 密集型任务远离主线程的示例:


    const inefficientPattern = /({(?:\s|\n)*(?:"|')(?:\s|\n|.)*?(?:"|'):(?:\s|\n)*(?:"|'|\[|{)(?:\s|\n|.)+?})(?:;|$)/g;

    for (let el of document.querySelectorAll(['div[class*="json"]', 'script[type="text/javascript"]', 'script[type="application/json"]'])) {

      if (el.innerHTML) {

        new Promise( function (resolve, reject) {

          resolve(el.innerHTML.match(inefficientPattern))

        }).then( matches => {

          console.log(matches)

        })

      }

    }

有趣的东西:立即执行承诺回调;我错了 👍 查看这个答案:Are JavaScript Promise asynchronous?


查看完整回答
反对 回复 2023-01-06
?
小唯快跑啊

TA贡献1619条经验 获得超2个赞

我认为您可以使用 setTimeout 将 for 循环拆分为多个迭代。这样浏览器就可以有时间在每次调用繁重的正则表达式解析之间进行渲染。


const results = [];

let checkAll = (elements) => {

  results.push(checkOne(elements[0]));

  if (elements.length > 1) {

    setTimeout(0, () => checkAll(elements.slice(1)));

  } else {

    // do something with results ...

  }

}


查看完整回答
反对 回复 2023-01-06
?
紫衣仙女

TA贡献1573条经验 获得超15个赞

尝试在 try/catch 组中使用 JSON.parse,就像这样


for (el of document.querySelectorAll(['div[class*="json"]', 'script[type="text/javascript"]', 'script[type="application/json"]'])) {

    if (el.innerHTML) {

        try {

           let matches = JSON.parse(el.innerHTML)

           console.log(JSON.stringify(matches))

        catch (err) {

           console.log('element was not a json')

        }

    }

}


如果元素的内容确实是有效的 JSON 语法,它将执行而不会抛出错误,否则你可以在 catch 组中做一些特殊的事情


精确

这不会在元素中找到 JSON 位,整个元素需要是有效的 JSON 语法


查看完整回答
反对 回复 2023-01-06
  • 4 回答
  • 0 关注
  • 13 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信