[학습 목표]
여러분, 2026년 현재 웹 환경은 과거와 다르게 동적인 JavaScript 기반 웹사이트가 대세입니다. 정적인 HTML만으로는 원하는 데이터를 추출하기 어려운 경우가 많죠. 이번 강의에서는 이러한 현대 웹사이트의 데이터를 효과적으로 수집할 수 있는 강력한 도구인 Playwright를 사용하여 JavaScript 웹사이트에서 데이터를 즉시 추출하는 실전 크롤링 기법을 완벽하게 마스터하는 것을 목표로 합니다. 단순히 이론을 넘어서, 직접 코드를 작성하고 실행하며 실질적인 웹 데이터 추출 능력을 키울 것입니다. 웹 크롤링의 기초부터 Playwright의 핵심 기능 활용까지, 초보자도 쉽게 따라 할 수 있도록 단계별로 안내해 드리겠습니다.
[사전 준비 사항]
본 실습을 성공적으로 수행하기 위해 다음 환경을 미리 설정해 주시기 바랍니다. 효율적인 학습과 원활한 진행을 위해 필수적인 과정입니다.
개발 환경
- 운영체제(OS): Windows 10/11, macOS (최신 버전 권장), 또는 Ubuntu 20.04+
- 통합 개발 환경(IDE): Visual Studio Code (VS Code) 최신 버전. 확장 프로그램은 필요에 따라 설치할 수 있으나, 기본 설치만으로 충분합니다.
필수 소프트웨어 설치
- Node.js: LTS (Long Term Support) 버전 18.x 이상 설치를 강력히 권장합니다. Node.js는 Playwright를 JavaScript 환경에서 실행하기 위한 런타임입니다.
- npm (Node Package Manager): Node.js 설치 시 자동으로 포함됩니다. 패키지 관리에 사용됩니다.
설치 명령어
터미널(Windows의 경우 PowerShell 또는 Git Bash, macOS/Linux의 경우 Terminal)을 열고 다음 명령어를 순서대로 실행하여 Playwright 프로젝트를 초기화하고 필요한 라이브러리를 설치합니다.
# 1. 새 프로젝트 폴더 생성 및 이동
mkdir playwright-data-extractor
cd playwright-data-extractor
# 2. Node.js 프로젝트 초기화
npm init -y
# 3. Playwright 라이브러리 설치 (브라우저 바이너리 포함)
npm install playwright
위 명령어를 실행하면 Playwright가 자동으로 Chromium, Firefox, WebKit 브라우저 바이너리를 다운로드하여 설치합니다. 이 과정은 네트워크 환경에 따라 다소 시간이 소요될 수 있습니다. 설치가 완료되면, 이제 Playwright를 사용하여 웹 크롤링을 시작할 준비가 된 것입니다.
[단계별 실습 과정]
이제 본격적으로 Playwright를 이용한 웹 데이터 추출 실습을 시작해 보겠습니다. 우리는 특정 웹사이트에서 제품 정보를 추출하는 시나리오를 가정하고 진행할 것입니다. 예시 웹사이트는 실제 웹사이트가 아닌, 크롤링 연습을 위해 간단히 구성된 가상의 페이지를 사용하는 것이 좋습니다. 하지만 이 튜토리얼에서는 일반적인 JavaScript 기반 웹사이트의 데이터 추출 원리를 설명하기 위해 실제와 유사한 접근 방식을 사용합니다.
1단계: Playwright 모듈 불러오기 및 브라우저 실행
먼저, Playwright를 사용하여 브라우저를 실행하고 새로운 페이지를 여는 기본적인 스크립트를 작성합니다. 프로젝트 폴더 내에 extractData.js 파일을 생성하고 다음 코드를 입력하세요.
// extractData.js
const { chromium } = require('playwright'); // 또는 firefox, webkit
async function main() {
const browser = await chromium.launch({ headless: true }); // headless: true는 UI 없이 백그라운드에서 실행
const page = await browser.newPage();
console.log('브라우저가 성공적으로 실행되었습니다.');
// 브라우저 닫기
await browser.close();
}
main();
이 코드는 Chromium 브라우저를 백그라운드(headless: true)에서 실행하고, 새 페이지를 연 다음, 간단한 메시지를 출력하고 브라우저를 닫습니다. headless: false로 설정하면 실제 브라우저 UI가 뜨는 것을 볼 수 있습니다. 개발 단계에서는 false로 설정하여 동작을 시각적으로 확인하는 것이 유용합니다.
2단계: 특정 웹페이지로 이동 및 대기
이제 우리가 데이터를 추출할 웹페이지로 이동하고, 페이지의 모든 내용이 로드될 때까지 기다리는 로직을 추가합니다. JavaScript 기반 웹사이트는 동적으로 콘텐츠를 로드하므로, 단순히 페이지 이동 명령만으로는 충분하지 않을 수 있습니다. Playwright의 page.waitForLoadState() 함수는 페이지 로드 상태를 기다리는 데 매우 유용합니다.
// extractData.js (업데이트)
const { chromium } = require('playwright');
async function main() {
const browser = await chromium.launch({ headless: false }); // 개발 편의를 위해 headless: false
const page = await browser.newPage();
const targetUrl = 'https://www.example.com'; // 실제 크롤링할 대상 URL로 변경하세요.
console.log(`대상 URL로 이동 중: ${targetUrl}`);
try {
await page.goto(targetUrl, { waitUntil: 'networkidle' }); // 네트워크 활동이 없을 때까지 대기
console.log('페이지 로드 완료.');
// 추가적인 대기 (필요시)
// await page.waitForTimeout(3000); // 3초 대기 (특정 요소가 로드될 때까지 기다리는 것이 더 좋음)
// 여기에서 데이터 추출 로직을 구현합니다.
} catch (error) {
console.error(`페이지 이동 중 오류 발생: ${error.message}`);
} finally {
await browser.close();
}
}
main();
waitUntil: 'networkidle' 옵션은 페이지의 네트워크 요청이 500ms 동안 2개 이하로 떨어질 때까지 기다립니다. 이는 대부분의 동적 콘텐츠가 로드될 때까지 기다리는 효과적인 방법입니다.
3단계: 웹페이지 요소 선택 및 데이터 추출
가장 핵심적인 단계입니다. Playwright는 CSS 선택자(Selector)를 사용하여 웹페이지의 특정 요소를 선택하고, 해당 요소에서 텍스트, 속성 값 등을 추출할 수 있습니다. 예를 들어, 웹페이지에 있는 모든 제목(h2 태그)과 특정 클래스를 가진 단락(p.description)의 텍스트를 추출해 보겠습니다.
// extractData.js (업데이트)
const { chromium } = require('playwright');
async function main() {
const browser = await chromium.launch({ headless: true }); // headless: true로 다시 변경
const page = await browser.newPage();
const targetUrl = 'https://quotes.toscrape.com/'; // 예시 웹사이트로 변경
console.log(`대상 URL로 이동 중: ${targetUrl}`);
try {
await page.goto(targetUrl, { waitUntil: 'networkidle' });
console.log('페이지 로드 완료. 데이터 추출 시작...');
// 모든 인용구(quote) 추출
const quotes = await page.$$('.quote'); // '.quote' 클래스를 가진 모든 요소 선택
const extractedData = [];
for (const quote of quotes) {
const text = await quote.$eval('.text', el => el.textContent.trim());
const author = await quote.$eval('.author', el => el.textContent.trim());
const tags = await quote.$$eval('.tag', tags => tags.map(tag => tag.textContent.trim()));
extractedData.push({
text,
author,
tags
});
}
console.log('--- 추출된 데이터 ---');
console.log(JSON.stringify(extractedData, null, 2));
console.log('---------------------');
} catch (error) {
console.error(`데이터 추출 중 오류 발생: ${error.message}`);
} finally {
await browser.close();
}
}
main();
위 코드에서는 quotes.toscrape.com이라는 예시 웹사이트를 사용하여 인용구, 작성자, 태그를 추출합니다.
page.$$('.quote'): 페이지에서.quote클래스를 가진 모든 요소를 선택하여 배열로 반환합니다.quote.$eval('.text', el => el.textContent.trim()): 각.quote요소 내부에서.text클래스를 가진 요소를 찾아 텍스트 콘텐츠를 추출합니다.$eval은 단일 요소에 대해 콜백 함수를 실행합니다.quote.$$eval('.tag', tags => tags.map(tag => tag.textContent.trim())): 각.quote요소 내부에서.tag클래스를 가진 모든 요소를 찾아 배열로 반환하고, 각 태그의 텍스트 콘텐츠를 추출하여 새로운 배열을 만듭니다.$$eval은 여러 요소에 대해 콜백 함수를 실행합니다.
이러한 방식으로 원하는 모든 데이터를 유연하게 추출할 수 있습니다. CSS 선택자를 정확히 사용하는 것이 중요하며, 브라우저의 개발자 도구(F12)를 활용하여 원하는 요소의 선택자를 찾는 연습을 많이 하셔야 합니다.
4단계: 페이지네이션(Pagination) 처리 (선택 사항)
대부분의 웹사이트는 한 페이지에 모든 데이터를 보여주지 않고, 여러 페이지에 걸쳐 데이터를 분할합니다(페이지네이션). Playwright를 사용하면 다음 페이지로 이동하는 버튼을 클릭하거나, URL 패턴을 변경하여 여러 페이지의 데이터를 순차적으로 추출할 수 있습니다.
// extractData.js (업데이트 - 페이지네이션 추가)
const { chromium } = require('playwright');
async function main() {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
const baseUrl = 'https://quotes.toscrape.com';
let currentPageUrl = baseUrl; // 시작 URL
const allExtractedData = [];
while (currentPageUrl) {
console.log(`페이지 이동 중: ${currentPageUrl}`);
await page.goto(currentPageUrl, { waitUntil: 'networkidle' });
console.log('페이지 로드 완료. 데이터 추출 시작...');
const quotes = await page.$$('.quote');
for (const quote of quotes) {
const text = await quote.$eval('.text', el => el.textContent.trim());
const author = await quote.$eval('.author', el => el.textContent.trim());
const tags = await quote.$$eval('.tag', tags => tags.map(tag => tag.textContent.trim()));
allExtractedData.push({
text,
author,
tags
});
}
// 다음 페이지 링크 찾기
const nextButton = await page.$('li.next a'); // 'Next' 버튼의 CSS 선택자
if (nextButton) {
currentPageUrl = await nextButton.getAttribute('href');
if (currentPageUrl) {
// 상대 경로인 경우 절대 경로로 변환
currentPageUrl = new URL(currentPageUrl, baseUrl).href;
}
} else {
currentPageUrl = null; // 다음 페이지가 없으면 루프 종료
}
await page.waitForTimeout(1000); // 다음 페이지로 이동 전 짧은 대기 (봇 감지 방지)
}
console.log('--- 최종 추출된 모든 데이터 ---');
console.log(JSON.stringify(allExtractedData, null, 2));
console.log('---------------------------------');
await browser.close();
}
main();
이 코드는 quotes.toscrape.com 웹사이트의 ‘Next’ 버튼을 찾아 클릭(또는 href 속성으로 이동)하여 모든 페이지를 순회하며 데이터를 추출합니다. while (currentPageUrl) 루프를 사용하여 다음 페이지 링크가 없을 때까지 반복합니다. new URL(currentPageUrl, baseUrl).href는 상대 경로 링크를 절대 경로로 변환하는 데 유용합니다.
[결과 확인]
위에서 작성한 extractData.js 파일을 실행하여 결과를 확인합니다. 터미널에서 다음 명령어를 입력하세요.
node extractData.js
명령어를 실행하면 Playwright가 브라우저를 백그라운드에서 실행하고, 지정된 웹사이트로 이동하며, 설정된 로직에 따라 데이터를 추출합니다. 추출된 데이터는 터미널에 JSON 형식으로 출력될 것입니다. 페이지네이션을 구현했다면, 여러 페이지에서 수집된 모든 데이터가 하나의 배열 형태로 나타나는 것을 확인할 수 있습니다.
출력되는 JSON 데이터를 통해 우리가 목표했던 웹사이트의 정보(예: 인용구, 작성자, 태그)가 성공적으로 수집되었음을 검증할 수 있습니다. 만약 데이터가 예상과 다르게 추출되거나 오류가 발생한다면, 다음 사항들을 점검해 보세요.
- CSS 선택자 확인: 브라우저 개발자 도구(F12)를 사용하여 요소의 CSS 선택자가 정확한지 다시 확인합니다. 동적인 웹사이트의 경우 선택자가 변경될 수 있습니다.
- 페이지 로드 대기:
page.goto()의waitUntil옵션이나page.waitForSelector(),page.waitForTimeout()등을 사용하여 페이지의 모든 요소가 로드될 때까지 충분히 기다리고 있는지 확인합니다. - 오류 메시지 분석: 터미널에 출력되는 오류 메시지를 주의 깊게 읽고 문제의 원인을 파악합니다.
- 헤드리스 모드:
headless: false로 설정하여 브라우저의 실제 동작을 눈으로 확인하며 디버깅하는 것이 매우 효과적입니다.
이 실습을 통해 여러분은 Playwright를 사용하여 JavaScript 기반의 동적 웹사이트에서 데이터를 추출하는 기본적인 원리와 실전 기술을 익히셨습니다. 이제 이 지식을 바탕으로 여러분이 필요한 다양한 웹사이트에서 데이터를 수집하는 강력한 크롤러를 개발할 수 있을 것입니다. 다음 단계로는 추출된 데이터를 파일(CSV, JSON)로 저장하거나, 데이터베이스에 연동하는 방법을 학습해 보는 것을 권장합니다. Playwright는 단순히 데이터를 추출하는 것을 넘어, 웹사이트 자동화, 테스트 등 다양한 분야에서 활용될 수 있는 강력한 도구임을 기억하십시오. 꾸준한 연습과 탐구를 통해 여러분의 IT 실무 역량을 더욱 강화하시길 바랍니다.
