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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 19:55:25