如果你在Chrome开发者工具的Network面板里排查过线上问题大概率有过这样的经历问题刚刚复现你赶紧截图把状态码、耗时、请求URL发给后端同学结果对方回一句我这边日志里没收到这条请求。截图只能证明看上去发生了什么而HAR文件能记录下来的是那一次请求从浏览器里出发、到最后返回或断开之间每一步的真实履历。HAR全称是HTTP Archive本质上是一份JSON档案文件。Chrome开发者工具里的Network面板可以把整个录制周期内所有网络请求的细节——请求头、响应头、Cookie、DNS阶段耗时、连接建立时间、等待时间、接收时间、服务器IP甚至连接ID——按照规范序列化保存下来。这就像给一次请求装了一台行车记录仪之后无论谁来问当时到底发生了什么都不用再靠记忆和截图猜。这篇文章会从零讲透HAR文件为什么需要它、Chrome里怎么导出、内部每个字段是什么意思、怎么用它定位真实问题以及哪些坑我踩过之后希望你绕开。适合经常跟网络请求打交道的人前端开发、接口联调、性能优化、运维排障都值得收藏一份。1. 为什么需要HAR一次请求的完整行车记录仪1.1 截图永远追问不出那1.2秒里发生了什么先讲个真实场景。业务方反馈某个接口偶尔要等十几秒才返回甚至直接报错。后端查日志说根本没收到这条请求前端在Network面板里截了个图发过来请求状态是红色耗时显示1.2秒看起来确实发了。但没有人说得清这1.2秒里到底发生了什么。是DNS解析慢了TCP连接根本没建起来请求发出去了但响应没回来还是响应回来了但被浏览器拦了截图只能告诉你结果HAR能告诉你过程。Chrome开发者工具里导出HAR之后你能看到这条请求的完整请求头包括Authorization、Cookie这些容易被忽略的字段完整响应头和服务端返回的Server标识每个阶段的耗时阻塞、DNS、连接、发送、等待、接收请求复用的是哪个TCP连接目标服务器IP是多少是否走了缓存响应体实际多大传输字节多少当前端说发了、后端说没收到这类争论出现时HAR是目前最接近现场的东西。1.2 HAR的本质一段按时间轴序列化的JSON档案HAR不是某种私有格式它就是一份普通的JSON文件。文件后缀习惯用.har但你用任意文本编辑器打开都能看到完整内容。HAR的格式有W3C的规范版本目前浏览器导出的基本是1.2版。规范里定义了log、entries、request、response、timings这些结构Chrome导出时严格照着这套规范写。所以HAR能做到跨工具、跨浏览器理解——你用Chrome导出的HAR交给Firefox、Safari或者命令行工具都能解析出同样的语义。打个比方截图是事故现场的照片HAR是黑匣子的数据记录。照片能看出撞了黑匣子能告诉你刹车是什么时候踩的、油门什么时候松的、方向打了多少度。1.3 适用场景与它帮不上忙的地方HAR最擅长回答的问题是在某一段时间窗口内浏览器到底发起过哪些网络活动每一步花了多少时间成功还是失败。我日常主要用它做这几件事线上接口偶发失败需要完整证据链给后端或网络组页面加载慢要分析资源加载的瓶颈在哪一段跨团队协作时把一次完整请求原样转交给负责服务端的人换电脑、换网络环境后复现某个旧问题需要回放当时的请求但HAR也有明显边界。它记录不了WebSocket建立之后的后续帧记录不了底层TCP层收到的RST还是FIN也记录不了浏览器不做网络请求时的内部调度细节。这些情况下需要Chrome的NetLog或者其他抓包工具第5节我会专门讲这条升级路径。2. Chrome里导出HAR从录制到保存的完整操作2.1 录制前先做这三个动作很多人直接打开Network面板问题复现完就右键导出结果发现关键请求早就被后续请求顶掉了或者跨页面跳转后之前的记录全没了。我的习惯是复现前先把录制环境清干净。第一步打开Chrome开发者工具切到Network面板。Windows可以用F12或CtrlShiftIMac是CmdOptI。第二步清空面板里已有的历史记录。点击Network面板工具栏左侧的清除按钮就是圆形带一条斜杠的那个图标。这一步很关键——旧记录会干扰你后续对时间线的判断。第三步根据复现场景决定是否勾选Preserve log。如果问题涉及页面跳转比如登录页跳主页之后才发起接口请求不勾的话跳转瞬间之前的请求记录会被清掉。勾上Preserve log才能保留整个跳转链路的完整记录。另外有个细节如果只想复现某个特定接口建议在Network面板上方的Filter过滤框里输入URL关键字然后再操作。这样导出的HAR里只包含目标请求文件小、发给别人也更好排查。我见过有人把一整屏无关请求的HAR发出来几百个entry里扒一个出问题的效率非常低。2.2 导出带不带响应体差别很大录制完成后在Network面板任意一条请求上右键你会看到两个关键选项Save all as HAR with content中文版大概是另存为HAR并包含内容之类Save as HAR with content针对单条请求注意新版Chrome的Network工具栏还有一个导出按钮下载图标点击效果等同Save all as HAR with content。with content的意思是把每个响应体内容也写进HAR文件的content.text字段里。不带content导出的HAR只保留请求头、响应头和元数据体积小很多但看不到响应正文带content的HAR几乎等于把当时服务器返回的完整数据都留了备份。多数排查场景建议导带content的版本。但如果你只是为了分析这个请求为什么慢或者有没有发出这种元数据层面的问题不带content的版本就够用文件小、传输更方便。还有个延伸如果你需要持续监控浏览器所有请求而不是手动录制后导出HAR只是存档格式实时采集要靠Chrome DevTools Protocol的Network域事件自己组装。逻辑也不复杂监听requestWillBeSent、responseReceived、loadingFinished这些事件按HAR规范填充字段。网上不少基于Puppeteer的HAR自动采集脚本就是这个思路。2.3 手机上复现的问题也能导出HAR很多线上问题只能在真机上复现这时候别再用手机截个图发我这种低效方式了。Android的做法手机开启USB调试用数据线连电脑电脑Chrome地址栏打开chrome://inspect找到对应设备和页面点inspect按钮。这时会打开一个远程DevTools窗口操作和电脑上一模一样Network面板录制、导出HAR都可以直接做。iOS的做法iPhone在设置 → Safari浏览器 → 高级 → 网页检查器里打开开关然后用Mac上的Safari在开发菜单里选择对应的设备页面同样会出一个Web InspectorNetwork面板里就能导出HAR。这个能力对排查只在手机网络环境下出现的请求失败特别有价值。曾经有个问题Wi-Fi下一切正常4G下接口随机超时最后就是靠手机端导出的HAR发现特定场景下移动网络DNS解析耗时突然飙到3秒和接口本身一点关系都没有。2.4 把HAR再拖回Network面板回放导出HAR不只是为了发给别人。过几天你想复盘当时的场景可以在Network面板里右键选择加载HAR文件直接把.har文件导入。也可以把.har文件直接拖进Network面板区域。导入之后整个请求列表、耗时瀑布图、请求头、响应体都会恢复成当时的样子。这意味着你可以在不重新访问页面的情况下反复分析那份历史数据。回放现场能力是HAR相比截图最大的价值——截图看一次就丢了HAR随时可以重新打开复盘。3. 读懂HAR内部结构字段级拆解与阅读顺序3.1 文件骨架log、creator、pages、entriesHAR的根对象是一个log内部有这些关键字段version格式版本一般是1.2creator生成器信息。Chrome导出的HAR里creator.name就是Chromeversion是对应的浏览器版本browser可选记录浏览器整体信息pages页面级记录包含页面URL、onContentLoad和onLoad的时间点entries核心数组每个元素对应一次请求/响应的完整记录我第一次看HAR时以为entries是唯一重要的其实creator信息也值得注意。当你拿到一份别人转发的HAR先看creator.name和version能快速判断这份档案是从什么环境导出的。比如有人拿代理工具伪造HARcreator字段会暴露来源。3.2 request与response别被content.size和bodySize绕晕每个entry里的request对象记录请求侧的完整信息method、url、httpVersion、queryString、cookies、postData、headers、headersSize、bodySize。这里有个经常被忽略的点headers数组里存的是原始请求头原文Authorization、Cookie全在里面完全是明文的。这直接决定了HAR的敏感程度第5节我会专门展开。response对象里的信息更值得抠细节status和statusText状态码比如200、404headers响应头原文content包括size、mimeType、text响应正文headersSize和bodySize响应头大小、响应体传输大小最容易混淆的是content.size和bodySize。HAR规范里content.size表示内容解压后的实际大小bodySize是网络传输的字节量。服务器开了gzip时bodySize会比content.size小很多。比如content.size显示5840字节bodySize只有1560字节这很正常说明响应体经过压缩传输后解压还原。如果你打开HAR发现一条静态资源请求再怎么看都只有几十字节但页面里图片明明几百KB别慌先看有没有压缩再确认content里是否包含content文本。很多情况下是带content导出没勾响应正文根本没被写进HAR。3.3 timings七个阶段的时间到底各自代表什么这是读HAR时信息量最大的部分。timings对象里有这些字段单位全部是毫秒字段含义blocked请求排队等待的时间包含连接池排队、代理协商等dnsDNS解析耗时connectTCP连接建立耗时含TLS握手部分场景send发送请求头/体耗时wait等待服务器返回首个字节的耗时约等于TTFBreceive接收响应体的耗时ssl部分工具会单列TLS握手耗时Chrome导出经常是0或-1time字段是整条请求的总耗时大致等于以上阶段之和。读timings有个经验法则大部分慢的问题都出在wait阶段。wait是浏览器发出请求后等待服务器响应第一个字节的时间它最能反映服务端的处理速度。blocked变大了说明浏览器在排队往往是连接池不够用后面第4节的实战案例就是这个。dns和connect异常变大则是网络链路的问题。顺便提醒一点timings里的-1表示未知/不适用0表示该阶段极短或直接复用了已有结果。比如一条请求复用了既有的TCP连接connect就是0这完全是正常现象。3.4 我拿到一份HAR之后的阅读顺序拿到一份陌生的HAR我不建议直接从第一个entry从头看到尾。那会淹没在没有意义的信息里。我的顺序是按状态码过滤先把4xx、5xx以及status是0的请求挑出来。status为0或没有response基本就是请求失败了。按time字段排序找出耗时最大的前10到20条看它们集中在哪个域名、什么资源类型。针对可疑条目先看timings判断慢在blocked、dns、wait还是receive。再看request headers和response headers注意Server、X-Cache、Content-Encoding这些字段。最后才看请求体和响应体内容验证前后端数据是否对得上。如果遇到几十个entry的大文件直接用命令行工具会更高效。比如用jq统计耗时前20的请求jq -r .log.entries[] | [.startedDateTime, .request.method, .request.url, .time, .timings.wait] | tsv network.har | sort -k4 -nr | head -20这个命令把每条请求的开始时间、方法、URL、总耗时、wait耗时输出成表格再按总耗时倒序取前20条。排查性能问题第一步先跑这个命令比在DevTools里肉眼刷瀑布图快得多。3.5 一份真实HAR片段长什么样看一下实际HAR片段心里才踏实。截取一段简化后的内容{ log: { version: 1.2, creator: { name: Chrome, version: 131.0.0.0 }, pages: [ { startedDateTime: 2025-01-15T12:00:00.123Z, id: page_1, title: https://example.com/, pageTimings: { onContentLoad: 580, onLoad: 1220 } } ], entries: [ { pageref: page_1, startedDateTime: 2025-01-15T12:00:01.000Z, time: 312, request: { method: GET, url: https://api.example.com/v1/users?page1, httpVersion: http/2.0, headers: [ { name: authorization, value: Bearer eyJ... }, { name: accept, value: application/json } ], queryString: [ { name: page, value: 1 } ], cookies: [], headersSize: 220, bodySize: 0 }, response: { status: 200, statusText: OK, httpVersion: http/2.0, headers: [ { name: content-type, value: application/json }, { name: server, value: nginx } ], content: { size: 5840, mimeType: application/json, text: ... }, headersSize: 180, bodySize: 1560 }, cache: {}, timings: { blocked: 5, dns: 0, connect: 0, send: 1, wait: 268, receive: 38, ssl: -1 }, connection: 12345, serverIPAddress: 203.0.113.10 } ] } }注意几个细节startedDateTime是ISO 8601格式末尾带Z表示UTC时间response.content.size是5840而bodySize是1560正好体现了压缩传输的差异timings里connect是0说明这条请求复用了之前的连接ssl是-1在Chrome导出的HAR里比较常见TLS握手时间被合进connect了。4. 实战用HAR把接口偶发失败的锅定位出来4.1 一个前端说发了、后端说没收到的现场有一段时间团队群里经常出现这样的消息这个接口又失败了帮我看看。前端在DevTools里看到的是一个红色的请求状态码列的空白网络层报错是error sending request这类提示后端翻日志完全没有对应的调用记录。网络组查了链路也说没看到明显异常。这种问题最难缠的地方在于它不是每次都失败也没人说得清失败的那一瞬间请求到底走到了哪一步。前端觉得我发了是你们服务端的问题后端觉得我没收到是你们前端或者网络的问题。我当时给的建议是下次再遇到别截图立刻在Network面板右键把当前会话导出成带content的HAR同时导一份同一时段成功的请求。4.2 失败entry里的两个关键信号拿到HAR后同时打开成功和失败两个请求的entry对比差异非常明显。失败的请求entry里response基本是空的status是0也没有响应头——这说明浏览器根本没有收到任何HTTP响应。这不代表请求没发出去只代表有去无回。更有价值的信号在timings里。那几条失败请求的connect时间都是0connection字段还跟前面某条成功请求的connection字段一模一样。这就暴露了一个关键事实失败请求复用了之前那条请求建立的TCP连接。再看wait字段失败的请求wait值异常大有的接近几十秒然后整个entry结束。也就是说请求确实发出去了但服务器那侧一直不给响应最后连接断开浏览器端表现为请求失败。4.3 连接复用与半开连接HAR看不到的那半截HAR能告诉你复用了一个已有连接但它不会告诉你这个连接在TCP层面是怎么断掉的。这里需要一点网络协议常识来补全推理。HTTP/1.1的keep-alive和HTTP/2的多路复用都依赖一个连接多次复用。如果服务端或中间设备把空闲连接悄悄回收了客户端不知道继续往这个半开连接上发请求TCP层面就会出问题——要么连接被重置要么请求发出后一直等不到应答直到超时。我们的情况和后者很像网关对长时间空闲的连接做了回收但应用的keep-alive超时设置比网关长。浏览器侧保存着那个看起来还能用的连接实际对端早就没了。等到这条连接被复用请求就卡在wait阶段最终失败。HAR本身看不到TCP层是收到了RST还是FIN。这一步如果想彻底实锤需要配合Chrome的chrome://net-export抓NetLogNetLog里能看到TCP层、HTTP/2帧层面的详细事件序列。但HAR已经足够把排查方向从前端背锅扭转到连接管理机制不匹配上。修复也不复杂把网关的空闲连接超时和应用的keep-alive超时配置成一致或者让应用服务器主动发送连接关闭通知。这个案例也提醒我以后再看到报错请求里connect为0、wait异常大、connection还是旧ID第一反应先怀疑连接复用。4.4 另一个实战blocked字段暴露的同域连接数瓶颈还有一个发生在前端性能优化里的例子和上面完全不同但同样靠HAR的timings字段锁定问题。现象是某页面加载白屏时间特别长接口本身都在几百毫秒内返回但页面里十几张小图片看起来加载很慢用户的直观感受就是卡。翻HAR的瀑布图很多图片请求time不算大但timings的blocked数值大得吓人有的甚至超过1.5秒。当时我第一反应是图片资源本身的问题结果点开发现图片都在CDN上cdns、connect全是0唯有blocked这一项占据了大半条时间线。blocked的含义就是排队。HTTP/1.1下浏览器对同一域名最多同时建立6个左右的TCP连接前6个慢请求占满了连接池后面的图片请求只能排队。HAR里每一条请求的blocked时间就是这个排队时间。解决办法有三条路一是把静态图片拆到独立域名绕过连接数上限二是升级HTTP/2多路复用同一个连接三是把低优先级资源延后加载别跟关键接口抢连接池。这个案例完美的展示了同一份HAR数据在不同场景下能读出完全不同的信息。看timings时不要只盯着waitblocked和connect同样值得关注。5. HAR的坑和边界这些事项一定别踩5.1 敏感信息在HAR里是裸奔的这是我最想强调的一点。HAR文件里保存了录制过程中所有请求的原始请求头、Cookie、Authorization、以及带content导出时的完整响应正文。换句话说是登录态、会话Token、第三方合作伙伴的凭证全都明文躺在那个JSON文件里。我见过有人顺手把HAR拖进工单系统、发到群里、传给外包团队然后登录态被滥用的事件。不是玩笑。所以导出HAR后的第一件事是判断这份文件会不会给外部人看。如果要发出去建议先用脚本对敏感字段做脱敏把authorization、cookie、set-cookie这些字段的值替换成占位符。最省事的方法是先导出不带content的版本但即便这样请求头里的敏感信息仍然存在。处理HAR要像处理数据库备份一样谨慎——它记录的是真实生产流量。5.2 Disable cache、缓存判断与空cache字段录制时如果勾选了Network面板里的Disable cache导出的HAR代表的是冷加载状态所有资源都走完整网络链路这时cache相关字段基本是空的没有参考意义。不勾选的情况下再次访问页面如果资源命中了内存缓存或磁盘缓存Network面板里可能根本没有对应的网络活动自然也就不会出现在导出的HAR中。有人因此以为资源加载丢了其实是缓存命中后没有产生网络请求。关于HAR里的cache字段我建议别指望太多。Chrome导出时这个字段大多数情况是一个空对象{}即使资源走了缓存也很难从中读出命中缓存的结论。要判断缓存策略直接看响应头的Cache-Control、Expires、Etag必要时配合一次禁用缓存前后两次录制的HAR做对比。5.3 体积、时区、-1与可伪造容易被忽略的细节长会话的HAR会非常庞大。录了半小时页面操作的带content HAR文件轻松上百MB浏览器打开都卡。所以录制时一定要提前用Filter过滤URL缩小录制范围别等录完了发现文件大到发不出去。startedDateTime这个时间字段要特别注意。它虽然写的是类似2025-01-15T12:00:00.123Z这样的格式末尾带Z代表UTC时间。你用浏览器直接看JSON会觉得它像是本地时间其实是UTC转换本地时间需要处理时区差不然对不上事发时间。timings里那些-1和0也别误读。-1是未知0是没花时间。一条完全命中了现有连接的请求connect为0不代表连接质量好只代表没建。还有一个几乎没人提的坑HAR是普通JSON任何人都可以手工修改、伪造一份看起来正常的HAR。所以它只能作为技术分析工具不能当成司法或审计级别的证据。当别人发你一份HAR时先看看creator和内部时间戳的逻辑再决定采信多少。5.4 当HAR不够用和NetLog配合的排查接力我前面说过HAR是应用层网络活动的台账它对TCP层、HTTP/2帧层、QUIC层的事无能为力。比如你想知道那个连接为什么被重置HAR里只有结果没有过程。这个场景下正确操作是使用Chrome自带的chrome://net-export。打开这个页面开始记录然后复现问题结束后下载NetLog文件。NetLog记录了浏览器网络栈内部几乎全部事件包括TCP连接状态变化、TLS握手过程、HTTP/2帧收发数据量很大但信息最全。我的习惯是先用HAR快速定位问题大概出现在哪个环节再针对那个环节用NetLog深挖底层。HAR是地图NetLog是显微镜两者配合使用效果最好。如果NetLog还不满足那就只剩抓包工具了那个是另一个话题。我自己现在的习惯是只要遇到能复现的网络问题第一件事就是开一个干净的Network窗口清空记录、按需过滤、复现一次、右键导出带content的HAR用日期问题描述.har存到本地。问题解决了也不删因为过两周很可能要翻出来对照。有一次我就是靠一个月前的一份失败请求HAR反推出现在新接口的网络拓扑变化。排查问题的效率很大程度取决于你能不能把当时到底发生了什么这件事完整记录下来HAR就是这件事目前最靠谱的答案。
