การเริ่มต้นใช้งาน Chrome แบบ Headless

TL;DR

Chrome แบบ Headless พร้อมให้บริการใน Chrome 59 เป็นวิธีเรียกใช้เบราว์เซอร์ Chrome ในสภาพแวดล้อมแบบ Headless พูดง่ายๆ คือ เรียกใช้ Chrome โดยไม่ใช้ Chrome ซึ่งจะนําฟีเจอร์ทั้งหมดของแพลตฟอร์มเว็บสมัยใหม่ที่ Chromium และเครื่องมือแสดงผล Blink ให้บริการมาไว้ในบรรทัดคําสั่ง

เหตุใดจึงมีประโยชน์

เบราว์เซอร์แบบ Headless เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการทดสอบอัตโนมัติและสภาพแวดล้อมเซิร์ฟเวอร์ที่คุณไม่จำเป็นต้องมีเชลล์ UI ที่มองเห็นได้ เช่น คุณอาจต้องการทำการทดสอบกับหน้าเว็บจริง สร้าง PDF ของหน้าเว็บ หรือตรวจสอบวิธีที่เบราว์เซอร์แสดงผล URL

การเริ่มใช้งานแบบไม่มีส่วนหัว (CLI)

วิธีที่ง่ายที่สุดในการเริ่มต้นใช้งานโหมด headless คือเปิดไบนารีของ Chrome จากบรรทัดคำสั่ง หากติดตั้ง Chrome 59 ขึ้นไป ให้เริ่ม Chrome ด้วย Flag --headless ดังนี้

chrome \
--headless \                   # Runs Chrome in headless mode.
--disable-gpu \                # Temporarily needed if running on Windows.
--remote-debugging-port=9222 \
https://www.chromestatus.com   # URL to open. Defaults to about:blank.

chrome ควรชี้ไปยังการติดตั้ง Chrome ตำแหน่งที่แน่นอนจะแตกต่างกันไปในแต่ละแพลตฟอร์ม เนื่องจากใช้ Mac ฉันจึงสร้างอีเมลแทนที่สะดวกสำหรับ Chrome แต่ละเวอร์ชันที่ติดตั้งไว้

หากคุณใช้ Chrome เวอร์ชันเสถียรและไม่สามารถรับเวอร์ชันเบต้าได้ เราขอแนะนำให้ใช้chrome-canaryสิ่งต่อไปนี้

alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

ดาวน์โหลด Chrome Canary ที่นี่

ฟีเจอร์บรรทัดคำสั่ง

ในบางกรณี คุณอาจไม่จําเป็นต้องเขียนสคริปต์แบบเป็นโปรแกรมสําหรับ Headless Chrome มีFlag บรรทัดคำสั่งที่มีประโยชน์บางอย่างสำหรับดำเนินการงานทั่วไป

การพิมพ์ DOM

แฟล็ก --dump-dom จะพิมพ์ document.body.innerHTML ไปยัง stdout

    chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/

สร้าง PDF

Flag --print-to-pdf จะสร้าง PDF ของหน้าเว็บ

chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/

ถ่ายภาพหน้าจอ

หากต้องการจับภาพหน้าจอของหน้าเว็บ ให้ใช้การแจ้ง --screenshot โดยทำดังนี้

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

# Size of a standard letterhead.
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 ที่จะช่วยเหลือคุณ ดูหัวข้อการใช้ Chrome แบบ Headless เป็นเครื่องมือจับภาพหน้าจออัตโนมัติ

โหมด REPL (อ่าน ประเมิน และแสดงผลซ้ำ)

Flag --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
$

กำลังแก้ไขข้อบกพร่องของ Chrome โดยไม่ใช้ UI ของเบราว์เซอร์ใช่ไหม

