Chromedp技术求助:如何正确获取资源大小、尺寸及加载耗时
解决方案:页面性能与资源加载数据获取
一、准确获取资源大小
你遇到的资源大小和Firefox检查器不符、content-length为空的问题,核心原因是:
content-length仅在非分块编码的响应中存在,动态生成内容、流式响应这类分块传输的资源不会返回这个头- Firefox检查器显示的是浏览器实际下载的编码后字节数,你之前的代码字段混用导致数据不准
正确的做法是优先用network.EventLoadingFinished里的EncodedDataLength(这和DevTools显示的大小完全一致),只有当该值为0时,再 fallback 到响应头的content-length:
修正后的资源大小获取逻辑
import ( "strconv" "time" "strings" ) // 用结构体统一存储资源的完整数据 type ResourceData struct { URL string MimeType string RequestStartTime time.Time ResponseTime time.Time FinishedTime time.Time EncodedDataLength int64 ContentLength int64 } // 用RequestID作为键关联不同事件的数据,避免嵌套循环 var resourceMap = make(map[string]ResourceData) chromedp.ListenTarget(ctx, func(ev interface{}) { switch ev := ev.(type) { case *network.EventRequestWillBeSent: // 记录请求开始时间,这是计算加载耗时的起点 if data, ok := resourceMap[ev.RequestID]; ok { data.RequestStartTime = ev.Timestamp.Time() resourceMap[ev.RequestID] = data } else { resourceMap[ev.RequestID] = ResourceData{ RequestStartTime: ev.Timestamp.Time(), URL: ev.Request.URL, } } case *network.EventResponseReceived: // 解析content-length(如果存在) contentLength := int64(0) if clVal, ok := ev.Response.Headers["content-length"]; ok { if clStr, ok := clVal.(string); ok { contentLength, _ = strconv.ParseInt(clStr, 10, 64) } } // 更新响应相关数据 if data, ok := resourceMap[ev.RequestID]; ok { data.ResponseTime = ev.Response.ResponseTime.Time() data.MimeType = ev.Response.MimeType data.ContentLength = contentLength resourceMap[ev.RequestID] = data } else { resourceMap[ev.RequestID] = ResourceData{ URL: ev.Response.URL, MimeType: ev.Response.MimeType, ResponseTime: ev.Response.ResponseTime.Time(), ContentLength: contentLength, } } case *network.EventLoadingFinished: // 记录加载完成时间和实际下载大小 if data, ok := resourceMap[ev.RequestID]; ok { data.FinishedTime = ev.Timestamp.Time() data.EncodedDataLength = ev.EncodedDataLength resourceMap[ev.RequestID] = data } } }) // 遍历输出最终数据 for _, data := range resourceMap { // 优先取EncodedDataLength,这是实际下载的大小 actualSize := data.EncodedDataLength if actualSize == 0 { actualSize = data.ContentLength } fmt.Printf("资源URL: %s\n", data.URL) fmt.Printf("MIME类型: %s\n", data.MimeType) fmt.Printf("实际大小: %d bytes\n", actualSize) }
二、获取特定文件的加载耗时
加载耗时就是请求开始时间到加载完成时间的差值,直接通过结构体中的时间字段计算即可,还能根据URL后缀筛选特定资源:
特定资源加载耗时计算
for _, data := range resourceMap { if !data.RequestStartTime.IsZero() && !data.FinishedTime.IsZero() { loadDuration := data.FinishedTime.Sub(data.RequestStartTime) // 筛选.js、.png这类特定资源 if strings.HasSuffix(data.URL, ".js") || strings.HasSuffix(data.URL, ".png") { fmt.Printf("特定资源[%s]加载耗时: %v\n", data.URL, loadDuration) } } }
三、获取页面加载时间与首次绘制时间
用Chrome的Performance API和页面事件来获取:
- 页面加载总耗时:从导航开始到
load事件触发的时间差 - 首次绘制(FP):通过
performance.getEntriesByType('paint')获取
代码示例
// 获取导航开始时间 var navigationStart time.Time chromedp.Run(ctx, chromedp.Evaluate(`new Date(performance.timing.navigationStart)`, &navigationStart), ) // 获取页面load事件触发时间 var loadEventTime time.Time chromedp.ListenTarget(ctx, func(ev interface{}) { if ev, ok := ev.(*page.EventLoadEventFired); ok { loadEventTime = ev.Timestamp.Time() } }) // 获取首次绘制时间 var firstPaintOffset float64 chromedp.Run(ctx, chromedp.Evaluate(`performance.getEntriesByType('paint')[0].startTime`, &firstPaintOffset), ) firstPaintTime := navigationStart.Add(time.Duration(firstPaintOffset * float64(time.Millisecond))) // 输出结果 fmt.Printf("页面加载总耗时: %v\n", loadEventTime.Sub(navigationStart)) fmt.Printf("首次绘制时间: %v (相对于导航开始)\n", firstPaintTime.Sub(navigationStart))
关键注意事项
- 必须先调用
network.Enable(),否则无法接收到任何网络事件 - 分块传输的资源只能依赖
EncodedDataLength获取实际大小,content-length完全不可靠 - 事件触发顺序不固定(比如请求事件可能早于响应事件),用
RequestID作为键的map是最可靠的关联方式
内容的提问来源于stack exchange,提问作者Angelo van Cleef
相关产品推荐
相关产品推荐

