页面里的数据多半是 JS 发请求拿来的。用浏览器抓取时,与其解析渲染后的 HTML,不如直接让 CDP 把网络响应体交出来。这篇用一个本机页面测 Network 事件的顺序、什么时候能取响应体,以及取不到的几种情况。
事件个数:requestWillBeSent 5、responseReceived 5、loadingFinished 5、dataReceived 6 / type=Document 状态 200 mimeType=text/html Content-Length(响应头)=90 encodedDataLength=206 getResponseBody:ok 长度=90 base64Encoded=False /a.png type=Image 状态 200 mimeType=image/png Content-Length(响应头)=70 encodedDataLength=186 getResponseBody:ok 长度=96 base64Encoded=True /app.js type=Script 状态 200 mimeType=application/javascript Content-Length(响应头)=75 encodedDataLength=204 getResponseBody:ok 长度=75 base64Encoded=False /api/data type=Fetch 状态 200 mimeType=application/json Content-Length(响应头)=16477 encodedDataLength=16627 getResponseBody:ok 长度=258780 base64Encoded=False /favicon.ico type=Other 状态 404 mimeType=text/plain Content-Length(响应头)=0 encodedDataLength=97 getResponseBody:ok 长度=0 base64Encoded=False
开着 Network 域,每个请求会按 requestWillBeSent、responseReceived、(若干 dataReceived)、loadingFinished 的顺序给出事件,用 requestId 关联。type 字段区分 Document、Script、Image、Fetch 等。实验里页面只有 img 和 script,浏览器还自己请求了 /favicon.ico,一共 5 个请求。getResponseBody 对文本给出文本,对图片给出 base64。
/api/data 网络上传输的字节数(loadingFinished 的 encodedDataLength)是 16627,getResponseBody 给出的是解压之后的文本 258780 字符、解析成 3000 个对象;服务器实际发送的 gzip 字节数 16477;页面 JS 里 window.n = 3000
响应带了 Content-Encoding: gzip 时,getResponseBody 给出的是浏览器已经解压的内容,不用自己解压。loadingFinished 里的 encodedDataLength 是网络上传输的字节数。实验里 gzip 正文 16477 字节,encodedDataLength 是 16627,解压后的文本 258780 字符(多出的 150 字节我推测是响应头,没有逐字节核对)。
responseReceived 到达时(正文 2000 字节还没发完)取:('err', "Network.getResponseBody: {'code': -32000, 'message': 'No data found for resource with give", None)
loadingFinished 到达后取:('ok', 2000, False)(距 responseReceived 1.26 秒;页面 JS 里收到的正文长度 2000)响应头到了(responseReceived)不等于正文到了。正文还在传输时调 getResponseBody 会报错 “No data found for resource with give…”(输出被截断,后面是 identifier);等 loadingFinished 之后才取得到。实验里 /slow 的正文分两块,中间停 0.6 秒。
访问 /redir:requestWillBeSent 里带 redirectResponse 的有 1 个,状态 302,Location=/api/data
跳转之后:此前 /api/data 那个 requestId 再取 getResponseBody:('err', "Network.getResponseBody: {'code': -32000, 'message': 'No resource with given identifier fo", None)重定向的那一跳在下一个 requestWillBeSent 的 redirectResponse 字段里(状态、Location)。页面跳走之后,旧页面的 requestId 取 getResponseBody 会报 “No resource with given identifier fo…”(输出被截断)。所以要在页面还在的时候取走需要的响应体。
不阻止:服务器收到 /a.png 1 次,其它请求 3 次;浏览器侧 loadingFailed 0 个([]);页面 JS 里 window.n = 3000
阻止 *.png:服务器收到 /a.png 0 次,其它请求 3 次;浏览器侧 loadingFailed 1 个([('', 'inspector')]);页面 JS 里 window.n = 3000Network.setBlockedURLs 能让浏览器不发出匹配的请求(实验里图片请求没有到达服务器,页面其余部分照常工作),对不需要图片、样式的抓取能省流量。
Network.enable 默认:页面收完 150 个,事后取得到响应体的有 142 个,取得到的是第 8–149 个(从 0 开始) maxTotalBufferSize=10 MiB:页面收完 150 个,事后取得到响应体的有 7 个,取得到的是第 143–149 个(从 0 开始) maxTotalBufferSize=1 GiB:页面收完 150 个,事后取得到响应体的有 150 个,取得到的是第 0–149 个(从 0 开始)
DevTools 会缓存响应体供事后查看,缓冲有上限:实验里默认设置下连续 150 个 1 MiB 的响应,事后只取得到 142 个,最早的 8 个被挤掉;把 maxTotalBufferSize 设成 10 MiB 就只剩最后 7 个。大量或大的响应要在 loadingFinished 时就取走,不要攒到最后。