เมื่อคุณเรียกใช้ Chrome ด้วย --remote-debugging-port=9222 ระบบจะเริ่มอินสแตนซ์ที่เปิดใช้โปรโตคอล DevTools โปรโตคอลนี้ใช้เพื่อสื่อสารกับ Chrome และขับเคลื่อนอินสแตนซ์เบราว์เซอร์แบบ Headless นอกจากนี้ ยังเป็นเครื่องมือที่ Sublime, VS Code และ Node ใช้สำหรับการแก้ไขข้อบกพร่องระยะไกลของแอปพลิเคชันด้วย #synergy

เนื่องจากคุณไม่มี UI ของเบราว์เซอร์เพื่อดูหน้าเว็บ ให้ไปที่ http://localhost:9222 ในเบราว์เซอร์อื่นเพื่อตรวจสอบว่าทุกอย่างทำงานได้ คุณจะเห็นรายการหน้าเว็บที่ตรวจสอบได้ ซึ่งคุณสามารถคลิกดูและดูสิ่งที่ Headless แสดงผล

เครื่องมือสำหรับนักพัฒนาเว็บระยะไกล
UI การแก้ไขข้อบกพร่องระยะไกลของ DevTools

จากตรงนี้ คุณสามารถใช้ฟีเจอร์เครื่องมือสำหรับนักพัฒนาเว็บที่คุ้นเคยเพื่อตรวจสอบ แก้ไขข้อบกพร่อง และปรับแต่งหน้าเว็บได้ตามปกติ หากคุณใช้ Headless แบบเป็นโปรแกรม หน้านี้ยังเป็นเครื่องมือแก้ไขข้อบกพร่องที่มีประสิทธิภาพในการดูคําสั่งโปรโตคอล DevTools ดิบทั้งหมดที่ส่งผ่านเครือข่ายเพื่อสื่อสารกับเบราว์เซอร์

การใช้แบบเป็นโปรแกรม (Node)

ผู้เชิดหุ่น

Puppeteer เป็นไลบรารี Node ที่พัฒนาโดยทีม Chrome ซึ่งให้บริการ API ระดับสูงเพื่อควบคุม Chrome แบบ Headless (หรือแบบเต็ม) ซึ่งคล้ายกับไลบรารีการทดสอบอัตโนมัติอื่นๆ เช่น Phantom และ NightmareJS แต่ใช้งานได้กับ Chrome เวอร์ชันล่าสุดเท่านั้น

นอกเหนือจากนี้ Puppeteer ยังใช้ถ่ายภาพหน้าจอ สร้าง PDF ไปยังส่วนต่างๆ ในหน้าเว็บ และดึงข้อมูลเกี่ยวกับหน้าเว็บเหล่านั้นได้อย่างง่ายดาย เราขอแนะนําให้ใช้ไลบรารีนี้หากต้องการทําให้การทดสอบเบราว์เซอร์เป็นแบบอัตโนมัติอย่างรวดเร็ว ซึ่งจะซ่อนความซับซ้อนของโปรโตคอล DevTools และจัดการงานที่ซ้ำซ้อน เช่น การเปิดตัวอินสแตนซ์แก้ไขข้อบกพร่องของ Chrome

วิธีติดตั้ง

npm i --save puppeteer

ตัวอย่าง - พิมพ์ User Agent

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();
})();

ดูข้อมูลเพิ่มเติมเกี่ยวกับ API แบบเต็มได้ในเอกสารประกอบของ Puppeteer

ไลบรารี CRI

chrome-remote-interface เป็นไลบรารีระดับล่างกว่า API ของ Puppeteer เราขอแนะนำให้ใช้วิธีนี้หากคุณต้องการอยู่ใกล้กับโลหะและใช้โปรโตคอล DevTools โดยตรง

การเปิดตัว Chrome

chrome-remote-interface จะไม่เปิด Chrome ให้คุณ คุณจึงต้องดำเนินการดังกล่าวด้วยตนเอง

