简介Chrome Headless Shell 129.0.6668.59 是面向 Windows 64 位系统的无头浏览器组件可服务于自动化测试、服务器端页面渲染以及 CI/CD 流水线集成。它能在没有图形界面的环境下完成网页加载、脚本注入与 DOM 操作让开发者和测试工程师无需安装完整 Chrome 即可获得接近真实浏览器的执行行为。压缩包共收纳 125 个文件包括 58 个 pak 资源文件、52 个 hyb 数据文件以及 dll 动态库、json 配置文件、js 脚本和可执行主程序整体体积约 100.37MB解压后即可配合 ChromeDriver 等自动化工具独立使用。目前已有 398 人学习下载。该版本在 Cookie 读写、用户动作模拟、无头渲染等典型自动化场景中表现稳定适合用于回归测试、网页截图和服务器端渲染任务能够帮助团队在资源受限环境中高效完成浏览器自动化验证。1. 这个 headless-shell 包到底是什么为什么值得单独下载做定时截图、爬页面结构、批量生成 PDF 的老哥们多半都遇到过同一个纠结Windows 服务器上装完整 Chrome 太重装完还得伺候它自动更新用chrome --headless凑合又总觉得这个模式是顺手支持的不是专门为无头场景设计的。这个chrome-headless-shell-win64-129.0.6668.59解决的就是这个问题——它是 Google 官方从 Chrome 里拆出来的无头浏览器内核没有 UI、没有扩展、没有更新组件解压后一个 exe 直接跑专门给自动化、爬虫和 CI 环境用。适合谁跑脚本抓数据的、做自动化测试的、还有需要在服务器上把网页转成图片或 PDF 的。如果你属于这三类人往下看。2. 选型和版本对应headless-shell、Chrome、ChromeDriver 三者怎么分工2.1 三者关系浏览器内核、完整产品、协议翻译层很多人第一次接触 headless-shell 时会把它和 ChromeDriver 搞混或者以为它就是去掉界面的 Chrome。我直接用一张表说清楚组件本质是否渲染页面是否需要它Chrome 完整版面向用户的完整浏览器是无头场景下不是必须headless-shell剥离 UI 的浏览器内核本体是无头场景的核心ChromeDriverWebDriver 协议的翻译层否用 Selenium 时才需要headless-shell 就是那个真正干活的内核它接收 URL加载页面执行 JS渲染出像素然后把截图或 DOM 交给你。ChromeDriver 不渲染页面它只是把 WebDriver 的命令翻译成浏览器能懂的东西最终执行渲染的还是浏览器内核。所以你完全可以不用 ChromeDriver直接通过 DevTools 协议CDP跟 headless-shell 通信这也是爬虫老手更常用的路子。2.2 版本对应规则129 系就该配 129 系版本号129.0.6668.59属于 Chrome 129 这条线。版本对应关系在自动化链路里是硬约束如果你用 ChromeDriverdriver 的版本必须和浏览器内核版本在同一代否则协议对不上、方法调不通。headless-shell 虽然没有必须配套这么夸张但 CDP 的有些接口是随版本演进的旧内核调新接口、新内核调报废接口报错都挺抽象。验证当前 headless-shell 版本的方式很简单chrome-headless-shell.exe --version它会输出版本字符串129.0.6668.59就是你要在 ChromeDriver 那边找的对应版本。我一般会把这个输出重定向到一个文件里存档换版本时对比用省得把版本号记岔了。2.3 什么时候不该用 headless-shell它也不是万能的边界得提前划清楚。headless-shell 没有扩展机制chrome://extensions这类页面基本可以当作不存在它也没有完整的浏览器 UI 逻辑像下载文件、弹窗提示这类强交互流程行为跟完整 Chrome 不完全一致。如果你要自动化操作浏览器开发者工具面板、要跑 Chrome 扩展、要模拟用户登录时的验证码交互headless-shell 不是最优解老老实实用完整 Chrome 的 headless 模式更稳。另外注意headless-shell 不做自动更新这既是优点也是隐患。优点是不会像完整 Chrome 一样半夜给你更新出个新版本打乱脚本隐患是安全补丁不会自己打上来如果拿它跑线上服务需要自己盯版本、定期手动换新包。3. 解压到跑通第一张截图目录结构、启动参数和最短验证路径3.1 解压后的目录先认清文件再动手这个包是压缩包形式解压后没有安装程序直接得到一个目录。目录里的核心文件是这几类文件/文件组作用需要注意的点chrome-headless-shell.exe主程序所有操作都由它执行没有它一切免谈icudtl.datICU 国际化数据字符编码相关缺失会导致启动崩溃v8_context_snapshot*.binV8 引擎上下文快照JS 执行依赖缺失时 JS 可能直接失效*.pak等资源文件浏览器内部资源、内置页面建议整体保留不要单独删这里有个容易踩的坑很多人解压后找不到chromedriver.exe以为包不完整。其实 headless-shell 包里本来就不带 driver它是给 CDP 用的不是给 WebDriver 用的。如果你要走 Selenium 路线ChromeDriver 得单独下载版本对标129.0.6668.59。3.2 最小验证先截一张图再说拿到文件后我习惯先做一次最短路径验证——截一张图。这一步跑通了说明 exe 能启动、内核能加载、渲染链路是通的chrome-headless-shell.exe --headless --no-sandbox --disable-gpu ^ --screenshotD:\tmp\test.png ^ --window-size1280,800 ^ --virtual-time-budget5000 ^ https://example.com代码说明--headless在 headless-shell 上其实可加可不加它本身就是无头运行我保留这个参数是为了跟完整 Chrome 的命令行对齐方便以后切换。--no-sandbox在 Windows 上不是必须但某些机器上沙箱和杀毒软件有冲突加上能减少莫名闪退。--screenshot指定截图输出路径注意目录要存在。--window-size控制截图尺寸宽度和高度用逗号分隔。--virtual-time-budget5000是核心它让浏览器最多快进 5 秒的虚拟时间页面里的异步加载、定时器都会被加速处理比真实等待更快也更稳。跑完去D:\tmp看有没有test.png有就说明第一关过了。这个流程在换版本或者换机器时特别适合当健康检查比直接改你那套大脚本快得多。3.3 常用参数表和各自的定位我用得最多的参数是下面这几个按使用频率排参数作用使用建议--virtual-time-budget毫秒虚拟时间快进快速等待页面完成渲染截图/抓 DOM 前建议加上值给 3000~10000--window-size宽,高设置窗口/截图尺寸默认是 800x600做报表图就得自己改--dump-dom把渲染后的 HTML 输出到标准输出抓页面结构、验证内容加载时用--hide-scrollbars截图中隐藏滚动条截图要干净时建议加--user-data-dir路径指定用户数据目录多实例并发时必须区分后面细说--disable-gpu禁用 GPU 加速无头模式默认不启用 GPU加上求个心理安慰抓 DOM 的完整命令长这样chrome-headless-shell.exe --headless --no-sandbox --disable-gpu ^ --dump-dom ^ --virtual-time-budget5000 ^ https://example.com D:\tmp\page.html注意--dump-dom输出的是渲染完成之后的 DOM不是初始 HTML。它会把 JS 动态插入的节点也算进去所以特别适合验证页面到底加载出了什么。重定向在 cmd 和 PowerShell 里的行为略有差异cmd 下建议先chcp 65001切 UTF-8 编码否则中文内容容易乱码这个坑在第 5 章专门说。4. 接入自动化链路远程调试端口、CDP 直连、Selenium/Playwright 对接4.1 用远程调试端口把控制权交出来命令行截图适合一次性任务真要写程序控制它得把调试端口打开。headless-shell 支持 DevTools 协议启动时加一个参数就能把控制权暴露出来chrome-headless-shell.exe --headless --no-sandbox --disable-gpu ^ --remote-debugging-port9222 ^ --user-data-dirD:\tmp\profile-9222 ^ about:blank这里about:blank的作用是先开一个空白页占位保证/json接口有 page 类型的 target 可查。--user-data-dir给这个实例单独指定目录避免跟其他实例共用数据产生锁冲突。启动后用任意 HTTP 客户端访问http://127.0.0.1:9222/json就能看到当前所有页面的调试信息curl http://127.0.0.1:9222/json返回的 JSON 里每个 page 对象都有webSocketDebuggerUrl字段这个地址就是后续 CDP 通信的入口。先确认能拿到这个 JSON再往下接代码。4.2 CDP 直连拿标题、执行 JS、截图有了 WebSocket 地址剩下的就是对着 CDP 协议发消息了。Python 这边我一般用requests拿调试地址再用websocket-client发命令import json import requests import websocket # 1. 从 HTTP 接口拿到页面列表取第一个 page 的 WebSocket 地址 pages requests.get(http://127.0.0.1:9222/json, timeout5).json() page next(p for p in pages if p[type] page) ws_url page[webSocketDebuggerUrl] print(debug url:, ws_url) # 2. 建立 WebSocket 连接发送 Runtime.evaluate 命令 ws websocket.create_connection(ws_url, timeout10) ws.send(json.dumps({ id: 1, method: Runtime.evaluate, params: {expression: document.title} })) resp json.loads(ws.recv()) value resp[result][result].get(value) print(page title:, value) ws.close()这里每一步都有讲究requests.get(/json)拿的是当前所有 page 类型的 targetnext()里的p[type] page是过滤条件避免拿到 service worker 之类非页面 target。Runtime.evaluate是在页面上下文里执行 JS 表达式expression参数就是你要执行的代码。消息里的id字段是自增的用于匹配请求和响应——CDP 是异步的没有 id 你根本不知道哪条响应对应哪条请求。result.result.value是返回值如果表达式执行报错这里会出现exceptionDetails字段排查时优先看它。4.3 对接到 Selenium 和 Playwright如果你不想直接写 CDP,团队里已经用了 Selenium 或 Playwright也可以把 headless-shell 当作底层浏览器接进去。Selenium 4 里通过binary_location指定内核路径from selenium import webdriver from selenium.webdriver.chrome.options import Options opts Options() opts.binary_location rD:\chrome-headless-shell-129\chrome-headless-shell.exe opts.add_argument(--headless) opts.add_argument(--no-sandbox) opts.add_argument(--disable-gpu) driver webdriver.Chrome(optionsopts) driver.get(https://example.com) print(driver.title) driver.quit()关键点binary_location指向的是 headless-shell 的 exe而不是完整 Chrome。同时你要确保机器上有对应版本的 ChromeDriver——Selenium 4 虽然自带 Selenium Manager但它能不能自动识别这个非标准内核路径我没把握稳妥做法是自己下载同版本 driver 并把它加到PATH或通过service参数指定。Playwright 那边更直接用executablePath指过去就行const { chromium } require(playwright); (async () { const browser await chromium.launch({ executablePath: D:/chrome-headless-shell-129/chrome-headless-shell.exe, headless: true, args: [--no-sandbox, --disable-gpu] }); const page await browser.newPage(); await page.goto(https://example.com); console.log(await page.title()); await browser.close(); })();注意 Playwright 的chromium.launch默认会用它自己捆绑的浏览器内核只有显式传入executablePath才会走 headless-shell。另外 headless-shell 缺少完整浏览器的一些能力比如文件下载和弹窗管理如果你的自动化用例涉及这些建议退回完整 Chrome。基础导航和 JS 执行headless-shell 完全够用。5. win64 避坑五连闪退、白图、端口拒连和版本错位5.1 启动和渲染闪退与白图第一条双击 exe 秒退命令行也一闪而过。现象在 cmd 里敲chrome-headless-shell.exe --version窗口一闪就没了什么输出都没有。原因最常见是两个——系统缺 VC 运行库或者杀毒软件把 exe 当可疑文件隔离了。headless-shell 依赖 Windows 的 VC 运行库干净服务器上经常没装。解决先装VC 2015-2022 x64运行库合集装完再跑--version。如果还是闪退去杀毒软件的隔离区看看有没有被拦有的话把解压目录加白名单。注意加白名单前确认这个包是你自己下载的别盲信来源。第二条截图能生成但打开是一张白图。现象--screenshot命令执行完文件也存在但图片内容是纯白的或者只有背景色没有内容。原因截图时机太早——页面还没渲染完浏览器就把当前画面输出成文件了。headless-shell 不会像完整 Chrome 那样等load事件后再给你截图--virtual-time-budget给少了就会出现这种问题。解决把--virtual-time-budget调到 5000 甚至 10000让虚拟时间走得更长。如果页面本身有懒加载图片、列表滚动加载虚拟时间走完也不一定触发懒加载这时候可以在命令前先用--dump-dom看一眼页面最终的 DOM 里到底有没有内容先确认是渲染问题还是页面问题。5.2 并发和连接profile 冲突、端口回环与中文乱码第三条同时起两个实例后一个实例页面打不开日志报错。现象第二个实例启动后/json接口能访问但页面空白或者直接拒绝新页面创建。原因两个实例共用了同一个--user-data-dirChrome 系内核在同一个用户数据目录上会加进程锁后启动的实例拿不到锁行为就变成半死状态。解决每个实例必须分配独立的--user-data-dir最好目录名里带上端口号一眼就能对上。第四条curlhttp://127.0.0.1:9222/json能通但局域网内其他机器访问这个端口连不上。现象在本机验证没问题换了机器走局域网 IP 访问连接被拒。原因默认情况下--remote-debugging-port只绑定回环地址127.0.0.1外部访问根本到不了这个端口。解决如果确实需要远程访问启动时加--remote-debugging-address0.0.0.0。但我强烈建议别把它暴露到公网——这个端口没有任何鉴权谁连上谁就能控制浏览器只让它监听回环地址是更安全的默认值。跨机器调试的场景优先考虑 SSH 隧道解决。第五条--dump-dom重定向到文件后中文内容在 cmd 里显示乱码但用编辑器打开文件是正常的。现象同一份 HTML用 PowerShell 重定向到文件再打开没问题直接在 cmd 里看就是乱码。原因cmd 默认用 GBK 编码解码输出而 Chrome 内核输出的是 UTF-8。乱码不是数据问题是终端解码问题。解决cmd 里先执行chcp 65001切到 UTF-8 代码页再跑命令或者干脆用 PowerShell 的$OutputEncoding [System.Text.Encoding]::UTF8。注意chcp 65001之后终端字体如果没配置好有些字形会显示成方框这是渲染问题不影响实际数据。6. 多实例并发与验证脚本换版本前先跑这一套6.1 一个脚本拉起五个实例生产环境经常需要同时处理一批任务一个实例串行跑太慢了。用 Python 脚本一次性拉起多个实例每个实例独立端口、独立用户目录import subprocess exe rD:\chrome-headless-shell-129\chrome-headless-shell.exe for i in range(1, 6): port 9200 i profile_dir fD:\\tmp\\profile-{port} cmd [ exe, --headless, --no-sandbox, --disable-gpu, f--remote-debugging-port{port}, f--user-data-dir{profile_dir}, about:blank ] subprocess.Popen(cmd) print(finstance {i} - port {port}, profile {profile_dir})端口从 9201 到 9205每个实例的数据目录独立不存在锁冲突。任务调度侧按端口号分发 URL谁空闲谁接单。要注意的是headless-shell 实例不会自动退出任务跑完它还在那占着内存。空闲实例建议手动 kill或者让它处理完最后一单后由调度脚本统一回收。6.2 换版本前的三关验证每次拿到新版本我都会强制跑一遍三关验证花不了两分钟但能省下线上故障排查的大把时间import requests for port in range(9201, 9206): try: tabs requests.get(fhttp://127.0.0.1:{port}/json, timeout3).json() print(f[OK] port {port}: {len(tabs)} tab(s) alive) except Exception as exc: print(f[FAIL] port {port}: {exc})第一关是端口存活/json能返回 JSON 说明实例活着第二关是截一张--virtual-time-budget5000的图确认渲染链路正常第三关是--dump-dom输出非空确认 JS 执行没问题。三关全过才敢把它正式替换进项目。之前有一次我图省事直接拿新版本替换了旧 exe 没跑验证结果线上定时任务静默失败了两天日志里全是空白截图排查到半夜才发现是页面懒加载时机和虚拟时间预算不匹配。从那以后我每次换版本都强制走一遍这套验证流程当成习惯。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?