Headless Chrome入门

原文地址:Getting Started with Headless Chrome By Eric Bidelman Engineer @ Google working on web tooling: Headless Chrome, Puppeteer, Lighthouse Headless Chrome在Chrome59中发布,用于在headless环境中运行Chrome浏览器,也就是在非Chrome环境中运行Chrome。它将Chromium和Blink渲染引擎提供的所有现代Web平台功能引入命令行。 它有什么用处呢? headless浏览器是自动测试和服务器环境的绝佳工具,您不需要可见的UI shell。例如,针对真实的网页进行测试,创建网页的PDF,或者只是检查浏览器如何呈现URL。 0. 开始 最简单的开始使用headless模式的方法是从命令行打开Chrome。如果你已经安装了Chrome59+的版本,可以使用 --headless 标签: 复制代码 chrome \ --headless \ # 在headless模式运行Chrome --disable-gpu \ # 在Windows上运行时需要--remote-debugging-port=9222 \ https://www.chromestatus.com # 打开URL. 默认为about:blank 复制代码 注意:若在Windows中运行,则需要在命令行添加 --disable-gpu 。 chrome 命令需要指向Chrome的安装路径。(即在Chrome的安装路径下运行) 1. 命令行功能 在某些情况下,您可能不需要以编程方式编写Headless Chrome脚本。下面是一些有用的命令行标志来执行常见任务。 1.1 打印DOM --dump-dom 将 document.body.innerHTML 在stdout打印出来: chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/ 1.2 创建PDF --print-to-pdf : chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/ 演示:在chrome安装目录下运行 chrome --headless --disable-gpu --print-to-pdf https://www.baidu.com/ 生成PDF文件:C:\Program Files (x86)\Google\Chrome\Application\69.0.3497.81\output.pdf 1.3 截屏 --screenshot 复制代码 chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/ # 标准屏幕大小 chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/ # Nexus 5x chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/ 复制代码 运行 --screenshot将会在当前运行目录下生成一个 screenshot.png 文件。若想给整个页面的截图,那么会比较复杂。来自 David Schnurr 的一篇很棒的博文介绍了这一内容。请查看 使用 headless Chrome 作为自动截屏工具。 1.4 REPL模式(read-eval-print loop) --repl 在REPL模式运行Headless,该模式允许通过命令行在浏览器中评估JS表达式: 复制代码 $ chrome --headless --disable-gpu --repl --crash-dumps-dir=./tmp https://www.chromestatus.com/ [0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit. >>> location.href {"result":{"type":"string","value":"https://www.chromestatus.com/features"}} >>> quit $ 复制代码 注意:使用repl模式时需要添加 --crash-dumps-dir 命令。 2. 在没有浏览器界面情况下调试Chrome 当使用 --remote-debugging-port=9222 运行Chrome时,会启用DevTools协议的实例。该协议用于与Chrome通信并且驱动headless浏览器实例。除此之外,它还是一个类似于 Sublime, VS Code, 和Node的工具,可用于远程调试一个应用。 由于没有浏览器UI来查看页面,因此需要在另一个浏览器中导航到http:// localhost:9222以检查一切是否正常。这将看到一个可查看页面的列表,可以在其中单击并查看Headless正在呈现的内容: DevTools远程调试界面 在这里,你可以使用熟悉的DecTools功能来查看、调试、修改页面。若以编程方式(programmatically)使用Headless,该页面的功能更强大,可以用于查看所有的DecTools协议的命令,并与浏览器进行通信。 3. 使用编程模式(Node) 3.1 Puppeteer Puppeteer 由Chrome团队开发的Node库。它提供了控制headless Chrome的高阶API。类似于 Phantom 和 NightmareJS这样的自动测试库,但它只用于最新版本的Chrome。 除此之外,Puppeteer还可用于截屏,创建PDF,页面导航,以及获取有关这些页面的信息。如果需要快速进行浏览器的自动化测试,建议使用该库。它隐藏了DevTools协议的复杂性,并负责启动Chrome的调试实例等冗余任务。 安装: npm i --save puppeteer 例子-打印用户代理信息: 复制代码 const puppeteer = require('puppeteer'); (async() => { const browser = await puppeteer.launch(); console.log(await browser.version()); await browser.close(); })(); 复制代码 例子-截屏 复制代码 const puppeteer = require('puppeteer'); (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle2'}); await page.pdf({path: 'page.pdf', format: 'A4'}); await browser.close(); })(); 复制代码 查看 Puppeteer's 文档 学习Puppeteer的更多用法。 3.2 CRI库 相对于Puppeteer's API来说,chrome-remote-interface 是一个低阶的库,推荐使用它更接近底层地直接使用DevTools协议。 打开Chrome chrome-remote-interface不能打开Chrome,因此需要自己打开Chrome。 在CLI部分,我们使用--headless --remote-debugging-port = 9222手动打开Chrome。但是,要实现完全自动化测试,您可能希望从应用程序中生成Chrome。 使用 child——process 的一种方式: 复制代码 const execFile = require('child_process').execFile; function launchHeadlessChrome(url, callback) { // Assuming MacOSx. const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'; execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback); } launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => { ... }); 复制代码 但是如果你想要一个适用于多个平台的可移植解决方案,那么事情会变得棘手。看看Chrome的硬编码路径吧:( 使用ChromeLaucher Lighthouse 是测试web应用质量绝佳工具。用于启动Chrome的强大的模块就是在Lighthouse中开发的,现在可以单独使用。 chrome-launcher NPM module 可以找到Chrome的安装路径,设置调试实例,打开浏览器,并且当程序运行完成时关掉它。最棒的是,由于Node,它可以跨平台工作! 默认情况下,chrome-launcher会尝试启动Chrome Canary(如果已安装),但可以更改它以手动选择要使用的Chrome。要使用它,首先从npm安装: npm i --save chrome-launcher 例子-使用 chrome-launcher 启动Headless模式 复制代码 const chromeLauncher = require('chrome-launcher'); // 可选: 设置launcher的日志记录级别以查看其输出 // 安装:: npm i --save lighthouse-logger // const log = require('lighthouse-logger'); // log.setLevel('info'); /** * 启动Chrome的调试实例 * @param {boolean=} headless True (default) 启动headless模式的Chrome. * False 启动Chrome的完成版本. * @return {Promise} */ function launchChrome(headless=true) { return chromeLauncher.launch({ // port: 9222, // Uncomment to force a specific port of your choice. chromeFlags: [ '--window-size=412,732', '--disable-gpu', headless ? '--headless' : '' ] }); } launchChrome().then(chrome => { console.log(`Chrome debuggable on port: ${chrome.port}`); ... // chrome.kill(); }); 复制代码 运行此脚本并没有太大作用,但在任务管理器中应该可以看到Chrome实例已启动,内容为 about:blank 。但是没有浏览器界面。因为是headless模式。 要控制浏览器,我们需要DevTools协议! 检索有关页面的信息 安装: npm i --save chrome-remote-interface 例子-打印用户代理 复制代码 const CDP = require('chrome-remote-interface'); ... launchChrome().then(async chrome => { const version = await CDP.Version({port: chrome.port}); console.log(version['User-Agent']); }); 复制代码 结果类似于: HeadlessChrome/60.0.3082.0 例子-检查网站是否有应用列表 复制代码 const CDP = require('chrome-remote-interface'); ... (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); // Extract the DevTools protocol domains we need and enable them. // See API docs: https://chromedevtools.github.io/devtools-protocol/ const {Page} = protocol; await Page.enable(); Page.navigate({url: 'https://www.chromestatus.com/'}); // Wait for window.onload before doing stuff. Page.loadEventFired(async () => { const manifest = await Page.getAppManifest(); if (manifest.url) { console.log('Manifest: ' + manifest.url); console.log(manifest.data); } else { console.log('Site has no app manifest'); } protocol.close(); chrome.kill(); // Kill Chrome. }); })(); 复制代码 例子-使用DOM API提取页面的 复制代码 const CDP = require('chrome-remote-interface'); ... (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); // Extract the DevTools protocol domains we need and enable them. // See API docs: https://chromedevtools.github.io/devtools-protocol/ const {Page, Runtime} = protocol; await Promise.all([Page.enable(), Runtime.enable()]); Page.navigate({url: 'https://www.chromestatus.com/'}); // Wait for window.onload before doing stuff. Page.loadEventFired(async () => { const js = "document.querySelector('title').textContent"; // Evaluate the JS expression in the page. const result = await Runtime.evaluate({expression: js}); console.log('Title of page: ' + result.result.value); protocol.close(); chrome.kill(); // Kill Chrome. }); })(); 复制代码 4. 使用Selenium,W​​ebDriver和ChromeDriver 现在,Selenium打开了一个完整地Chrome的实例,也就是说,换句话说,它是一种自动化解决方案,但并非完全headless。但是,Selenium可以通过一些配置来运行headless Chrome。我建议使用headless Chrome运行Selenium,若你还是想要如何自己设置的完整说明,我已经在下面的一些例子中展示了如何让你放弃。 使用ChromeDriver ChromeDriver 2.32使用了Chrome61,并且在headless Chrome运行的更好。 安装: npm i --save-dev selenium-webdriver chromedriver 例子 复制代码 const fs = require('fs'); const webdriver = require('selenium-webdriver'); const chromedriver = require('chromedriver'); const chromeCapabilities = webdriver.Capabilities.chrome(); chromeCapabilities.set('chromeOptions', {args: ['--headless']}); const driver = new webdriver.Builder() .forBrowser('chrome') .withCapabilities(chromeCapabilities) .build(); // Navigate to google.com, enter a search. driver.get('https://www.google.com/'); driver.findElement({name: 'q'}).sendKeys('webdriver'); driver.findElement({name: 'btnG'}).click(); driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000); // Take screenshot of results page. Save to disk. driver.takeScreenshot().then(base64png => { fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64')); }); driver.quit(); 复制代码 使用WebDriverIO WebDriverIO 是Selenium WebDriver之上的更高阶的API。 安装: npm i --save-dev webdriverio chromedriver 例子-chromestatus.com上的CSS filter功能 复制代码 const webdriverio = require('webdriverio'); const chromedriver = require('chromedriver'); const PORT = 9515; chromedriver.start([ '--url-base=wd/hub', `--port=${PORT}`, '--verbose' ]); (async () => { const opts = { port: PORT, desiredCapabilities: { browserName: 'chrome', chromeOptions: {args: ['--headless']} } }; const browser = webdriverio.remote(opts).init(); await browser.url('https://www.chromestatus.com/features'); const title = await browser.getTitle(); console.log(`Title: ${title}`); await browser.waitForText('.num-features', 3000); let numFeatures = await browser.getText('.num-features'); console.log(`Chrome has ${numFeatures} total features`); await browser.setValue('input[type="search"]', 'CSS'); console.log('Filtering features...'); await browser.pause(1000); numFeatures = await browser.getText('.num-features'); console.log(`Chrome has ${numFeatures} CSS features`); const buffer = await browser.saveScreenshot('screenshot.png'); console.log('Saved screenshot...'); chromedriver.stop(); browser.end(); })(); 复制代码 5. 更多资源 以下是一些有用的资源,可帮助您入门: 文档: DevTools Protocol Viewer - API参考文档 工具: chrome-remote-interface - 包装DevTools协议的节点模块 Lighthouse - 用于测试Web应用质量的自动化工具;大量使用协议 chrome-launcher - 节点模块,用于启动Chrome,为自动化做好准备 演示: "The Headless Web" - Paul Kinlan关于使用Headless和api.ai的博客文章 6. FAQ 6.1 是否需要 --disable-gpu 命令? 仅Windows平台需要。其他平台不需要。--disable-gpu命令是一个临时解决一些错误的方案。在将来的Chrome版本中,不再需要此命令。有关更多信息,请参阅 crbug.com/737678。 6.2 是否需要 Xvfb? 不需要。Headless Chrome不使用窗口,因此不再需要像Xvfb这样的显示服务器。没有它,也可以愉快地运行自动化测试。 什么是Xvfb?Xvfb是一种用于类Unix系统的内存显示服务器,它使您能够运行图形应用程序(如Chrome)而无需附加物理显示设备。许多人使用Xvfb运行早期版本的Chrome进行“headless”测试。 6.3 如何创建运行Headless Chrome的Docker容器? 看看lighthouse-ci。它有一个示例 Dockerfile ,它使用node:8-slim作为基本映像,在App Engine Flex上安装+ 运行Lighthouse 。 6.4 Headless Chrome与PhantomJS有什么关系? Headless Chrome与PhantomJS等工具类似。两者都可用于headless环境中的自动化测试。两者之间的主要区别在于Phantom使用较旧版本的WebKit作为其渲染引擎,而Headless Chrome使用最新版本的Blink。 目前,Phantom还提供了比DevTools 协议更高级别的API。 6.5 在哪里提交bugs? 对于Headless Chrome的bugs,请在crbug.com上提交。 对于DevTools协议中的错误,请将它们发送到github.com/ChromeDevTools/devtools-protocol。https://www.cnblogs.com/chaoxiZ/p/9664150.html </div> <div class="article-tags"> <div class="tags-label">关键字:</div> <div class="tag-list"> </div> </div> </div> </div> <!-- 侧边栏 --> <div class="col-lg-4"> <!-- 热门资讯 --> <div class="sidebar"> <h4 class="sidebar-title"><a href="/">青岛软件培训</a></h4> <div class="hot-news-item" onclick="location.href=news-detail.html?id=2"> <p class="card-text"> 可能你正在寻找一家靠谱的IT培训机构, 渴望突破职业瓶颈, 找一份得体的工作。 恰巧万码学堂正在寻找像你这样不甘平凡的追光者! 我们拒绝纸上谈兵,直接参与真实开发流程!<br> <b>现在行动,未来可期</b>‌<br> 立即拨打<a href="tel:0532-85025005">0532-85025005</a>,预约免费职业规划咨询 前20名咨询者赠送《2025高薪技术岗位白皮书》!<br> <b>你不是在报名课程,而是在投资五年后的自己!</b> </p> <a href="http://www.qingruanit.net/c_online/p_yudingshitingke.html" target="_blank" class="btn btn-primary">申请免费试听课程</a> </div> </div> </div> </div> </div> </section> <!-- 数据统计 --> <section class="stats-section"> <div class="container"> <div class="row"> <div class="col-lg-3 col-md-6 mb-4"> <div class="stat-item"> <span class="stat-number">50000+</span> <div class="stat-label">5万行代码练就真实本领</div> </div> </div> <div class="col-lg-3 col-md-6 mb-4"> <div class="stat-item"> <span class="stat-number">17年</span> <div class="stat-label">创办于2008年老牌培训机构</div> </div> </div> <div class="col-lg-3 col-md-6 mb-4"> <div class="stat-item"> <span class="stat-number">1000+</span> <div class="stat-label">合作企业</div> </div> </div> <div class="col-lg-3 col-md-6 mb-4"> <div class="stat-item"> <span class="stat-number">98%</span> <div class="stat-label">就业率</div> </div> </div> </div> </div> </section> <!-- 联系我们 --> <section id="contact" class="py-5"> <div class="container"> <h2 class="section-title">联系我们</h2> <div class="row justify-content-center"> <div class="col-lg-8"> <div class="row"> <div class="col-md-6 mb-4"> <div class="text-center"> <i class="fas fa-phone feature-icon"></i> <h4>电话咨询</h4> <p class="feature-text">0532-85025005</p> </div> </div> <div class="col-md-6 mb-4"> <div class="text-center"> <i class="fas fa-envelope feature-icon"></i> <h4>扫码添加微信</h4> <p class="feature-text"> <a href="https://work.weixin.qq.com/kfid/kfc5d203e0a7d2f3da5" target="_blank"><img src="https://www.wanmait.com/static/images/weixin.png"></a> </p> </div> </div> </div> </div> </div> </div> </section> <!-- 友情链接 --> <!-- <section class="friends-links"> <div class="container"> <h3 class="friends-links-title">友情链接</h3> <div class="friends-links-container"> <a href="#" target="_blank">腾讯课堂</a> </div> </div> </section> --> <!-- 页脚 --> <footer class="footer"> <div class="container"> <div class="row"> <div class="col-lg-6"> <h5 class="footer-title">万码学堂</h5> <p class="footer-text"><a href="http://www.ruanjianpeixun.net">青岛软件培训</a></p> </div> <div class="col-lg-6"> <h5 class="footer-title">选择一家好的青岛软件培训学校,就要看教学质量和口碑</h5> </div> </div> <hr class="my-4" style="border-color: #34495e;"> <div class="row"> <div class="col-12 text-center"> <p class="footer-text mb-0">万码学堂-做最负责任的教育-联系电话0532-85025005<p><a href="https://beian.miit.gov.cn/" target="_blank">鲁ICP备09077726号-3</a></p></p> </div> </div> </div> <script src="https://www.wanmait.com/online/wmonline.js?site=3"></script> </footer> </body> </html> <!-- JavaScript --> <script src="/bootstrap/js/jquery-3.6.0.min.js"></script> <script src="/bootstrap/js/bootstrap.bundle.min.js"></script> <script> // 确保导航栏功能正常工作 document.addEventListener("DOMContentLoaded", function() { // 手动初始化Bootstrap的Collapse组件 const navbarToggler = document.querySelector(".navbar-toggler"); const navbarCollapse = document.querySelector(".navbar-collapse"); if (navbarToggler && navbarCollapse) { navbarToggler.addEventListener("click", function(e) { e.preventDefault(); navbarCollapse.classList.toggle("show"); // 更新aria-expanded属性 const isExpanded = navbarCollapse.classList.contains("show"); navbarToggler.setAttribute("aria-expanded", isExpanded); }); } // 导航栏滚动效果 window.addEventListener("scroll", function() { const navbar = document.querySelector(".navbar"); if (window.scrollY > 50) { navbar.style.background = "rgba(255, 255, 255, 0.95)"; navbar.style.backdropFilter = "blur(10px)"; } else { navbar.style.background = "#ffffff"; navbar.style.backdropFilter = "none"; } }); // 数字动画效果 function animateNumbers() { const statNumbers = document.querySelectorAll(".stat-number"); statNumbers.forEach(stat => { const target = parseInt(stat.textContent.replace(/[^\d]/g, "")); const suffix = stat.textContent.replace(/[\d]/g, ""); let current = 0; const increment = target / 50; const timer = setInterval(() => { current += increment; if (current >= target) { current = target; clearInterval(timer); } stat.textContent = Math.floor(current) + suffix; }, 30); }); } // 当统计区域进入视口时触发动画 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { animateNumbers(); observer.unobserve(entry.target); } }); }); const statsSection = document.querySelector(".stats-section"); if (statsSection) { observer.observe(statsSection); } }); </script>