ในส่วน CLI เราได้เริ่ม Chrome ด้วยตนเองโดยใช้ --headless --remote-debugging-port=9222 อย่างไรก็ตาม หากต้องการทำให้การทดสอบเป็นแบบอัตโนมัติทั้งหมด คุณอาจต้องสร้าง 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 ที่เขียนไว้ในโค้ด :(

การใช้ ChromeLauncher

Lighthouse เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการทดสอบคุณภาพของเว็บแอป โมดูลที่มีประสิทธิภาพสำหรับการเปิดใช้ Chrome ได้รับการพัฒนาภายใน Lighthouse และตอนนี้ได้แยกออกมาเพื่อใช้งานแบบสแตนด์อโลนแล้ว chrome-launcher โมดูล NPM จะค้นหาตำแหน่งที่ติดตั้ง Chrome, ตั้งค่าอินสแตนซ์แก้ไขข้อบกพร่อง, เปิดเบราว์เซอร์ และปิดเบราว์เซอร์เมื่อโปรแกรมเสร็จสิ้น ที่สำคัญคือใช้งานได้ข้ามแพลตฟอร์มด้วย Node

โดยค่าเริ่มต้น chrome-launcher จะพยายามเปิด Chrome Canary (หากติดตั้งไว้) แต่คุณสามารถเปลี่ยนเพื่อเลือก Chrome ที่จะใช้ด้วยตนเองได้ หากต้องการใช้ ให้ติดตั้งจาก npm ก่อน โดยทำดังนี้

npm i --save chrome-launcher

ตัวอย่าง - การใช้ chrome-launcher เพื่อเปิดใช้โหมดไม่มีส่วนหัว

const chromeLauncher = require('chrome-launcher');

// Optional: set logging level of launcher to see its output.
// Install it using: npm i --save lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');

/**
 * Launches a debugging instance of Chrome.
 * @param {boolean=} headless True (default) launches Chrome in headless mode.
 *     False launches a full version of Chrome.
 * @return {Promise<ChromeLauncher>}
 */
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 โปรดทราบว่าจะไม่มี UI ของเบราว์เซอร์ เราไม่มีส่วนหัว

เราต้องใช้โปรโตคอล DevTools เพื่อควบคุมเบราว์เซอร์

ดึงข้อมูลเกี่ยวกับหน้าเว็บ

มาติดตั้งไลบรารีกัน

npm i --save chrome-remote-interface
ตัวอย่าง

ตัวอย่าง - พิมพ์ User Agent

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

ตัวอย่าง - ตรวจสอบว่าเว็บไซต์มีไฟล์ Manifest ของเว็บแอปหรือไม่

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.
});

})();

ตัวอย่าง - ดึงข้อมูล <title> ของหน้าเว็บโดยใช้ 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.
});

})();

การใช้ Selenium, WebDriver และ ChromeDriver

ขณะนี้ Selenium จะเปิดอินสแตนซ์ Chrome แบบเต็ม กล่าวคือ เป็นโซลูชันอัตโนมัติแต่ไม่ใช่แบบ Headless ทั้งหมด อย่างไรก็ตาม Selenium สามารถกําหนดค่าให้ทํางานแบบ Headless Chrome ได้ เราขอแนะนําให้เรียกใช้ Selenium ด้วย Headless Chrome หากต้องการวิธีการแบบเต็มในการตั้งค่าด้วยตนเอง แต่เราได้ใส่ตัวอย่างบางส่วนไว้ด้านล่างเพื่อช่วยให้คุณเริ่มต้นใช้งาน

การใช้ ChromeDriver

ChromeDriver 2.32 ใช้ Chrome 61 และทำงานร่วมกับ Chrome แบบ Headless ได้ดี

ติดตั้ง

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 เป็น API ระดับที่สูงขึ้นบน Selenium WebDriver

ติดตั้ง

npm i --save-dev webdriverio chromedriver

ตัวอย่าง: กรองฟีเจอร์ CSS ใน chromestatus.com

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();

})();

แหล่งข้อมูลเพิ่มเติม

