You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Puppeteer生产环境JS堆内存溢出求助(HTML分页代码异常)

HTML分页函数的生产环境内存溢出问题

我编写了一个JavaScript函数,可按指定字符数将HTML字符串拆分为多个页面,核心是保证分页位置处于标签开闭之间时HTML仍保持有效:

function paginate(text, pageLength) {  
  // strip outter div. This would screw things up
  text = text.replace('<div>', '')
  text = text.replace('</div>', '')

  const template = document.createElement('template');
  template.innerHTML = text;
  const fragment = template.content; // This is a DocumentFragment
  
  const nodes = fragment.childNodes
  const rawNodesArray = Array.from(nodes)

  const nodesArray = []
  // break text nodes by words
  rawNodesArray.map( (node) => {
    const fullText = node.textContent
    const wordList = fullText.match(/\S+\s?/g)
    if (wordList) { 
      wordList.map( (word) => {
        const newNode = node.cloneNode(true)
        newNode.textContent = word
        nodesArray.push(newNode)
      })
    }
  })

  const pages = []
  let page = []
  let pageCharCount = 0
  while (nodesArray.length) {
    // dont include leading whitespace if its the start of the page
    const nextNodeLength = nodesArray[0].textContent.trimEnd().length
    const lengthWithNextChunk = pageCharCount + nextNodeLength
  
    if ( lengthWithNextChunk <= pageLength ) {
      const nodeToAdd = nodesArray.shift()
      page.push(nodeToAdd) // add node to current page list
      pageCharCount = page.reduce( (sum, node) => {
        return sum + node.textContent.length
      }, 0) // update count and scrap corresponding text chunk
    } else if (nextNodeLength > pageLength) {
      const node = nodesArray.shift()
      const chunk = node.textContent
      const hyphen = '' //optional
      const remainingChars = pageLength - pageCharCount
      const appendToPage = (remainingChars - hyphen.length > 0)
      const spliceIndex = (appendToPage) ? remainingChars - hyphen.length  : pageLength - hyphen.length
      const firstHalf = chunk.slice(0, spliceIndex) + hyphen
      const secondHalf = chunk.slice(spliceIndex)
      const clonedNode = node.cloneNode(true)
      node.textContent = firstHalf
      clonedNode.textContent = secondHalf

      if (appendToPage) {
        pageCharCount += node.textContent.length
        page.push(node)

        nodesArray.unshift(clonedNode)
      } else {
        pages.push(page)
        page = []
        pageCharCount = 0
        nodesArray.unshift(node, clonedNode)
      }
    } else {
      pages.push(page)
      page = []
      pageCharCount = 0
    }  
  }
  if (page.length) {
    pages.push(page)
  }

  const template_pages = pages.map( (page) => {
    const tpl = document.createElement('template');

    for(let i=0; i<page.length; i++) {
      tpl.content.appendChild(page[i])
    }
    return tpl
  })

  const html_pages = template_pages.map( (tpl) => {
    tpl.innerHTML = tpl.innerHTML.trim()
    // This fixes pages like <em>foo </em>
    const lastChild = tpl.lastChild
    if (lastChild) {
      lastChild.textContent = lastChild.textContent.trimEnd()
    }
    return tpl.innerHTML
  })

  return html_pages
}

export default paginate

该函数在以下场景测试运行正常:

  • Vitest测试套件
  • 桌面Chrome浏览器
  • 本地Puppeteer实例(无头Chrome)

但在生产环境的Puppeteer实例中测试时,出现以下错误:

2022-11-15T23:27:39.993980+00:00 app[web.1]: <--- JS stacktrace --->
2022-11-15T23:27:39.993980+00:00 app[web.1]: 
2022-11-15T23:27:39.993981+00:00 app[web.1]: ==== JS stack trace =========================================
2022-11-15T23:27:39.993981+00:00 app[web.1]: 
2022-11-15T23:27:39.993981+00:00 app[web.1]: 0: ExitFrame [pc: 0x242aff6dbf1d]
2022-11-15T23:27:39.993982+00:00 app[web.1]: 1: StubFrame [pc: 0x242aff6dd4a6]
2022-11-15T23:27:39.993982+00:00 app[web.1]: Security context: 0x3876ddb9e6c1 <JSObject>
2022-11-15T23:27:39.993984+00:00 app[web.1]: 2: /* anonymous */ [0x3cf2c0102b69] [/app/node_modules/winston/lib/winston/common.js:~321] [pc=0x242aff9713bd](this=0x233d4e1c4959 <Object map = 0x2d2eefa874d9>,obj=115,key=0x385eb8f429e1 <String[5]: 30444>)
2022-11-15T23:27:39.993985+00:00 app[web.1]: 3: /* anonymous */ [0x3cf2c0102b69] [/app/node_modules/winston/lib/winston/common.js:~321] [pc=0x242aff971...
2022-11-15T23:27:39.993985+00:00 app[web.1]: 
2022-11-15T23:27:39.993985+00:00 app[web.1]: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
2022-11-15T23:27:39.994847+00:00 app[web.1]: 1: 0x8fb090 node::Abort() [node]
2022-11-15T23:27:39.995408+00:00 app[web.1]: 2: 0x8fb0dc  [node]
2022-11-15T23:27:39.996258+00:00 app[web.1]: 3: 0xb0336e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
2022-11-15T23:27:39.997489+00:00 app[web.1]: 4: 0xb035a4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
2022-11-15T23:27:39.999339+00:00 app[web.1]: 5: 0xef7602  [node]
2022-11-15T23:27:40.000024+00:00 app[web.1]: 6: 0xef7708 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [node]
2022-11-15T23:27:40.003356+00:00 app[web.1]: 7: 0xf037e2 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
2022-11-15T23:27:40.004037+00:00 app[web.1]: 8: 0xf04114 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
2022-11-15T23:27:40.004744+00:00 app[web.1]: 9: 0xf06d81 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [node]
2022-11-15T23:27:40.005418+00:00 app[web.1]: 10: 0xed0204 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [node]
2022-11-15T23:27:40.006141+00:00 app[web.1]: 11: 0x11702de v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [node]
2022-11-15T23:27:40.006145+00:00 app[web.1]: 12: 0x242aff6dbf1d
2022-11-15T23:27:40.258485+00:00 heroku[web.1]: State changed from up to crashed
2022-11-15T23:30:58.444906+00:00 heroku[web.1]: State changed from crashed to down

运行环境:Node v16.x,部署在Heroku 2X dyno(配备1GB内存)。

该问题仅在Heroku生产环境中100%复现,其他环境均无法重现。已尝试以下方案:

  • 排查内存泄漏:观察Chrome内存时间线未发现内存持续堆积
  • 调整Node垃圾回收参数:如node --optimize_for_size --max_old_space_size=900 src/index.js,尝试不同--max_old_space_size值均无效
  • 升级至最大Heroku dyno(14GB内存):问题仍存在

我的代码中是否存在潜在问题?为何仅该环境出现此错误?我还遗漏了哪些排查方向?


内容的提问来源于stack exchange,提问作者emersonthis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 04:30:50