본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
AI Generated
2026. 1. 31. · 13 Views
Browser 자동화 시스템 완벽 가이드
Playwright와 Puppeteer를 활용한 브라우저 자동화의 핵심 개념을 실무 중심으로 학습합니다. CDP 프로토콜부터 스크린샷, 웹 스크래핑까지 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
목차
- 브라우저_자동화의_필요성
- Playwright_vs_Puppeteer
- src/browser_코드_분석
- CDP_Chrome_DevTools_Protocol_이해
- 스크린샷과_액션_구현
- 실전_웹_스크래핑_자동화
1. 브라우저 자동화의 필요성
어느 날 김개발 씨는 매일 아침 10개 이상의 웹사이트에서 데이터를 수동으로 수집하고 있었습니다. 클릭하고, 복사하고, 붙여넣기를 반복하는 단순 작업에 하루 2시간씩 소비되고 있었습니다.
선배 박시니어 씨가 그 모습을 보고 말했습니다. "그거, 자동화하면 5분이면 끝나는데요?"
브라우저 자동화는 사람이 웹 브라우저에서 하는 반복적인 작업을 코드로 대신 실행하는 기술입니다. 마치 로봇 비서가 여러분 대신 웹사이트를 방문하고, 버튼을 클릭하고, 정보를 수집하는 것과 같습니다.
이를 통해 개발자는 시간을 절약하고 더 중요한 일에 집중할 수 있습니다.
다음 코드를 살펴봅시다.
// Playwright를 사용한 기본 자동화 예제
import { chromium } from 'playwright';
async function autoLogin() {
// 브라우저 실행 (headless: false는 브라우저 창을 보여줌)
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// 웹사이트 방문
await page.goto('https://example.com/login');
// 입력 필드에 자동으로 정보 입력
await page.fill('#username', 'myuser@email.com');
await page.fill('#password', 'mypassword');
// 로그인 버튼 클릭
await page.click('button[type="submit"]');
// 페이지 로딩 대기
await page.waitForURL('**/dashboard');
console.log('로그인 성공!');
await browser.close();
}
김개발 씨는 스타트업에 입사한 지 두 달 된 주니어 개발자입니다. 매일 아침 출근하면 가장 먼저 하는 일이 있었습니다.
경쟁사 10곳의 웹사이트를 방문해서 가격 정보를 엑셀에 정리하는 것이었습니다. 첫 번째 사이트를 열고, 로그인하고, 가격 페이지로 이동하고, 숫자를 복사해서 붙여넣습니다.
그리고 다시 두 번째 사이트를 엽니다. 같은 과정을 10번 반복하다 보면 어느새 2시간이 훌쩍 지나갑니다.
손목도 아프고, 집중력도 떨어집니다. 어느 날 선배 박시니어 씨가 김개발 씨의 모니터를 보더니 놀란 표정을 지었습니다.
"아직도 그걸 수동으로 하고 있었어요? 그거 자동화하면 5분이면 끝나는데요." 브라우저 자동화란 무엇일까요?
쉽게 비유하자면, 브라우저 자동화는 마치 로봇 비서를 고용하는 것과 같습니다. 여러분이 매일 하는 반복적인 웹 작업을 정확히 기억했다가, 명령만 내리면 빠르고 정확하게 대신 실행해 주는 비서 말입니다.
사람은 실수도 하고 피곤해하지만, 이 로봇 비서는 24시간 지치지 않고 정확하게 일합니다. 자동화가 없던 시절에는 어땠을까요?
개발자들은 반복적인 웹 작업을 모두 손으로 직접 해야 했습니다. 100개의 계정으로 테스트를 해야 한다면?
100번 로그인하고 로그아웃해야 했습니다. 매일 아침 같은 데이터를 수집해야 한다면?
매일 2시간씩 클릭과 복사를 반복해야 했습니다. 더 큰 문제는 사람은 실수를 한다는 것입니다.
복사할 때 빠뜨릴 수도 있고, 피곤해서 잘못된 값을 입력할 수도 있습니다. 또한 시간이 지날수록 이런 단순 반복 작업은 개발자의 의욕을 떨어뜨립니다.
바로 이런 문제를 해결하기 위해 브라우저 자동화 기술이 등장했습니다. 브라우저 자동화를 사용하면 반복 작업을 완전히 자동화할 수 있습니다.
한 번 코드를 작성하면, 매일 아침 자동으로 실행되도록 설정할 수 있습니다. 또한 정확성도 보장됩니다.
코드는 피곤해하지 않고, 실수하지 않습니다. 무엇보다 시간 절약이라는 큰 이점이 있습니다.
김개발 씨가 2시간씩 하던 작업을 5분으로 줄일 수 있다면, 하루에 1시간 55분을 절약하는 셈입니다. 한 달이면 40시간, 거의 일주일 치 근무 시간을 아끼는 것과 같습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 chromium.launch()로 브라우저를 실행합니다.
headless: false 옵션은 브라우저 창을 실제로 보여주는 설정입니다. 개발할 때는 false로 설정해서 무슨 일이 일어나는지 눈으로 확인하는 것이 좋습니다.
다음으로 page.goto()로 웹사이트를 방문합니다. 마치 주소창에 URL을 입력하고 엔터를 누르는 것과 같습니다.
그리고 page.fill()로 입력 필드에 값을 채웁니다. CSS 선택자를 사용해서 정확한 입력 필드를 찾아냅니다.
마지막으로 page.click()으로 버튼을 클릭하고, waitForURL()로 페이지 이동이 완료될 때까지 기다립니다. 이렇게 사람이 하는 모든 동작을 코드로 표현할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 전자상거래 서비스를 개발한다고 가정해봅시다.
상품 가격 모니터링에 브라우저 자동화를 활용하면 경쟁사의 가격 변동을 실시간으로 추적할 수 있습니다. 회귀 테스트에도 유용합니다.
새 기능을 배포하기 전에 주요 시나리오를 자동으로 테스트해서 버그를 미리 발견할 수 있습니다. 많은 기업에서 E2E 테스트, 웹 스크래핑, 품질 보증 등에 이런 패턴을 적극적으로 사용하고 있습니다.
특히 QA 팀에서는 수백 개의 테스트 케이스를 자동화해서 배포 전에 자동으로 실행합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 웹사이트의 로봇 차단 정책을 무시하는 것입니다. 일부 웹사이트는 자동화 봇을 감지하고 차단합니다.
이렇게 하면 IP가 차단되거나 법적 문제가 발생할 수 있습니다. 따라서 공식 API가 있다면 API를 먼저 사용하고, 자동화는 테스트나 합법적인 용도로만 사용해야 합니다.
또한 적절한 딜레이를 넣어서 서버에 무리를 주지 않도록 배려해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 도움으로 자동화 스크립트를 작성한 김개발 씨는 감탄했습니다. "와, 정말 5분 만에 끝나네요!" 이제 김개발 씨는 매일 아침 스크립트를 실행하고, 절약된 2시간을 더 중요한 기능 개발에 사용합니다.
브라우저 자동화를 제대로 이해하면 반복 작업에서 해방되고, 더 창의적인 일에 집중할 수 있습니다.
실전 팁
💡 - 개발할 때는 headless: false로 설정해서 브라우저 동작을 눈으로 확인하세요
- 배포할 때는 headless: true로 설정하면 서버 리소스를 절약할 수 있습니다
- 웹사이트의 이용약관을 확인하고, 합법적인 범위 내에서만 자동화를 사용하세요
2. Playwright vs Puppeteer
박시니어 씨의 설명을 들은 김개발 씨는 곧바로 구글에 "브라우저 자동화"를 검색했습니다. 그런데 검색 결과에 Playwright, Puppeteer, Selenium 등 여러 도구가 나왔습니다.
"어떤 걸 써야 하죠?" 김개발 씨가 물었습니다. 박시니어 씨는 미소 지으며 답했습니다.
"요즘은 Playwright가 대세예요. Puppeteer도 좋지만 Playwright가 더 강력하죠."
Playwright와 Puppeteer는 모두 브라우저 자동화를 위한 Node.js 라이브러리입니다. Puppeteer는 구글이 만든 Chrome 전용 도구였고, Playwright는 마이크로소프트가 만든 멀티 브라우저 지원 도구입니다.
두 도구 모두 강력하지만, Playwright가 더 많은 기능과 브라우저를 지원합니다.
다음 코드를 살펴봅시다.
// Puppeteer 예제
import puppeteer from 'puppeteer';
async function puppeteerExample() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log('Puppeteer:', title);
await browser.close();
}
// Playwright 예제 (거의 동일한 API)
import { chromium } from 'playwright';
async function playwrightExample() {
// Chromium, Firefox, WebKit 모두 지원
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log('Playwright:', title);
await browser.close();
}
김개발 씨는 두 도구의 차이가 궁금했습니다. 코드를 보니 거의 비슷해 보이는데, 왜 다들 Playwright를 추천하는 걸까요?
박시니어 씨가 설명을 시작했습니다. "일단 역사부터 알아야 해요." Puppeteer는 2017년 구글이 만든 도구입니다.
구글 Chrome 팀이 직접 개발했기 때문에 Chrome과의 통합이 매우 뛰어났습니다. 당시에는 혁명적인 도구였습니다.
그 전까지 사용하던 Selenium보다 훨씬 빠르고 안정적이었으니까요. 하지만 Puppeteer에는 한 가지 아쉬운 점이 있었습니다.
Chrome과 Chromium만 지원한다는 것입니다. Firefox나 Safari에서는 테스트할 수 없었습니다.
실무에서는 여러 브라우저를 지원해야 하는 경우가 많은데, 이것이 큰 제약이었습니다. 그래서 등장한 것이 Playwright입니다.
Playwright는 2020년 마이크로소프트가 공개한 도구입니다. 재미있는 사실은 Playwright를 만든 사람들이 원래 Puppeteer를 만들었던 구글 개발자들이라는 점입니다.
그들이 마이크로소프트로 이직해서 Puppeteer의 단점을 보완한 새로운 도구를 만든 것입니다. Playwright의 가장 큰 장점은 멀티 브라우저 지원입니다.
하나의 코드로 Chromium, Firefox, WebKit(Safari)을 모두 테스트할 수 있습니다. chromium.launch() 대신 firefox.launch()나 webkit.launch()를 사용하면 됩니다.
코드는 거의 동일한데, 실행되는 브라우저만 바뀝니다. 예를 들어 전자상거래 사이트를 운영한다고 생각해봅시다.
고객들은 Chrome도 사용하고, Safari도 사용하고, Firefox도 사용합니다. Puppeteer로는 Chrome만 테스트할 수 있지만, Playwright로는 모든 브라우저에서 동일하게 작동하는지 자동으로 확인할 수 있습니다.
또 다른 장점은 더 강력한 기능입니다. Playwright는 자동 대기 기능이 더 스마트합니다.
예를 들어 버튼을 클릭하려고 할 때, 버튼이 화면에 나타날 때까지, 클릭 가능한 상태가 될 때까지 자동으로 기다립니다. Puppeteer에서는 이런 대기를 직접 코드로 작성해야 하는 경우가 많았습니다.
네트워크 요청 가로채기도 더 쉽습니다. API 응답을 모킹하거나, 특정 리소스를 차단하거나, 네트워크 속도를 제한하는 등의 작업을 간단한 코드로 할 수 있습니다.
이는 다양한 네트워크 환경에서 테스트할 때 매우 유용합니다. 그렇다면 Puppeteer는 쓸모없는 걸까요?
전혀 그렇지 않습니다. Puppeteer는 여전히 훌륭한 도구입니다.
Chrome만 지원하면 충분한 경우라면 Puppeteer도 좋은 선택입니다. 또한 Puppeteer는 더 오래된 도구이기 때문에 커뮤니티가 크고, 관련 자료도 많습니다.
하지만 새로운 프로젝트를 시작한다면 Playwright를 추천합니다. 더 많은 기능, 더 나은 안정성, 멀티 브라우저 지원이라는 장점이 있기 때문입니다.
위의 코드를 비교해보면 거의 동일합니다. API가 의도적으로 비슷하게 설계되어서, Puppeteer에서 Playwright로 마이그레이션하는 것도 어렵지 않습니다.
대부분의 경우 puppeteer를 playwright로 바꾸고, chromium을 명시적으로 import하는 정도만 수정하면 됩니다. 실제 현업에서는 어떻게 선택할까요?
스타트업이나 새 프로젝트라면 Playwright를 선택하는 것이 좋습니다. 장기적으로 더 많은 이점을 얻을 수 있습니다.
기존에 Puppeteer를 사용하고 있다면 굳이 급하게 바꿀 필요는 없습니다. 잘 작동하고 있다면 그대로 사용해도 괜찮습니다.
멀티 브라우저 테스트가 필요하다면 Playwright가 거의 유일한 선택지입니다. 최신 기능이 필요하다면 Playwright가 더 활발하게 개발되고 있으므로 Playwright를 선택하세요.
김개발 씨는 고개를 끄덕였습니다. "그럼 Playwright로 시작하는 게 낫겠네요!" 박시니어 씨가 웃으며 말했습니다.
"좋은 선택이에요. 한 번 배워두면 Chrome, Firefox, Safari 모두 테스트할 수 있으니까요."
실전 팁
💡 - 새 프로젝트라면 Playwright를 선택하세요. 멀티 브라우저 지원이 큰 장점입니다
- Puppeteer에서 Playwright로 마이그레이션은 어렵지 않습니다. API가 거의 비슷합니다
- 두 도구 모두 공식 문서가 매우 잘 되어 있으니 공식 문서를 활용하세요
3. src/browser 코드 분석
김개발 씨는 회사 코드베이스를 살펴보다가 src/browser 폴더를 발견했습니다. 그 안에는 BrowserManager, ScreenshotService, ActionService 같은 파일들이 있었습니다.
"이게 다 뭐죠?" 박시니어 씨가 설명했습니다. "아, 그거요.
우리가 브라우저 자동화를 체계적으로 관리하기 위해 만든 구조예요."
src/browser 구조는 브라우저 자동화 코드를 깔끔하게 정리하는 설계 패턴입니다. BrowserManager는 브라우저 생명주기를 관리하고, ScreenshotService는 스크린샷 기능을, ActionService는 클릭이나 입력 같은 동작을 담당합니다.
이런 관심사의 분리를 통해 코드의 유지보수성이 크게 향상됩니다.
다음 코드를 살펴봅시다.
// src/browser/BrowserManager.ts
import { chromium, Browser, BrowserContext, Page } from 'playwright';
export class BrowserManager {
private browser: Browser | null = null;
private context: BrowserContext | null = null;
// 브라우저 초기화
async initialize() {
this.browser = await chromium.launch({ headless: true });
this.context = await this.browser.newContext();
}
// 새 페이지 생성
async newPage(): Promise<Page> {
if (!this.context) throw new Error('브라우저가 초기화되지 않았습니다');
return await this.context.newPage();
}
// 리소스 정리
async close() {
await this.context?.close();
await this.browser?.close();
}
}
김개발 씨가 처음 본 회사 코드는 한 파일에 모든 것이 들어 있었습니다. 브라우저를 열고, 스크린샷 찍고, 클릭하고, 닫는 모든 코드가 500줄짜리 파일 하나에 섞여 있었습니다.
어디에 뭐가 있는지 찾기도 어려웠고, 수정하려면 파일 전체를 읽어야 했습니다. 박시니어 씨는 이 문제를 해결하기 위해 체계적인 구조를 만들었습니다.
마치 책을 장르별로 정리하는 도서관처럼, 브라우저 자동화 코드도 역할별로 분리하면 훨씬 관리하기 쉬워집니다. 스크린샷 관련 코드는 ScreenshotService에, 클릭이나 입력 같은 동작은 ActionService에, 브라우저 생명주기 관리는 BrowserManager에 넣는 것입니다.
BrowserManager는 무엇을 할까요? BrowserManager는 브라우저의 생성, 사용, 종료를 담당합니다.
마치 자동차 키를 관리하는 발렛파킹 직원과 같습니다. 필요할 때 브라우저를 꺼내주고, 다 쓰고 나면 안전하게 정리합니다.
initialize() 메서드는 브라우저를 시작합니다. 단순히 chromium.launch()를 호출하는 것 이상으로, BrowserContext도 함께 생성합니다.
BrowserContext는 브라우저 내의 격리된 세션이라고 생각하면 됩니다. 쿠키나 로컬 스토리지가 서로 섞이지 않도록 분리해줍니다.
newPage() 메서드는 새 탭을 만듭니다. 브라우저가 초기화되지 않았다면 에러를 던져서 잘못된 사용을 방지합니다.
이런 방어적 프로그래밍은 런타임 에러를 줄여줍니다. close() 메서드는 모든 리소스를 정리합니다.
Context를 닫고, Browser를 닫습니다. 이 순서가 중요합니다.
자식부터 닫고 부모를 닫는 것입니다. 왜 이렇게 클래스로 분리할까요?
처음에는 간단한 스크립트로 시작합니다. 파일 하나에 모든 코드를 넣어도 작동합니다.
하지만 프로젝트가 커지면 문제가 생깁니다. 스크린샷 기능을 수정했는데 클릭 기능이 망가진다든지, 어디에 버그가 있는지 찾기 어렵다든지 하는 문제들입니다.
클래스로 분리하면 이런 문제가 해결됩니다. 각 클래스는 자기 역할만 담당합니다.
BrowserManager는 브라우저 관리만, ScreenshotService는 스크린샷만 담당합니다. 이렇게 하면 버그가 생겨도 어디를 봐야 할지 명확합니다.
또한 테스트하기 쉬워집니다. 각 클래스를 독립적으로 테스트할 수 있습니다.
BrowserManager가 제대로 브라우저를 열고 닫는지만 테스트하면 됩니다. 다른 기능과 섞여 있지 않으니 테스트 코드도 간단합니다.
재사용성도 높아집니다. 다른 프로젝트에서 브라우저 관리가 필요하면 BrowserManager 클래스만 가져다 쓰면 됩니다.
500줄짜리 파일을 통째로 복사할 필요가 없습니다. 실제 현업에서는 어떻게 확장될까요?
큰 프로젝트에서는 더 많은 서비스가 추가됩니다. NavigationService는 페이지 이동을 담당하고, WaitService는 요소가 나타날 때까지 기다리는 로직을 담당합니다.
CookieService는 쿠키 관리를, StorageService는 로컬 스토리지 관리를 담당합니다. 각 서비스는 BrowserManager에 의존합니다.
BrowserManager가 제공하는 Page 객체를 받아서 작업합니다. 이런 의존성 주입 패턴은 코드를 느슨하게 결합시켜서 유연성을 높입니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 과도한 추상화입니다.
간단한 스크립트인데 불필요하게 10개의 클래스로 나누면 오히려 복잡해집니다. 이렇게 하면 코드를 이해하기 어려워지고, 수정할 때마다 여러 파일을 왔다 갔다 해야 합니다.
따라서 적절한 균형이 중요합니다. 코드가 200-300줄을 넘어가고, 여러 기능이 섞여 있다면 분리를 고려하세요.
하지만 50줄짜리 간단한 스크립트라면 굳이 클래스로 만들 필요는 없습니다. 김개발 씨는 코드를 보며 감탄했습니다.
"이렇게 정리하니까 훨씬 보기 좋네요!" 박시니어 씨가 웃으며 말했습니다. "처음에는 귀찮아 보이지만, 나중에 수정할 일이 생기면 이 구조에 감사하게 될 거예요." 실제로 한 달 후, 스크린샷 기능에 워터마크를 추가하는 요청이 들어왔습니다.
김개발 씨는 ScreenshotService 파일만 열어서 10분 만에 작업을 끝냈습니다. 만약 코드가 한 파일에 섞여 있었다면 몇 시간이 걸렸을 것입니다.
실전 팁
💡 - 코드가 200줄을 넘어가면 관심사별로 분리를 고려하세요
- 각 클래스는 하나의 책임만 가져야 합니다 (단일 책임 원칙)
- 간단한 스크립트는 굳이 클래스로 만들지 마세요. 과도한 추상화는 독이 됩니다
4. CDP Chrome DevTools Protocol 이해
어느 날 김개발 씨는 코드에서 이상한 용어를 발견했습니다. cdp.send('Page.captureScreenshot')라는 코드가 있었습니다.
"CDP가 뭐죠?" 박시니어 씨가 설명했습니다. "아, 그거요.
Chrome DevTools Protocol의 약자예요. Playwright나 Puppeteer가 브라우저와 통신할 때 사용하는 저수준 프로토콜이죠."
**CDP(Chrome DevTools Protocol)**는 Chrome 브라우저와 외부 프로그램이 통신하는 방법을 정의한 프로토콜입니다. 개발자 도구에서 사용하는 것과 동일한 프로토콜로, Playwright와 Puppeteer도 내부적으로 CDP를 사용합니다.
CDP를 직접 사용하면 Playwright API에 없는 고급 기능도 구현할 수 있습니다.
다음 코드를 살펴봅시다.
// CDP를 직접 사용하는 예제
import { chromium } from 'playwright';
async function useCDP() {
const browser = await chromium.launch();
const page = await browser.newPage();
// CDP 세션 생성
const client = await page.context().newCDPSession(page);
// CDP 명령 전송: 네트워크 요청 모니터링 활성화
await client.send('Network.enable');
// 네트워크 요청 이벤트 리스닝
client.on('Network.requestWillBeSent', (params) => {
console.log('요청:', params.request.url);
});
// 페이지 방문
await page.goto('https://example.com');
// 고급 스크린샷: 특정 영역만 캡처
const screenshot = await client.send('Page.captureScreenshot', {
clip: { x: 0, y: 0, width: 800, height: 600, scale: 1 }
});
console.log('스크린샷 캡처 완료');
await browser.close();
}
김개발 씨는 Playwright의 고수준 API로 대부분의 작업을 할 수 있었습니다. 그런데 어느 날 특이한 요구사항이 들어왔습니다.
"네트워크 요청의 타이밍 정보를 밀리초 단위로 수집해 주세요." Playwright의 일반 API로는 이런 세밀한 정보를 얻기 어려웠습니다. 그때 박시니어 씨가 CDP를 소개했습니다.
CDP란 무엇일까요? CDP는 마치 브라우저의 리모컨과 같습니다.
여러분이 개발자 도구를 열면 네트워크 탭, 콘솔 탭, 성능 탭 등이 보입니다. 이 모든 기능은 내부적으로 CDP를 통해 구현되어 있습니다.
개발자 도구도 CDP를 사용해서 브라우저 내부 정보를 가져오는 것입니다. Playwright와 Puppeteer도 마찬가지입니다.
내부적으로 CDP를 사용해서 브라우저를 제어합니다. page.goto()를 호출하면, 내부에서는 Page.navigate CDP 명령을 전송합니다.
page.screenshot()을 호출하면, Page.captureScreenshot CDP 명령이 실행됩니다. 그렇다면 왜 CDP를 직접 사용할까요?
Playwright API는 80퍼센트의 사용 케이스를 커버합니다. 일반적인 클릭, 입력, 스크린샷 같은 작업은 Playwright API만으로 충분합니다.
하지만 나머지 20퍼센트의 고급 사용 케이스는 CDP를 직접 사용해야 할 수 있습니다. 예를 들어 네트워크 성능 측정, 메모리 프로파일링, 커스텀 디버깅 도구 같은 작업은 CDP를 직접 사용하는 것이 더 효과적입니다.
위의 코드를 단계별로 살펴보겠습니다. 먼저 page.context().newCDPSession(page)로 CDP 세션을 생성합니다.
이 세션을 통해 CDP 명령을 주고받습니다. 다음으로 client.send('Network.enable')로 네트워크 모니터링을 활성화합니다.
이것은 개발자 도구의 네트워크 탭을 여는 것과 같습니다. 그리고 client.on()으로 이벤트 리스너를 등록합니다.
CDP는 이벤트 기반 프로토콜입니다. 네트워크 요청이 발생하면 Network.requestWillBeSent 이벤트가 발생하고, 우리가 등록한 콜백이 호출됩니다.
마지막으로 client.send('Page.captureScreenshot')로 고급 스크린샷을 찍습니다. clip 옵션으로 특정 영역만 캡처할 수 있습니다.
Playwright의 page.screenshot()보다 더 세밀한 제어가 가능합니다. CDP는 어떤 명령들이 있을까요?
CDP에는 수백 개의 명령과 이벤트가 있습니다. Page 도메인은 페이지 관련 작업을, Network 도메인은 네트워크 작업을, Performance 도메인은 성능 측정을 담당합니다.
각 도메인마다 다양한 명령과 이벤트가 정의되어 있습니다. 공식 CDP 문서를 보면 모든 명령의 목록과 파라미터를 확인할 수 있습니다.
예를 들어 Emulation.setGeolocationOverride로 GPS 위치를 속일 수 있고, Animation.setPlaybackRate로 CSS 애니메이션 속도를 조절할 수 있습니다. 실제 현업에서는 언제 CDP를 사용할까요?
성능 모니터링 도구를 만들 때 CDP가 유용합니다. 페이지 로딩 시간, 리소스 다운로드 시간, JavaScript 실행 시간 등을 밀리초 단위로 측정할 수 있습니다.
E2E 테스트에서도 네트워크 요청을 감시하거나 모킹할 때 CDP를 사용합니다. 모바일 기기 에뮬레이션도 CDP로 더 정교하게 할 수 있습니다.
화면 크기뿐 아니라 터치 이벤트, GPS, 가속도계까지 시뮬레이션할 수 있습니다. 하지만 주의할 점도 있습니다.
CDP는 저수준 API이기 때문에 사용하기 복잡합니다. Playwright API는 브라우저 간 호환성을 보장하지만, CDP는 Chrome 전용입니다.
Firefox나 Safari에서는 작동하지 않습니다. 따라서 먼저 Playwright API로 해결할 수 있는지 확인하고, 정말 필요한 경우에만 CDP를 사용하세요.
대부분의 경우 Playwright API만으로 충분합니다. 또한 CDP는 Chrome 버전에 따라 변경될 수 있습니다.
특정 명령이 더 이상 작동하지 않을 수도 있습니다. 따라서 CDP를 사용할 때는 Chrome 버전 호환성을 체크해야 합니다.
김개발 씨는 CDP로 네트워크 타이밍 정보를 성공적으로 수집했습니다. "와, 이렇게 세밀한 정보까지 얻을 수 있다니!" 박시니어 씨가 웃으며 말했습니다.
"CDP는 강력하지만, 필요할 때만 사용하세요. 일반적인 작업은 Playwright API로 충분합니다."
실전 팁
💡 - 먼저 Playwright API로 해결할 수 있는지 확인하고, 정말 필요할 때만 CDP를 사용하세요
- CDP는 Chrome 전용입니다. Firefox나 Safari에서는 작동하지 않습니다
- 공식 CDP 문서를 참고해서 사용 가능한 명령과 이벤트를 확인하세요
5. 스크린샷과 액션 구현
김개발 씨의 다음 과제는 웹페이지의 특정 영역을 스크린샷으로 캡처하고, 버튼을 클릭하는 자동화 스크립트를 작성하는 것이었습니다. "스크린샷은 간단할 것 같은데, 버튼 클릭은 어떻게 하죠?" 박시니어 씨가 답했습니다.
"Playwright가 알아서 버튼을 찾고 클릭 가능할 때까지 기다려줘요. 아주 편리하죠."
스크린샷 기능은 웹페이지를 이미지로 저장하고, 액션 기능은 클릭, 입력, 스크롤 같은 사용자 동작을 자동화합니다. Playwright는 자동 대기 기능을 제공해서 요소가 준비될 때까지 기다렸다가 액션을 수행합니다.
이를 통해 안정적인 자동화 스크립트를 쉽게 작성할 수 있습니다.
다음 코드를 살펴봅시다.
// 스크린샷과 액션 구현 예제
import { chromium } from 'playwright';
async function screenshotAndAction() {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
// 전체 페이지 스크린샷
await page.screenshot({ path: 'fullpage.png', fullPage: true });
// 특정 요소만 스크린샷
const element = await page.locator('.main-content');
await element.screenshot({ path: 'element.png' });
// 버튼 클릭 (자동으로 요소가 나타날 때까지 대기)
await page.click('button.submit');
// 입력 필드에 텍스트 입력
await page.fill('input[name="email"]', 'test@example.com');
// 드롭다운 선택
await page.selectOption('select#country', 'Korea');
// 키보드 입력
await page.press('input[name="search"]', 'Enter');
console.log('모든 액션 완료');
await browser.close();
}
김개발 씨는 자동화 스크립트를 작성하면서 가장 먼저 부딪힌 문제가 타이밍 이슈였습니다. 버튼을 클릭하려고 하는데, 아직 버튼이 화면에 나타나지 않아서 에러가 발생하는 것이었습니다.
과거에 Selenium을 사용했던 개발자들은 이 문제를 해결하기 위해 sleep(3000) 같은 고정된 대기 시간을 넣었습니다. 하지만 이것은 좋은 방법이 아닙니다.
네트워크가 빠르면 불필요하게 3초를 기다리고, 느리면 3초로도 부족할 수 있습니다. Playwright는 이 문제를 자동 대기로 해결합니다.
page.click('button.submit')을 호출하면, Playwright는 여러 단계를 자동으로 수행합니다. 먼저 버튼이 DOM에 나타날 때까지 기다립니다.
그다음 버튼이 화면에 보일 때까지 기다립니다. 그리고 버튼이 클릭 가능한 상태가 될 때까지 기다립니다.
마치 똑똑한 비서가 상황을 판단하는 것과 같습니다. 버튼이 준비되지 않았다면 기다리고, 준비되면 즉시 클릭합니다.
개발자가 직접 대기 로직을 작성할 필요가 없습니다. 스크린샷 기능은 어떻게 작동할까요?
page.screenshot()은 현재 보이는 화면을 이미지로 저장합니다. fullPage: true 옵션을 사용하면 스크롤해야 보이는 부분까지 모두 캡처합니다.
긴 페이지를 한 장의 이미지로 저장할 수 있습니다. 특정 요소만 캡처하고 싶다면 locator()로 요소를 찾고, 그 요소의 screenshot()을 호출하면 됩니다.
예를 들어 헤더만, 또는 특정 카드만 캡처할 수 있습니다. 스크린샷은 시각적 회귀 테스트에 매우 유용합니다.
새 기능을 배포하기 전에 주요 페이지의 스크린샷을 찍고, 배포 후에 다시 찍어서 비교합니다. 만약 의도하지 않은 변경이 있다면 이미지를 비교해서 바로 발견할 수 있습니다.
액션 기능은 다양한 사용자 동작을 시뮬레이션합니다. page.click()은 마우스 클릭을, page.fill()은 텍스트 입력을, page.selectOption()은 드롭다운 선택을 수행합니다.
page.press()는 키보드 키를 누르는 동작을 합니다. 예를 들어 Enter 키를 눌러서 폼을 제출할 수 있습니다.
각 액션은 실제 사용자의 동작을 정확하게 시뮬레이션합니다. 단순히 값을 변경하는 것이 아니라, 포커스를 주고, 키보드 이벤트를 발생시키고, 블러 이벤트까지 발생시킵니다.
이렇게 하면 JavaScript로 작성된 이벤트 핸들러도 제대로 실행됩니다. 실제 현업에서는 어떻게 활용할까요?
E2E 테스트에서 스크린샷과 액션을 조합해서 사용합니다. 예를 들어 회원가입 플로우를 테스트한다면, 각 단계를 액션으로 수행하고, 각 화면을 스크린샷으로 저장합니다.
나중에 문제가 생기면 스크린샷을 보고 어디서 잘못되었는지 파악할 수 있습니다. 모니터링 도구에서도 유용합니다.
주기적으로 웹사이트를 방문해서 스크린샷을 찍고, 이전 스크린샷과 비교합니다. 만약 차이가 크다면 알림을 보냅니다.
이렇게 하면 예상치 못한 UI 변경을 빠르게 감지할 수 있습니다. 데모 자동 생성에도 활용됩니다.
제품 소개 페이지에 들어갈 스크린샷을 자동으로 생성할 수 있습니다. UI가 변경될 때마다 수동으로 스크린샷을 찍는 대신, 스크립트를 실행해서 최신 스크린샷을 얻습니다.
하지만 주의할 점도 있습니다. CSS 선택자가 변경되면 스크립트가 망가질 수 있습니다.
button.submit 클래스가 button.primary로 바뀌면 클릭이 실패합니다. 따라서 안정적인 선택자를 사용하는 것이 중요합니다.
data-testid 같은 속성을 사용하면 더 안정적입니다. CSS 클래스는 디자인 변경 시 바뀔 수 있지만, data-testid는 테스트 전용이므로 잘 바뀌지 않습니다.
또한 너무 많은 스크린샷을 찍으면 디스크 공간이 부족할 수 있습니다. 테스트에서 스크린샷을 찍을 때는 실패한 경우에만 저장하는 것이 좋습니다.
성공한 테스트의 스크린샷은 보통 필요 없습니다. 김개발 씨는 스크린샷과 액션을 조합해서 완전한 자동화 스크립트를 작성했습니다.
"이제 손으로 클릭할 일이 없네요!" 박시니어 씨가 웃으며 말했습니다. "자동 대기 덕분에 안정적이죠.
예전에는 타이밍 이슈 때문에 골치 아팠는데, 이제는 Playwright가 알아서 처리해 줍니다."
실전 팁
💡 - data-testid 같은 안정적인 선택자를 사용하세요. CSS 클래스는 자주 변경됩니다
- 스크린샷은 실패한 경우에만 저장해서 디스크 공간을 절약하세요
- fullPage 옵션으로 긴 페이지 전체를 한 장으로 캡처할 수 있습니다
6. 실전 웹 스크래핑 자동화
김개발 씨는 이제 배운 내용을 실전에 적용할 준비가 되었습니다. 회사의 실제 프로젝트는 경쟁사 10곳의 상품 정보를 매일 수집하는 것이었습니다.
"이제 직접 만들어볼 차례네요." 박시니어 씨가 격려했습니다. "지금까지 배운 것을 모두 활용하면 충분히 할 수 있어요."
웹 스크래핑 자동화는 여러 웹사이트에서 데이터를 자동으로 수집하는 시스템입니다. BrowserManager로 브라우저를 관리하고, ActionService로 로그인하고, 데이터를 추출해서 데이터베이스에 저장합니다.
에러 처리, 재시도 로직, 로깅을 추가해서 안정적으로 운영할 수 있습니다.
다음 코드를 살펴봅시다.
// 실전 웹 스크래핑 자동화 시스템
import { chromium } from 'playwright';
async function scrapeCompetitors() {
const browser = await chromium.launch({ headless: true });
const results = [];
const competitors = [
'https://competitor1.com/products',
'https://competitor2.com/products',
'https://competitor3.com/products',
];
for (const url of competitors) {
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
// 상품 정보 추출
const products = await page.$$eval('.product-item', items => {
return items.map(item => ({
name: item.querySelector('.product-name')?.textContent,
price: item.querySelector('.product-price')?.textContent,
image: item.querySelector('img')?.src,
}));
});
results.push({ url, products, timestamp: new Date() });
console.log(`${url}에서 ${products.length}개 수집 완료`);
await page.close();
// 서버 부하 방지를 위한 딜레이
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
console.error(`${url} 수집 실패:`, error.message);
}
}
await browser.close();
return results;
}
김개발 씨는 드디어 실전 프로젝트를 시작했습니다. 지금까지 배운 BrowserManager, 스크린샷, 액션 기능을 모두 조합해서 실제로 작동하는 스크래핑 시스템을 만들 차례였습니다.
첫 번째 단계는 요구사항 정리였습니다. 매일 아침 9시에 경쟁사 10곳의 웹사이트를 방문해서 상품 이름, 가격, 이미지 URL을 수집합니다.
수집한 데이터는 데이터베이스에 저장하고, 가격 변동이 있으면 슬랙으로 알림을 보냅니다. 만약 수집이 실패하면 3번까지 재시도합니다.
이런 요구사항을 코드로 구현하려면 여러 부분을 고려해야 합니다. 에러 처리는 필수입니다.
웹사이트가 다운되어 있을 수도 있고, HTML 구조가 변경되었을 수도 있습니다. 한 사이트에서 에러가 나도 나머지 사이트는 계속 수집해야 합니다.
그래서 try-catch로 각 사이트를 감쌉니다. 딜레이 추가도 중요합니다.
너무 빠르게 요청하면 서버가 봇으로 인식해서 차단할 수 있습니다. 또한 상대 서버에 부담을 주는 것도 예의가 아닙니다.
그래서 각 요청 사이에 2초 정도의 딜레이를 넣습니다. waitUntil: 'networkidle' 옵션은 네트워크 요청이 완료될 때까지 기다립니다.
단순히 HTML이 로드되는 것이 아니라, JavaScript로 동적으로 로드되는 데이터까지 기다립니다. 최근 웹사이트는 대부분 JavaScript를 사용하므로 이 옵션이 중요합니다.
데이터 추출은 $$eval()을 사용합니다. $$eval()은 모든 매칭되는 요소를 찾아서 콜백 함수를 실행합니다.
브라우저 컨텍스트에서 실행되므로 querySelector() 같은 DOM API를 사용할 수 있습니다. 각 상품 아이템에서 이름, 가격, 이미지를 추출해서 배열로 반환합니다.
?. 연산자는 옵셔널 체이닝입니다. 요소가 없으면 에러 대신 undefined를 반환합니다.
일부 상품에 이미지가 없어도 스크립트가 중단되지 않습니다. 실제 프로젝트에서는 더 많은 기능이 필요합니다.
재시도 로직을 추가하면 일시적인 네트워크 오류를 극복할 수 있습니다. 실패하면 3초 후에 다시 시도하고, 3번까지 시도합니다.
그래도 실패하면 알림을 보내고 다음 사이트로 넘어갑니다. 로그인이 필요한 사이트도 있습니다.
이 경우 먼저 로그인 페이지로 이동해서 아이디와 비밀번호를 입력하고 로그인합니다. 쿠키가 저장되므로 이후 페이지 방문에서는 로그인 상태가 유지됩니다.
스케줄링은 cron이나 GitHub Actions를 사용합니다. 매일 아침 9시에 자동으로 스크립트를 실행하도록 설정합니다.
실행 결과는 슬랙이나 이메일로 받을 수 있습니다. 데이터 저장은 데이터베이스에 합니다.
PostgreSQL이나 MongoDB 같은 데이터베이스에 수집한 데이터를 저장합니다. 타임스탬프를 함께 저장해서 가격 변동 이력을 추적할 수 있습니다.
실제 현업에서는 어떤 문제가 생길까요? HTML 구조 변경이 가장 흔한 문제입니다.
웹사이트가 리뉴얼되면 CSS 선택자가 바뀝니다. .product-item이 .product-card로 바뀌면 스크립트가 작동하지 않습니다.
정기적으로 확인하고, 변경사항을 반영해야 합니다. 봇 감지도 문제입니다.
일부 웹사이트는 headless 브라우저를 감지하고 차단합니다. 이런 경우 headless: false로 설정하거나, User-Agent를 실제 브라우저처럼 변경하거나, 프록시를 사용해야 할 수 있습니다.
법적 문제도 고려해야 합니다. 웹사이트의 이용약관을 확인하고, 스크래핑이 허용되는지 확인하세요.
공개된 데이터라도 무단으로 수집하면 문제가 될 수 있습니다. 가능하면 공식 API를 사용하는 것이 가장 좋습니다.
김개발 씨는 2주 동안 시스템을 완성했습니다. 처음에는 HTML 구조를 잘못 파악해서 데이터가 제대로 추출되지 않았지만, 개발자 도구로 확인하며 하나씩 수정했습니다.
이제 매일 아침 자동으로 데이터가 수집되고, 가격 변동이 있으면 슬랙 알림이 옵니다. 김개발 씨는 더 이상 2시간씩 수동으로 데이터를 입력할 필요가 없어졌습니다.
박시니어 씨가 칭찬했습니다. "잘했어요.
이제 진짜 개발자가 된 것 같네요. 반복 작업을 자동화하는 것, 그게 프로그래밍의 본질이니까요." 김개발 씨는 뿌듯했습니다.
브라우저 자동화를 배우면서 단순한 도구 사용법을 넘어, 문제를 발견하고 해결하는 방법을 배웠습니다. CDP 같은 저수준 프로토콜부터 실전 스크래핑까지, 전체 그림을 이해하게 되었습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요. 작은 것부터 시작하세요.
매일 하는 반복 작업을 하나씩 자동화해 보세요. 그러면 어느새 자동화의 힘을 실감하게 될 것입니다.
실전 팁
💡 - HTML 구조 변경에 대비해서 정기적으로 스크립트를 점검하세요
- 웹사이트의 이용약관을 확인하고, 합법적인 범위 내에서만 스크래핑하세요
- 재시도 로직과 에러 알림을 추가해서 안정적으로 운영하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
UX와 협업 패턴 완벽 가이드
AI 에이전트와 사용자 간의 효과적인 협업을 위한 UX 패턴을 다룹니다. 프롬프트 핸드오프부터 인터럽트 처리까지, 현대적인 에이전트 시스템 설계의 핵심을 배웁니다.
자가 치유 및 재시도 패턴 완벽 가이드
AI 에이전트와 분산 시스템에서 필수적인 자가 치유 패턴을 다룹니다. 에러 감지부터 서킷 브레이커까지, 시스템을 스스로 복구하는 탄력적인 코드 작성법을 배워봅니다.
Feedback Loops 컴파일러와 CI/CD 완벽 가이드
컴파일러 피드백 루프부터 CI/CD 파이프라인, 테스트 자동화, 자가 치유 빌드까지 현대 개발 워크플로우의 핵심을 다룹니다. 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다.
실전 MCP 통합 프로젝트 완벽 가이드
Model Context Protocol을 활용한 실전 통합 프로젝트를 처음부터 끝까지 구축하는 방법을 다룹니다. 아키텍처 설계부터 멀티 서버 통합, 모니터링, 배포까지 운영 레벨의 MCP 시스템을 구축하는 노하우를 담았습니다.
MCP 동적 도구 업데이트 완벽 가이드
AI 에이전트의 도구를 런타임에 동적으로 로딩하고 관리하는 방법을 알아봅니다. 플러그인 시스템 설계부터 핫 리로딩, 보안까지 실무에서 바로 적용할 수 있는 내용을 다룹니다.