파이썬 웹 크롤링
정적 페이지는 BeautifulSoup, JS 동적 페이지는 Playwright.
22편의 자동화에 한 도구를 더하면 진짜 강력해집니다 — 웹에서 데이터 가져오기. 가격 비교·뉴스 모니터링·환율 추적·경쟁사 신제품 감시. 공식 API 가 없는 정보의 95% 는 웹 페이지에 HTML 로 노출돼 있어요. 그걸 자동 추출하는 게 크롤링(스크레이핑).
23편을 마치면 ① 정적/동적 페이지의 차이 ② requests + BeautifulSoup 기본 ③ CSS 선택자 ④ Playwright 로 JS 페이지 ⑤ 매너 — robots.txt·rate-limit·법적 경계 — 5가지를 손에 익힙니다.
정적 vs 동적 페이지 — 첫 분기
같은 페이지 URL 이라도 두 종류로 나뉩니다.
- 정적 — 서버가 완성된 HTML 을 한 번에 보냄. 페이지 소스에 내가 보는 글자가 다 있음.
requests + BeautifulSoup으로 충분 - 동적 — JS 가 브라우저에서 데이터를 추가로 받아 화면을 그림. 페이지 소스에는 빈 껍데기만. 실제 브라우저가 필요 → Playwright
판별: requests.get(URL).text 안에 찾는 내용이 있으면 정적, 없으면 동적. 동적이라도 보통은 view-source: 또는 개발자 도구 Network 탭에서 진짜 API URL 을 찾아 직접 호출하는 게 가장 빠릅니다.
requests + BeautifulSoup — 정적 정공법
(.venv) $ pip install requests beautifulsoup4 lxml
import requests
from bs4 import BeautifulSoup
UA = {"User-Agent": "Mozilla/5.0 (study-bot/1.0)"}
r = requests.get("https://example.com/blog", headers=UA, timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "lxml") # lxml 이 가장 빠른 파서
# 한 요소
title = soup.select_one("h1.post-title")
print(title.get_text(strip=True))
# 여러 요소
for art in soup.select("article.post"):
t = art.select_one("h2").get_text(strip=True)
link = art.select_one("a")["href"]
print(t, "→", link)
select / select_one 이 핵심 — CSS 선택자로 찾습니다. 브라우저 개발자 도구에서 요소 우클릭 → "Copy → Copy selector" 가 자주 쓰는 트릭. 다음 표가 자주 쓰는 패턴.
📌 CSS 선택자 7가지
· h1 — 태그명
· .title — 클래스
· #main — id
· div.post — 태그 + 클래스
· article a — 자손
· li > a — 직계 자식
· a[href^="https"] — 속성 조건 (시작/끝/포함)
Playwright — JS 동적 페이지
(.venv) $ pip install playwright
(.venv) $ playwright install chromium # 브라우저 다운로드
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example-spa.com/products", timeout=30000)
page.wait_for_selector("div.product-card")
titles = page.eval_on_selector_all(
"div.product-card h3",
"els => els.map(e => e.textContent.trim())"
)
print(titles)
browser.close()
실제 크롬을 띄워 JS 가 그린 결과를 받습니다. 무겁지만 React·Vue·Angular SPA 가 그린 화면도 그대로 봅니다.
대안: 브라우저 Network 탭 트릭
# JS 가 호출하는 API 를 찾아 직접 부른다 (가능하면 항상 이게 정공법)
r = requests.get(
"https://api.example.com/v2/products",
headers={"User-Agent": "...", "Accept": "application/json"},
params={"page": 1},
)
data = r.json()
# Playwright 보다 100배 빠르고 안정적
Playwright 는 모든 다른 방법이 실패했을 때의 마지막 카드. JS 가 그린 페이지라도 그 데이터를 받아오는 API 가 있고, 개발자 도구에서 찾을 수 있습니다.
매너와 함정 + 마무리
robots.txt 확인 (example.com/robots.txt) — Disallow 경로는 안 가져간다. ② rate-limit — 한 사이트당 초당 1회 이하 권장, time.sleep(1). ③ User-Agent 와 연락처 — 봇이라고 솔직하게. ④ API 가 있으면 API 우선. ⑤ 저작권·약관 — 데이터 재배포는 별도 문제.
# 안전한 크롤러 뼈대
import time, requests
from bs4 import BeautifulSoup
UA = {"User-Agent": "research-bot/0.1 (+mailto:[email protected])"}
def fetch(url: str) -> BeautifulSoup:
r = requests.get(url, headers=UA, timeout=10)
r.raise_for_status()
return BeautifulSoup(r.text, "lxml")
for url in urls:
try:
soup = fetch(url)
# ... 추출 ...
except requests.RequestException as e:
log.error("실패 %s: %s", url, e)
time.sleep(1.0) # rate-limit 매너
다음 미션: ① 본인 블로그(또는 공개 뉴스 RSS) 의 글 제목 10개 추출 ② requests + BeautifulSoup 로 환율 한 페이지 크롤링 후 CSV 저장 ③ 개발자 도구로 어떤 사이트의 숨은 API URL 찾아보기.
다음 편 미리보기
24편 — "파이썬 async·await": 동시성 처리. 100개 URL 호출이 100배 빨라지는 비밀.
← 22편 "자동화" · 다음: 24편 "async·await"