🚀 CDP & Playwright
조대협 (http://bcho.tistory.com)
아래 코드는 프롬프트를 이용해서, Sonnet-4를 사용해 생성한 컨텐츠 입니다.
브라우저 자동화 학습 교본
Chrome DevTools Protocol과 Playwright를 활용한 웹 자동화 완벽 가이드
📑 목차
1. 소개
🎯 학습 목표
이 교본에서는 다음 내용을 학습합니다:
- Chrome DevTools Protocol (CDP)의 기본 개념과 사용법
- Playwright를 활용한 브라우저 자동화
- 봇 감지 우회 기법 (CAPTCHA 처리, 사람처럼 행동하기)
- 실무에서 활용 가능한 웹 스크래핑 및 자동화 기술
📚 브라우저 자동화란?
브라우저 자동화는 웹 브라우저를 프로그래밍 방식으로 제어하는 기술입니다. 이를 통해 반복적인 웹 작업을 자동화하거나, 웹 애플리케이션 테스트, 웹 스크래핑 등을 수행할 수 있습니다.
🛠️ 주요 기술
- CDP (Chrome DevTools Protocol): Chrome의 저수준 디버깅 프로토콜
- Playwright: Microsoft에서 개발한 고수준 브라우저 자동화 라이브러리
- 웹 스크래핑 및 데이터 수집
- E2E (End-to-End) 테스트 자동화
- 웹 애플리케이션 모니터링
- 반복 작업 자동화 (폼 작성, 스크린샷 캡처 등)
2. 아키텍처 이해
🏗️ CDP 아키텍처
↓
WebSocket 연결
↓
Chrome DevTools Protocol
↓
Chrome 브라우저 (--remote-debugging-port)
CDP는 WebSocket을 통해 Chrome 브라우저와 직접 통신합니다. 브라우저를 --remote-debugging-port 옵션으로 실행하면 디버깅 포트가 열리고, 이를 통해 브라우저를 원격으로 제어할 수 있습니다.
🎭 Playwright 아키텍처
↓
Playwright API
↓
CDP (내부적으로 사용)
↓
Chrome/Firefox/Safari 브라우저
Playwright는 내부적으로 CDP를 사용하지만, 고수준 API를 제공하여 더 쉽고 안정적인 브라우저 제어를 가능하게 합니다. 또한 여러 브라우저를 지원합니다.
- CDP: 저수준 제어, 높은 유연성, 복잡한 코드
- Playwright: 고수준 API, 사용 편의성, 안정성
3. 프로젝트 설정
📦 1. 프로젝트 초기화
mkdir browser-automation-demo
cd browser-automation-demo
npm init -y
📦 2. package.json 설정
{
"name": "browser-automation-demo",
"version": "1.0.0",
"type": "module",
"description": "CDP and Playwright browser automation examples",
"scripts": {
"cdp": "node cdp-example.js",
"playwright": "node playwright-example.js"
},
"dependencies": {
"playwright-core": "^1.58.2",
"ws": "^8.19.0"
}
}
📦 3. 의존성 설치
npm install
- playwright-core: Playwright 코어 라이브러리
- ws: WebSocket 클라이언트 (CDP용)
📁 프로젝트 구조
browser-automation-demo/
├── package.json
├── cdp-example.js # Pure CDP 예제
├── playwright-example.js # Playwright 예제
└── README.md
CDP 4. CDP 예제
🎯 목표
순수 CDP를 사용하여 Chrome을 실행하고, Google에서 "AI"를 검색한 후 스크린샷을 캡처합니다.
📝 전체 코드
import { spawn } from 'child_process';
import WebSocket from 'ws';
import http from 'http';
import fs from 'fs';
const CDP_PORT = 9222;
function launchChrome() {
const chromeArgs = [
`--remote-debugging-port=${CDP_PORT}`,
'--no-first-run',
'--no-default-browser-check',
'--disable-background-networking',
'--disable-default-apps',
'--disable-extensions',
'--disable-sync',
'--disable-translate',
'about:blank'
];
const chromePaths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
'/usr/bin/google-chrome',
'/usr/bin/chromium-browser'
];
let chromePath = chromePaths.find(path => {
try {
return fs.existsSync(path);
} catch {
return false;
}
});
if (!chromePath) {
chromePath = 'google-chrome';
}
console.log(`🚀 Launching Chrome from: ${chromePath}`);
const chrome = spawn(chromePath, chromeArgs);
chrome.on('error', (err) => {
console.error('Failed to start Chrome:', err);
process.exit(1);
});
return chrome;
}
async function getCdpWebSocketUrl() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for CDP endpoint'));
}, 10000);
const tryConnect = () => {
http.get(`http://127.0.0.1:${CDP_PORT}/json/version`, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
clearTimeout(timeout);
const info = JSON.parse(data);
resolve(info.webSocketDebuggerUrl);
});
}).on('error', () => {
setTimeout(tryConnect, 100);
});
};
tryConnect();
});
}
async function sendCdpCommand(ws, method, params = {}) {
return new Promise((resolve, reject) => {
const id = Date.now();
const handler = (data) => {
const response = JSON.parse(data);
if (response.id === id) {
ws.off('message', handler);
if (response.error) {
reject(new Error(response.error.message));
} else {
resolve(response.result);
}
}
};
ws.on('message', handler);
ws.send(JSON.stringify({ id, method, params }));
setTimeout(() => {
ws.off('message', handler);
reject(new Error(`Timeout for ${method}`));
}, 30000);
});
}
async function main() {
console.log('=== CDP Example: Google Search ===\\n');
const chrome = launchChrome();
await new Promise(resolve => setTimeout(resolve, 2000));
const wsUrl = await getCdpWebSocketUrl();
console.log(`✅ CDP WebSocket URL: ${wsUrl}\\n`);
const ws = new WebSocket(wsUrl);
await new Promise((resolve) => {
ws.on('open', resolve);
});
console.log('✅ WebSocket connected\\n');
await sendCdpCommand(ws, 'Page.enable');
await sendCdpCommand(ws, 'DOM.enable');
await sendCdpCommand(ws, 'Runtime.enable');
console.log('📂 Navigating to Google...');
await sendCdpCommand(ws, 'Page.navigate', { url: 'https://www.google.com' });
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('🔍 Searching for "AI"...');
const searchScript = `
(function() {
const input = document.querySelector('textarea[name="q"]') ||
document.querySelector('input[name="q"]');
if (input) {
input.value = 'AI';
input.form.submit();
return 'Search submitted';
}
return 'Search box not found';
})();
`;
const result = await sendCdpCommand(ws, 'Runtime.evaluate', {
expression: searchScript,
returnByValue: true,
awaitPromise: false
});
console.log(`📝 Script result: ${result.value}`);
await new Promise(resolve => setTimeout(resolve, 3000));
const screenshot = await sendCdpCommand(ws, 'Page.captureScreenshot', {
format: 'png',
fromSurface: true
});
fs.writeFileSync('cdp-screenshot.png', Buffer.from(screenshot.data, 'base64'));
console.log('\\n📸 Screenshot saved: cdp-screenshot.png');
ws.close();
chrome.kill();
console.log('\\n✅ Done!');
}
main().catch(console.error);
🔍 코드 분석
1. Chrome 실행
const chrome = spawn(chromePath, [
'--remote-debugging-port=9222',
// ... 기타 옵션
]);
--remote-debugging-port 옵션으로 Chrome을 실행하면 CDP 프로토콜을 사용할 수 있는 포트가 열립니다.
2. WebSocket 연결
const wsUrl = await getCdpWebSocketUrl();
const ws = new WebSocket(wsUrl);
HTTP GET 요청으로 /json/version 엔드포인트에서 WebSocket URL을 가져온 후 연결합니다.
3. CDP 명령 전송
await sendCdpCommand(ws, 'Page.navigate', {
url: 'https://www.google.com'
});
CDP 명령은 JSON 형식으로 전송되며, 각 명령에는 고유한 ID가 부여됩니다.
4. JavaScript 실행
await sendCdpCommand(ws, 'Runtime.evaluate', {
expression: searchScript,
returnByValue: true
});
페이지 컨텍스트에서 JavaScript를 실행하여 검색창을 찾고 검색을 수행합니다.
5. 스크린샷 캡처
const screenshot = await sendCdpCommand(ws, 'Page.captureScreenshot', {
format: 'png',
fromSurface: true
});
fs.writeFileSync('cdp-screenshot.png',
Buffer.from(screenshot.data, 'base64'));
▶️ 실행
npm run cdp
- 모든 작업을 수동으로 관리해야 함 (대기, 타이밍 등)
- 요소 선택이 불안정할 수 있음
- 코드가 복잡하고 길어짐
Playwright 5. Playwright 예제 (기본)
🎯 목표
Playwright의 고수준 API를 사용하여 동일한 작업을 더 간단하게 수행합니다.
📝 기본 코드
import { chromium } from 'playwright-core';
import fs from 'fs';
async function main() {
console.log('=== Playwright Example: Google Search ===\\n');
const chromePaths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
'/usr/bin/google-chrome',
'/usr/bin/chromium-browser'
];
let executablePath = chromePaths.find(path => {
try {
return fs.existsSync(path);
} catch {
return false;
}
});
console.log(`🚀 Launching Chrome from: ${executablePath || 'system default'}\\n`);
const browser = await chromium.launch({
headless: false,
executablePath: executablePath || undefined,
args: [
'--no-first-run',
'--no-default-browser-check',
'--disable-background-networking'
]
});
console.log('✅ Browser launched\\n');
const context = await browser.newContext({
viewport: { width: 1280, height: 720 }
});
const page = await context.newPage();
console.log('📂 Navigating to Google...');
await page.goto('https://www.google.com', {
waitUntil: 'networkidle',
timeout: 30000
});
console.log('✅ Page loaded\\n');
console.log('🔍 Typing "AI" in search box...');
const searchInput = await page.locator('textarea[name="q"], input[name="q"]').first();
await searchInput.waitFor({ state: 'visible', timeout: 5000 });
await searchInput.fill('AI');
console.log('⏎ Pressing Enter...');
await searchInput.press('Enter');
console.log('⏳ Waiting for search results...');
await page.waitForLoadState('networkidle', { timeout: 10000 });
await page.waitForTimeout(2000);
const title = await page.title();
console.log(`📄 Page title: ${title}\\n`);
console.log('📸 Taking screenshot...');
await page.screenshot({
path: 'playwright-screenshot.png',
fullPage: false
});
console.log('✅ Screenshot saved: playwright-screenshot.png\\n');
const results = await page.locator('h3').count();
console.log(`🔢 Found ${results} result headers\\n`);
await browser.close();
console.log('✅ Browser closed\\n');
console.log('✅ Done!');
}
main().catch(console.error);
🔍 코드 분석
1. 브라우저 실행
const browser = await chromium.launch({
headless: false,
executablePath: executablePath
});
Playwright가 Chrome 실행 및 CDP 연결을 자동으로 처리합니다.
2. 페이지 열기
const page = await context.newPage();
await page.goto('https://www.google.com', {
waitUntil: 'networkidle'
});
waitUntil 옵션으로 페이지 로드 완료를 자동으로 대기합니다.
3. 요소 찾기 및 입력
const searchInput = await page.locator('textarea[name="q"]').first();
await searchInput.waitFor({ state: 'visible' });
await searchInput.fill('AI');
Playwright는 요소가 준비될 때까지 자동으로 대기하며, 안정적인 요소 선택을 제공합니다.
4. 스크린샷
await page.screenshot({
path: 'playwright-screenshot.png',
fullPage: false
});
한 줄로 스크린샷 캡처가 가능합니다.
▶️ 실행
npm run playwright
- 코드가 간결하고 읽기 쉬움
- 자동 대기 기능으로 안정성 향상
- 직관적인 API
- 여러 브라우저 지원 (Chrome, Firefox, Safari)
Advanced 6. 사람처럼 행동하기
🎯 목표
봇 감지 시스템을 우회하기 위해 사람처럼 행동하는 기능을 구현합니다. CAPTCHA 자동 처리, 랜덤 마우스 움직임, 사람같은 타이핑 등을 추가합니다.
이 기술은 교육 목적으로만 사용해야 합니다. 웹사이트의 서비스 약관을 위반하거나 불법적인 목적으로 사용하지 마세요.
📝 고급 기능 코드
1. 랜덤 딜레이 함수
function randomDelay(min = 0, max = 1000) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function humanLikeDelay(min = 300, max = 1000) {
const delay = randomDelay(min, max);
await new Promise(resolve => setTimeout(resolve, delay));
}
0-1000ms 사이의 랜덤 딜레이를 생성하여 사람처럼 불규칙한 타이밍을 만듭니다.
2. 랜덤 마우스 움직임
async function moveMouseRandomly(page, count = 5) {
const viewport = page.viewportSize();
for (let i = 0; i < count; i++) {
const x = Math.floor(Math.random() * viewport.width);
const y = Math.floor(Math.random() * viewport.height);
await page.mouse.move(x, y, { steps: randomDelay(5, 15) });
await humanLikeDelay(100, 500);
}
}
화면 내 랜덤 위치로 마우스를 이동시킵니다. steps 옵션으로 부드러운 이동을 구현합니다.
3. 사람처럼 클릭하기
async function humanLikeClick(page, locator) {
const box = await locator.boundingBox();
if (box) {
const x = box.x + box.width / 2 + randomDelay(-5, 5);
const y = box.y + box.height / 2 + randomDelay(-5, 5);
await page.mouse.move(x, y, { steps: randomDelay(10, 30) });
await humanLikeDelay(100, 300);
await page.mouse.down();
await new Promise(resolve => setTimeout(resolve, randomDelay(50, 150)));
await page.mouse.up();
}
}
요소의 정확한 중앙이 아닌 약간 랜덤한 위치를 클릭하고, 마우스 다운과 업 사이에 자연스러운 딜레이를 추가합니다.
4. 사람처럼 타이핑하기
async function humanLikeType(page, locator, text) {
await locator.click();
await humanLikeDelay(200, 500);
for (const char of text) {
await page.keyboard.type(char);
await new Promise(resolve => setTimeout(resolve, randomDelay(50, 200)));
}
}
각 문자마다 50-200ms의 랜덤 딜레이를 주어 자연스러운 타이핑을 시뮬레이션합니다.
5. CAPTCHA 자동 처리
async function checkAndHandleCaptcha(page) {
console.log('🤖 Checking for "I\\'m not a robot" checkbox...');
try {
const captchaFrame = page.frameLocator('iframe[src*="recaptcha"]').first();
const checkbox = captchaFrame.locator('.recaptcha-checkbox-border, #recaptcha-anchor');
const isVisible = await checkbox.isVisible({ timeout: 3000 }).catch(() => false);
if (isVisible) {
console.log('✅ Found CAPTCHA checkbox!');
await humanLikeDelay(500, 1000);
await moveMouseRandomly(page, 3);
console.log('🖱️ Clicking "I\\'m not a robot" checkbox...');
await humanLikeClick(page, checkbox);
await humanLikeDelay(2000, 3000);
console.log('✅ CAPTCHA checkbox clicked\\n');
return true;
} else {
console.log('ℹ️ No CAPTCHA detected\\n');
return false;
}
} catch (error) {
console.log('ℹ️ No CAPTCHA detected (error checking)\\n');
return false;
}
}
reCAPTCHA iframe을 찾아 "I'm not a robot" 체크박스를 자동으로 클릭합니다. 체크박스를 찾지 못하면 조용히 넘어갑니다.
6. 봇 감지 우회 설정
const browser = await chromium.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled'
]
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...'
});
await context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
});
navigator.webdriver 속성을 숨기고, 자동화 관련 Chrome 기능을 비활성화하여 봇 감지를 우회합니다.
📝 전체 코드 (통합)
import { chromium } from 'playwright-core';
import fs from 'fs';
function randomDelay(min = 0, max = 1000) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function humanLikeDelay(min = 300, max = 1000) {
const delay = randomDelay(min, max);
await new Promise(resolve => setTimeout(resolve, delay));
}
async function moveMouseRandomly(page, count = 5) {
const viewport = page.viewportSize();
for (let i = 0; i < count; i++) {
const x = Math.floor(Math.random() * viewport.width);
const y = Math.floor(Math.random() * viewport.height);
await page.mouse.move(x, y, { steps: randomDelay(5, 15) });
await humanLikeDelay(100, 500);
}
}
async function humanLikeClick(page, locator) {
const box = await locator.boundingBox();
if (box) {
const x = box.x + box.width / 2 + randomDelay(-5, 5);
const y = box.y + box.height / 2 + randomDelay(-5, 5);
await page.mouse.move(x, y, { steps: randomDelay(10, 30) });
await humanLikeDelay(100, 300);
await page.mouse.down();
await new Promise(resolve => setTimeout(resolve, randomDelay(50, 150)));
await page.mouse.up();
}
}
async function humanLikeType(page, locator, text) {
await locator.click();
await humanLikeDelay(200, 500);
for (const char of text) {
await page.keyboard.type(char);
await new Promise(resolve => setTimeout(resolve, randomDelay(50, 200)));
}
}
async function checkAndHandleCaptcha(page) {
console.log('🤖 Checking for "I\\'m not a robot" checkbox...');
try {
const captchaFrame = page.frameLocator('iframe[src*="recaptcha"]').first();
const checkbox = captchaFrame.locator('.recaptcha-checkbox-border, #recaptcha-anchor');
const isVisible = await checkbox.isVisible({ timeout: 3000 }).catch(() => false);
if (isVisible) {
console.log('✅ Found CAPTCHA checkbox!');
await humanLikeDelay(500, 1000);
await moveMouseRandomly(page, 3);
console.log('🖱️ Clicking "I\\'m not a robot" checkbox...');
await humanLikeClick(page, checkbox);
await humanLikeDelay(2000, 3000);
console.log('✅ CAPTCHA checkbox clicked\\n');
return true;
} else {
console.log('ℹ️ No CAPTCHA detected\\n');
return false;
}
} catch (error) {
console.log('ℹ️ No CAPTCHA detected (error checking)\\n');
return false;
}
}
async function main() {
console.log('=== Playwright Example: Google Search (Human-like) ===\\n');
const browser = await chromium.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled'
]
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
});
await context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
});
const page = await context.newPage();
await page.goto('https://www.google.com', {
waitUntil: 'networkidle'
});
await humanLikeDelay(1000, 2000);
await moveMouseRandomly(page, 3);
await checkAndHandleCaptcha(page);
const searchInput = await page.locator('textarea[name="q"]').first();
await humanLikeType(page, searchInput, 'AI');
await humanLikeDelay(500, 1000);
await page.keyboard.press('Enter');
await page.waitForLoadState('networkidle');
await humanLikeDelay(1000, 2000);
await checkAndHandleCaptcha(page);
await moveMouseRandomly(page, 4);
await page.screenshot({ path: 'playwright-screenshot.png' });
await browser.close();
}
main().catch(console.error);
🔑 핵심 기법 정리
기법목적구현
| 랜덤 딜레이 | 불규칙한 타이밍 | 0-1000ms 랜덤 대기 |
| 마우스 움직임 | 사람처럼 행동 | 랜덤 좌표 + 부드러운 이동 |
| 자연스러운 클릭 | 봇 감지 우회 | 요소 중앙 ±5px 오프셋 |
| 사람같은 타이핑 | 키 입력 자연화 | 문자당 50-200ms 딜레이 |
| CAPTCHA 처리 | 자동화 차단 우회 | iframe 내 체크박스 감지 및 클릭 |
| webdriver 숨김 | 봇 감지 우회 | navigator.webdriver = undefined |
7. CDP vs Playwright 비교
항목CDPPlaywright
| 난이도 | 어려움 (저수준 API) | 쉬움 (고수준 API) |
| 코드 길이 | 길고 복잡함 | 짧고 간결함 |
| 요소 선택 | JavaScript 직접 작성 | .locator() API |
| 자동 대기 | 수동 setTimeout | 자동 대기 내장 |
| 안정성 | 타이밍 이슈 발생 가능 | 매우 안정적 |
| 유연성 | 매우 높음 | 적당함 |
| 브라우저 지원 | Chrome 계열만 | Chrome, Firefox, Safari |
| 학습 곡선 | 가파름 | 완만함 |
| 사용 사례 | 특수한 제어 필요 시 | 대부분의 자동화 작업 |
📊 언제 무엇을 사용할까?
- 일반적인 웹 스크래핑
- E2E 테스트
- 빠른 프로토타이핑
- 안정성이 중요한 경우
- 매우 세밀한 제어가 필요한 경우
- Playwright로 불가능한 작업
- CDP 프로토콜 학습이 목적인 경우
- 최소한의 의존성이 필요한 경우
8. 모범 사례
✅ DO (권장사항)
- 에러 처리: 모든 비동기 작업에 try-catch 사용
- 타임아웃 설정: 무한 대기 방지를 위한 적절한 타임아웃
- 리소스 정리: 브라우저와 페이지를 항상 닫기
- 선택자 안정성: 변하지 않는 선택자 사용 (data-testid 등)
- 대기 전략: 명시적 대기 사용 (waitFor, waitForLoadState)
- 봇 감지 우회: 사람처럼 행동하기 (랜덤 딜레이, 마우스 움직임)
- Rate Limiting: 서버 부하를 고려한 요청 간격 조절
❌ DON'T (피해야 할 사항)
- 고정 딜레이: setTimeout(5000) 같은 고정 대기 대신 조건부 대기 사용
- CSS 선택자 남용: 너무 구체적이거나 불안정한 선택자
- 리소스 누수: 브라우저를 닫지 않고 프로세스 종료
- 동기 코드: async/await 없이 Promise 사용
- 서비스 약관 위반: 웹사이트 정책 확인 없이 스크래핑
- 과도한 요청: 짧은 시간에 대량 요청으로 서버 부하 유발
🛡️ 보안 및 윤리
- robots.txt 준수
- 서비스 약관 확인
- 개인정보 보호
- 저작권 존중
- 교육 목적으로만 사용
🚀 성능 최적화
- Headless 모드: 실제 배포 시 headless: true 사용
- 리소스 차단: 불필요한 이미지, 폰트 등 차단
- 병렬 처리: 여러 페이지를 동시에 처리
- 브라우저 재사용: 매번 새로 실행하지 않고 재사용
리소스 차단 예제
await page.route('**/*.{png,jpg,jpeg,gif,svg}', route => route.abort());
await page.route('**/*.{woff,woff2,ttf}', route => route.abort());
📝 코드 구조화
class GoogleSearchBot {
constructor() {
this.browser = null;
this.page = null;
}
async init() {
this.browser = await chromium.launch({ headless: false });
const context = await this.browser.newContext();
this.page = await context.newPage();
}
async search(query) {
await this.page.goto('https://www.google.com');
const input = this.page.locator('textarea[name="q"]');
await input.fill(query);
await input.press('Enter');
await this.page.waitForLoadState('networkidle');
}
async getResults() {
return await this.page.locator('h3').allTextContents();
}
async close() {
if (this.browser) {
await this.browser.close();
}
}
}
const bot = new GoogleSearchBot();
await bot.init();
await bot.search('AI');
const results = await bot.getResults();
console.log(results);
await bot.close();
🎓 추가 학습 자료
📚 공식 문서
🔧 유용한 도구
- Playwright Inspector: 디버깅 도구
- Playwright Codegen: 자동 코드 생성
- Chrome DevTools: 요소 검사 및 선택자 테스트
Playwright Codegen 사용
npx playwright codegen https://www.google.com
브라우저에서 수행한 작업을 자동으로 코드로 변환해줍니다.
💡 다음 단계
- 다양한 웹사이트에서 실습
- 복잡한 시나리오 구현 (로그인, 폼 작성 등)
- 데이터베이스 연동
- 스케줄링 (cron jobs)
- 분산 스크래핑 (여러 인스턴스 동시 실행)
'빅데이타 & 머신러닝 > 생성형 AI (ChatGPT etc)' 카테고리의 다른 글
| Open AI Day 2025 발표 내용 정리 #1 (0) | 2025.10.08 |
|---|---|
| Claude Code 성능 극대화를 위한 베스트 프랙티스 (7) | 2025.09.02 |
| AI 자동화와 Zapier, n8n 소개 및 비교 (4) | 2025.07.17 |
| Langchain이 있는데, Langgraph가 왜 필요할까? (1) | 2025.07.17 |
| 고급 Agent를 위한 Langgraph 개념 이해 #4 - Tool 호출 (2) | 2025.07.15 |