【问题标题】:How to stop page from dying when applying multiple RegExp .match() through JS?通过 JS 应用多个 RegExp .match() 时如何阻止页面死机?
【发布时间】:2020-12-02 15:35:28
【问题描述】:

想要从页面中挑选出所有的 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 没有显示它),所以问题是:可能是什么问题以及如何优化表达式以降低负载?

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

【问题讨论】:

  • 您可以在 try/catch 组中使用 JSON.parse。如果 JSON.parse 抛出错误,则不是 json
  • 为什么要在 JavaScript 中寻找 JSON? JSON 是 JavaScript 对象/数组语法的子集,但它们并不完全相同。
  • 即使你找到了,如果它使用了 JSON 中不允许的 JS 特性,例如字符串周围的单引号,或者不是的对象键,你将无法解析它引用。
  • @Barmar 因为 JSON 并不总是存储在 type="application/json" 脚本中,它通常存储为字典,保存到变量中,或者只是在 div 中。从技术上讲,问题不是JSON,而是关于页面加载以及如何优化它:)
  • 我在 regex101.com 上试过你的正则表达式。它与有效 JSON 格式的 ["foo"] 不匹配。看起来它只匹配 JSON 对象,而不是数组。 OTOH,它匹配无效的 JSON,如 {"x": {"a": ["foo"], "b":}

标签: javascript json regex


【解决方案1】:

正如其他答案所指出的,这是一个复杂的正则表达式,可以针对大部分网页源代码执行。一种可能的解决方法包括利用浏览器的异步功能,使用 Promises 或 Web Workers 来解冻 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,看看它是如何从函数参数中提取对象表达式的。

【讨论】:

    【解决方案2】:

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

    你可以使用web workers作为第一选择,这是一个干净的解决方案,或者你可以做一些hacky解决方法并使用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?

    【讨论】:

    • 使用 Promises 提供相同的结果,所以是的。目前,使用 web worker 感觉是解决方案,但也许我可以先优化一下表达方式,谢谢 :)
    • @sortas 嗯很有趣;
    【解决方案3】:

    尝试在 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 语法

    【讨论】:

    • 我的意思是,JSON 可能位于脚本的中间,例如,分配给某个变量,这样它就不起作用了。很好的解决方案,只是它会丢失一些 JSON 数据,所以不适用。
    • @sortas 这听起来像是一些主要的逻辑问题。想到单一职责。但是没有足够的信息说明您为什么首先需要这样做。
    • @AustinTFrench 不知道我明白了,抱歉 :) 我想从页面获取 JSON 数据。有时它只是带有清晰 JSON 的 <script>。有时它是一个脚本,其中 JSON 应用于某个变量。有时是div,其中 JSON 存储为内部文本。所以,应用 RegExp 感觉是最稳定的解决方案,但我遇到了页面死亡的问题,所以寻求一些建议:)
    • JSON:JavaScript 对象表示法。你是说你也在为 JSON 解析脚本标签???或者有运行的脚本(在通过脚本标签加载的脚本中)返回一个 JS 对象?无论如何,这感觉就像一场灾难。然后添加一个新问题,正则表达式。
    【解决方案4】:

    我认为您可以使用 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 ...
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-07-09
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      • 2014-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-05
      相关资源
      最近更新 更多