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
相关产品推荐
相关产品推荐

