Puppeteer는 Chrome DevTools Protocol(CDP)을 추상화한 고수준 Node.js 라이브러리이다. CDP를 직접 다루는 것보다 사용하기 쉬운 API를 제공하지만, 때로는 CDP를 직접 사용해야 하는 경우가 있다.
CDP란? 쉽게 말해서, 개발자 도구(Chrome DevTools)가 브라우저와 통신하는 방식이다. 우리가 Chrome 개발자 도구에서 할 수 있는 모든 작업(요소 검사, 네트워크 모니터링, 자바스크립트 디버깅 등)을 프로그래밍 방식으로 수행할 수 있게 해준다.
Puppeteer는 내부적으로 Chrome DevTools Protocol(CDP)을 사용하여 Chrome/Chromium 브라우저와 통신한다. 마치 레고 블록처럼 구성되있다.
Browser (Chrome/Chromium)
├── Browser Context (incognito)
│ ├── Page
│ │ ├── Frame
│ │ │ └── Execution Context
│ │ └── Worker
│ └── CDP Session
└── Target
// Puppeteer의 high-level API
await page.click('.button');
// 실제 내부에서는 이런 CDP 명령어들이 실행됨
// 1. DOM 요소 찾기
// 2. 요소가 보이는지 확인
// 3. 클릭 이벤트 발생
또한, Chrome/Chromium은 멀티 프로세스 아키텍처를 사용한다.
// 프로세스 정보 확인
const client = await page.target().createCDPSession();
const processes = await client.send('SystemInfo.getProcessInfo');
console.log('현재 실행 중인 프로세스:', processes);
// Puppeteer 방식 (High-level)
await page.goto('https://example.com');
await page.type('#input', 'hello! world');
// 동일한 작업의 CDP 방식 (Low-level)
const client = await page.target().createCDPSession();
await client.send('Page.navigate', { url: 'https://example.com' });
await client.send('Input.insertText', { text: 'hello! wolrd' });
// Puppeteer와 CDP를 함께 사용하는 예
const page = await browser.newPage();
const client = await page.target().createCDPSession();
// Puppeteer API 사용
await page.goto('https://example.com');
// CDP 직접 사용으로 세밀한 제어
await client.send('Network.enable');
await client.send('Network.emulateNetworkConditions', {
offline: false,
latency: 100,
downloadThroughput: 1024 * 1024,
uploadThroughput: 1024 * 1024
});
// Puppeteer 방식
await page.setRequestInterception(true);
page.on('request', request => {
request.continue();
});
// CDP 방식
const client = await page.target().createCDPSession();
await client.send('Network.enable');
await client.send('Network.setRequestInterception', { patterns: [{ urlPattern: '*' }] });
client.on('Network.requestIntercepted', async ({ interceptionId, request }) => {
await client.send('Network.continueInterceptedRequest', { interceptionId });
});
// Puppeteer 방식
const metrics = await page.metrics();
// CDP 방식 - 더 상세한 정보 접근
const client = await page.target().createCDPSession();
await client.send('Performance.enable');
const performanceMetrics = await client.send('Performance.getMetrics');
CDP는 브라우저의 거의 모든 기능에 접근할 수 있는 "저수준" 인터페이스를 제공하며, Puppeteer는 이를 사용하기 쉽게 추상화한 "고수준" 인터페이스를 제공한다.
따라서, 딱 보기에도 CDP 방식이 다루기엔 더 까다로워보인다. 그렇다면 언제 어떤 방식을 사용하는것이 좋을까?
// 간단한 자동화 작업
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await page.pdf({ path: 'page.pdf' });
// 세밀한 제어가 필요한 작업
const client = await page.target().createCDPSession();
await client.send('Emulation.setDeviceMetricsOverride', {
width: 375,
height: 812,
deviceScaleFactor: 3,
mobile: true
});
물론 같이 사용할 경우도 존재한다.
async function setupBrowserAutomation() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const client = await page.target().createCDPSession();
// Puppeteer로 기본 설정
await page.setViewport({ width: 1200, height: 800 });
// CDP로 세밀한 제어
await client.send('Network.enable');
await client.send('Network.setBypassServiceWorker', { bypass: true });
// 두 API를 조합한 고급 기능 구현
client.on('Network.responseReceived', async ({ requestId, response }) => {
if (response.status === 404) {
// Puppeteer API로 스크린샷 캡처
await page.screenshot({
path: `error-${requestId}.png`
});
}
});
}
이러한 아키텍처 이해를 바탕으로 Puppeteer와 CDP를 더 효과적으로 활용할 수 있으며, 특히 성능 최적화와 리소스 관리에 있어 더 좋은 선택을 할 수 있다.
https://jsoverson.medium.com/using-chrome-devtools-protocol-with-puppeteer-737a1300bac0
https://chromedevtools.github.io/devtools-protocol/
https://github.com/ChromeDevTools/devtools-protocol