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

chromedp抓取HTML超时及性能优化问题求助

优化chromedp抓取HTML生成PDF的性能方案

问题背景

我们使用chromedp从URL抓取HTML并生成PDF,单操作耗时3-4秒。该功能用于定时任务(cron)向客户发送带PDF附件的邮件,现有25000个商户,每个商户对应约100个客户,总客户量达250万。

当前定时任务每次处理1个商户、为其100个客户发送邮件,循环抓取HTML时单条邮件耗时5-6秒,还频繁出现context deadline exceeded和websocket url timeout reached错误。已采用带缓冲通道的Worker Pool运行任务,现需缩短chromedp的抓取耗时。

现有示例代码

func CaptureHtml()(template.HTML, error){
    ctx, cancel := chromedp.NewContext(context.Background())
    defer cancel()

    url := "url_from_where_you want to capture html"

    var dataHTML string
    err := chromedp.Run(ctx, chromedp.Navigate(url), RunWithTimeOut(&ctx, 30, chromedp.Tasks{
        chromedp.WaitVisible("#root", chromedp.ByQuery)}))
   if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Page loaded successfully")

    // Sleep for a brief period to ensure the data rendering is complete (adjust timing as needed)
    time.Sleep(2 * time.Second)

    // Capture the HTML content of the element containing the rendered API data
    if err := chromedp.Run(ctx,
        chromedp.OuterHTML("#root", &dataHTML, chromedp.NodeVisible),
    ); err != nil {
        log.Fatal(err)
    }
    CreatePdfInBytes(ctx, dataHTML)
    return template.HTML(htmlContent), err
}

func CreatePdfInBytes(ctx context.Context, html string){
    var wg sync.WaitGroup
    var pdfBuf []byte

    navigate := chromedp.Navigate("about:blank")

    eventLoader := chromedp.ActionFunc(func(ctx context.Context) error {
        loaderctx, cancel := context.WithCancel(ctx)
        chromedp.ListenTarget(loaderctx, func(event interface{}) {
            if _, ok := event.(*page.EventLoadEventFired); ok {
                wg.Done()
                cancel()
            }
        })
        return nil
    })

    setDocContent := chromedp.ActionFunc(func(ctx context.Context) error {
        frameTree, err := page.GetFrameTree().Do(ctx)
        if err != nil {
            return err
        }
        return page.SetDocumentContent(frameTree.Frame.ID, html).Do(ctx)

    })

    loaderWg := chromedp.ActionFunc(func(ctx context.Context) error {
        wg.Wait()
        return nil
    })

    genPdf := chromedp.ActionFunc(func(ctx context.Context) error {
        buf, _, err := page.PrintToPDF().WithMarginTop(0.3).WithMarginBottom(0.3).WithPrintBackground(true).Do(ctx)
        if err != nil {
            return err
        }
        pdfBuf = buf
        return nil
    })

    wg.Add(1)
    err := chromedp.Run(ctx, navigate, eventLoader, setDocContent, loaderWg, genPdf)
    if err != nil {
        fmt.Println(err)
    }

    file, err := os.Create("abc.pdf")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    // Write the PDF data to the file
    _, err = file.Write(pdfBuf)
    if err != nil {
        panic(err)
    }
    return 
}

核心优化方案

1. 复用chromedp上下文,避免重复初始化

当前每次调用CaptureHtml都新建浏览器上下文,实例启动销毁是主要耗时点。可全局维护上下文池,复用已启动的浏览器实例:

  • 初始化时创建固定数量的上下文(根据CPU核心数调整)
  • 用通道实现上下文的获取与归还,避免重复创建销毁

示例修改思路:

var ctxPool = make(chan context.Context, 4) // 按需调整池大小

func init() {
    for i := 0; i < cap(ctxPool); i++ {
        ctx, cancel := chromedp.NewContext(context.Background())
        defer cancel() // 程序退出时清理
        ctxPool <- ctx
    }
}

func CaptureHtml()(template.HTML, error){
    ctx := <-ctxPool
    defer func() { ctxPool <- ctx }() // 用完归还到池
    // ... 后续原有逻辑
}

2. 移除固定time.Sleep,精准等待渲染完成

硬编码的time.Sleep(2*time.Second)完全可替换为精准等待逻辑:

  • 监听Network.EventLoadingFinished事件,等待所有异步请求完成
  • 或等待标识渲染完成的元素状态变化

替换示例:

// 替换time.Sleep,等待所有网络请求结束
var networkIdle bool
err := chromedp.Run(ctx,
    chromedp.Navigate(url),
    chromedp.WaitVisible("#root", chromedp.ByQuery),
    chromedp.ActionFunc(func(ctx context.Context) error {
        listenerCtx, cancel := context.WithCancel(ctx)
        defer cancel()
        chromedp.ListenTarget(listenerCtx, func(event interface{}) {
            if _, ok := event.(*network.EventLoadingFinished); ok {
                // 等待短时间确认无新请求,按需调整
                time.AfterFunc(500*time.Millisecond, func() {
                    networkIdle = true
                    cancel()
                })
            }
        })
        for !networkIdle {
            time.Sleep(100*time.Millisecond)
        }
        return nil
    }),
)

3. 合并HTML抓取与PDF生成流程,减少页面跳转

当前流程先抓取HTML再跳转至空白页生成PDF,两次页面操作可合并:

  • 在原页面渲染完成后直接调用page.PrintToPDF生成PDF,跳过二次页面加载
  • 若需保留HTML抓取,可在生成PDF的同时完成HTML捕获,减少chromedp.Run调用次数

4. 优化超时设置与错误处理

  • 给chromedp上下文设置全局合理超时,避免单个任务阻塞Worker Pool
  • 替换log.Fatal和panic为优雅错误返回,避免任务直接崩溃
  • 初始化上下文时增加超时参数解决websocket超时问题:
ctx, cancel := chromedp.NewContext(
    context.Background(),
    chromedp.WithDialTimeout(10*time.Second),
    chromedp.WithExecTimeout(30*time.Second),
)

5. 批量处理同商户客户请求

同商户的客户PDF内容差异通常仅为客户信息,可:

  • 预加载商户基础页面,通过chromedp的Eval功能动态替换客户数据后生成PDF
  • 避免重复加载整个页面,大幅减少渲染耗时

6. 启用无头浏览器性能优化参数

初始化chromedp时添加浏览器启动参数提升渲染速度:

ctx, cancel := chromedp.NewContext(
    context.Background(),
    chromedp.WithArgs(
        "--headless=new", // 新版无头模式性能更优
        "--disable-gpu",
        "--no-sandbox",
        "--disable-dev-shm-usage", // 避免内存不足
        "--disable-extensions",
        "--disable-images", // 若图片不影响PDF内容可禁用加载
    ),
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:29:53