在 9 种页面上试提取内嵌 JSON 的方法:合法 JSON 的 script 标签用正则或 lxml 都行,有注释和末尾逗号的只有 json5;window.__STATE__ = {…}; 用非贪婪正则会被字符串里的 "};" 截断,raw_decode 或花括号配对才稳;JS 对象字面量里的 undefined 连 json5 也解析不了;字符串里没转义的 </script> 谁都救不了。
在本机登录页上完整走一遍并故意做错:要先 GET 表单拿会话 cookie 和 csrf_token,POST 时带上全部隐藏字段;没有 cookie 或 token 不对返回 403,缺 form_id 或填了隐藏输入框返回 400;登录失败后 token 轮换,旧 token 再提交 403;登录成功后会话 id 换了;AJAX 的 token 通过 X-CSRF-Token 头回传。
同一个提取目标在 12 种略有差异的 HTML 写法上:最直接的正则只答对 3 种,更宽松的正则 5 种,lxml 解析 12 种全对;正则会漏掉属性顺序/引号/空白差异,多取注释和 script 里的假标签,不解码实体不去标签;嵌套量词的正则 L=26 要 4.1 秒;而正则只比 lxml 快约 4.6 倍(1.39 对 6.43 ms)。
模拟 8 个版本的改版:同一个提取器没有一个版本抛异常,但 v3 抓到 0 个商品、v5 价格悄悄 ×100;只看异常命中 0 / 5,字段空值率命中 4 / 5,价格中位数偏离命中 3 / 5,组合检查 5 / 5(1 个误报);灰度发布时整体平均失败率只有 22%,个别页面却是 100%。
用 Chrome 的 CDP Network 事件记录一个单页应用加载时发的 7 个请求,找到真正含数据的 /api/products;直接用 requests 重放要补上 Cookie(缺了 401)和 X-Requested-With(缺了 403);3 次请求取到 25 条,2 ms,而浏览器渲染要 63 ms;API 还多返回页面没显示的字段。
实测 headless Chrome 154 的开销:冷启动到调试端口可用中位 396 ms,新开标签页加载简单页面 24 ms;0 个标签页就有 9 个进程、RSS 之和约 1177 MiB,每多一个标签页多一个渲染进程、约 +109 MiB(同一站点与不同站点一样);用 CDP 拦截图片和样式让加载从 31 ms/2052 KiB 降到 13 ms/1 KiB。
在会随机延迟加载数据的本机页面上比较等待策略:固定等待 0.5 秒只有 5 / 12 成功,1.5 秒成功但多等 916 ms;轮询到期望的元素数量几乎不多等(约 45 ms);只等第一个元素在分两阶段加载的页面上 0 / 12;网络空闲在有长连接的页面上永远等不到(6035 ms 到上限)。
30 次加载同一页面:每页启动新浏览器 46862 ms(452.1 ms/页),复用浏览器新标签页 2289 ms(38.0 ms/页),独立 context 5232 ms(114.1 ms/页),同一个标签页 1305 ms(5.7 ms/页);默认 context 与同一标签页共享 Cookie 和缓存(29/29 带 Cookie、图片只请求 1 次),独立 context 与新浏览器互相隔离;不关标签页 60 次后 69 个进程、7990 MiB。
在本机 headless Chrome 154 里让页面自己的 JavaScript 读自动化相关的值:只开远程调试端口时 navigator.webdriver 是 false,加 --enable-automation 才是 true(同时视口矮 56 像素);User-Agent 里直接写着 HeadlessChrome;JS 与请求头读到的 UA 一致。本文只描述页面能看到什么,不涉及隐藏办法。
在本机用 IntersectionObserver 做的无限滚动页面上实测:不滚动只有第 1 页(10 条);匀速滚动并用“连续 3 次没增长”判定到底会在第 40 条提前停下;每次滚到底部拿到全部 100 条(10 次请求);3000 px 的高视口下朴素写法卡在 10 条,加载后重新 observe 才能继续。
在本机的 WebSocket 服务器上实测:Chrome 的 CDP Network 事件里能看到握手、发出的帧和收到的帧;websocket-client 直接连时握手头和浏览器的差别;服务器断开后不重连、重连不带 since、重连带 since 分别丢多少数据;不发心跳会被服务器关闭;Origin 校验对库和浏览器各是什么结果。
在本机页面上实测 CDP Network 域:事件顺序、getResponseBody 返回的是解压后的文本(图片是 base64)、必须等到 loadingFinished 才能取、页面跳走后取不到、setBlockedURLs 能挡住图片请求、默认缓冲下 150 个 1 MiB 响应事后只取得到 142 个。