แหล่งข้อมูลที่มีประโยชน์สำหรับการเริ่มต้นใช้งานมีดังนี้

เอกสาร

เครื่องมือ

  • chrome-remote-interface - โมดูล Node ที่รวมโปรโตคอล DevTools
  • Lighthouse - เครื่องมืออัตโนมัติสำหรับทดสอบคุณภาพเว็บแอป ใช้งานโปรโตคอลนี้อย่างหนัก
  • chrome-launcher - โมดูล Node สำหรับเปิด Chrome พร้อมใช้งานการทำงานอัตโนมัติ

เดโม

  • "The Headless Web" - บล็อกโพสต์ที่ยอดเยี่ยมของ Paul Kinlan เกี่ยวกับการใช้ Headless กับ api.ai

คำถามที่พบบ่อย

ฉันต้องใช้การแจ้งว่าไม่เหมาะสม --disable-gpu ไหม

เฉพาะใน Windows เท่านั้น แพลตฟอร์มอื่นๆ ไม่จำเป็นต้องใช้ การแจ้งว่าไม่เหมาะสม --disable-gpu เป็นวิธีแก้ปัญหาชั่วคราวสำหรับข้อบกพร่องบางรายการ คุณจะไม่ต้องใช้ Flag นี้ใน Chrome เวอร์ชันต่อๆ ไป ดูข้อมูลเพิ่มเติมได้ที่ crbug.com/737678

ฉันยังต้องใช้ Xvfb อยู่ใช่ไหม

ไม่ Chrome แบบ headless ไม่ได้ใช้หน้าต่าง คุณจึงไม่จำเป็นต้องใช้เซิร์ฟเวอร์การแสดงผลอย่าง Xvfb อีกต่อไป คุณทําการทดสอบอัตโนมัติได้โดยไม่ต้องใช้

Xvfb คืออะไร Xvfb เป็นเซิร์ฟเวอร์การแสดงผลในหน่วยความจำสำหรับระบบที่คล้าย Unix ซึ่งช่วยให้คุณเรียกใช้แอปพลิเคชันกราฟิก (เช่น Chrome) ได้โดยไม่ต้องต่อจอแสดงผล ผู้ใช้จํานวนมากใช้ Xvfb เพื่อเรียกใช้ Chrome เวอร์ชันเก่าสําหรับการทดสอบ "Headless"

ฉันจะสร้างคอนเทนเนอร์ Docker ที่เรียกใช้ Chrome แบบ Headless ได้อย่างไร

โปรดดู lighthouse-ci ไฟล์นี้มีตัวอย่าง Dockerfile ใช้ node:8-slim เป็นอิมเมจฐาน ติดตั้ง + เรียกใช้ Lighthouse ใน App Engine Flex

ฉันใช้ Selenium / WebDriver / ChromeDriver กับเครื่องมือนี้ได้ไหม

ได้ ดูการใช้ Selenium, WebDriver และ ChromeDriver

การดำเนินการนี้เกี่ยวข้องกับ PhantomJS อย่างไร

Headless Chrome คล้ายกับเครื่องมืออย่าง PhantomJS ทั้ง 2 รายการนี้ใช้สำหรับการทดสอบอัตโนมัติในสภาพแวดล้อมแบบ Headless ได้ ความแตกต่างหลักระหว่าง 2 อย่างนี้คือ Phantom ใช้ WebKit เวอร์ชันเก่าเป็นเครื่องมือแสดงผล ส่วน Headless Chrome ใช้ Blink เวอร์ชันล่าสุด

ปัจจุบัน Phantom ยังมี API ระดับสูงกว่าโปรโตคอล DevTools ด้วย

ฉันจะรายงานข้อบกพร่องได้ที่ใด

สำหรับข้อบกพร่องเกี่ยวกับ Chrome แบบ Headless โปรดรายงานที่ crbug.com

สำหรับข้อบกพร่องในโปรโตคอล DevTools โปรดรายงานที่ github.com/ChromeDevTools/devtools-protocol