BioPlayground

🧬
목록으로

웹 크롤링 기초 — BeautifulSoup

Python의 requests와 BeautifulSoup으로 웹 페이지에서 원하는 데이터를 추출하는 방법을 배웁니다.

중급
|
10
|
검증 완료 (2026-07)
웹 크롤링BeautifulSouprequestsHTML 파싱스크래핑
진행률0/17 (0%)

웹 크롤링 기초 — BeautifulSoup

이 토픽을 마치면

requests로 웹 페이지를 가져오고, BeautifulSoup으로 원하는 데이터를 추출하는 기본 흐름을 구현할 수 있습니다.


크롤링이란

웹 크롤링(web crawling)은 프로그램이 웹 페이지를 방문해서 데이터를 자동으로 수집하는 것입니다. 스크래핑(scraping)이라고도 합니다.

브라우저가 하는 일을 코드로 하는 것입니다:

  1. 웹 서버에 요청(request)을 보냄
  2. HTML을 받아옴
  3. HTML에서 원하는 데이터를 추출

1단계 — HTML 가져오기 (requests)

python
import requests
url = "https://example.com"
response = requests.get(url)
print(response.status_code) # 200 = 정상
print(response.text[:200]) # HTML 텍스트의 처음 200자

requests.get(url)이 브라우저처럼 서버에 요청을 보내고, 서버가 보낸 HTML을 response.text로 받습니다.

python
# 상태 코드 확인
if response.status_code == 200:
html = response.text
elif response.status_code == 404:
print("페이지를 찾을 수 없습니다")
elif response.status_code == 403:
print("접근이 거부되었습니다")

2단계 — HTML 파싱 (BeautifulSoup)

python
from bs4 import BeautifulSoup
html = """
<html>
<body>
<h1 class="title">뉴스 제목</h1>
<div class="content">
<p>첫 번째 문단입니다.</p>
<p>두 번째 문단입니다.</p>
</div>
<ul id="tags">
<li>Python</li>
<li>데이터</li>
<li>크롤링</li>
</ul>
</body>
</html>
"""
soup = BeautifulSoup(html, "html.parser")

BeautifulSoup은 HTML 문자열을 파싱해서 트리 구조의 객체로 만들어줍니다. 이 객체에서 태그, 클래스, id 등으로 원하는 부분을 찾을 수 있습니다.


3단계 — 데이터 추출

태그로 찾기

python
# 첫 번째 h1 태그
title = soup.find("h1")
print(title.text) # "뉴스 제목"
print(title["class"]) # ["title"]
# 모든 p 태그
paragraphs = soup.find_all("p")
for p in paragraphs:
print(p.text)
# "첫 번째 문단입니다."
# "두 번째 문단입니다."

CSS 선택자로 찾기

python
# select — CSS 선택자 사용 (querySelectorAll과 동일)
items = soup.select("ul#tags li")
for item in items:
print(item.text)
# "Python"
# "데이터"
# "크롤링"
# 클래스로 선택
content = soup.select_one("div.content")
print(content.text.strip())

find/find_all보다 select/select_one이 CSS에 익숙한 사람에게는 직관적입니다.


실전 예시 — 테이블 데이터 추출

python
table_html = """
<table>
<tr><th>이름</th><th>점수</th></tr>
<tr><td>철수</td><td>85</td></tr>
<tr><td>영희</td><td>92</td></tr>
<tr><td>민수</td><td>78</td></tr>
</table>
"""
soup = BeautifulSoup(table_html, "html.parser")
rows = soup.select("tr")
data = []
for row in rows[1:]: # 헤더 제외
cols = row.find_all("td")
data.append({
"이름": cols[0].text,
"점수": int(cols[1].text)
})
print(data)
# [{'이름': '철수', '점수': 85}, {'이름': '영희', '점수': 92}, {'이름': '민수', '점수': 78}]
python
# pandas DataFrame으로 바로 변환
import pandas as pd
df = pd.DataFrame(data)
print(df)

주의할 점

요청 간격

python
import time
urls = ["https://example.com/page/1", "https://example.com/page/2"]
for url in urls:
response = requests.get(url)
time.sleep(1) # 1초 대기 — 서버에 부담을 주지 않기 위해

연속으로 빠르게 요청하면 서버에 부담을 주거나 IP가 차단될 수 있습니다.

User-Agent 설정

python
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0"
}
response = requests.get(url, headers=headers)

일부 서버는 봇을 차단하기 위해 User-Agent를 확인합니다.

robots.txt

text
https://example.com/robots.txt

이 파일은 크롤링이 허용된 경로와 금지된 경로를 명시합니다. 사이트의 크롤링 정책을 먼저 확인하는 것이 예의이자 법적 안전장치입니다.


크롤링의 한계

BeautifulSoup은 정적 HTML만 처리할 수 있습니다. JavaScript로 동적으로 로딩되는 콘텐츠(SPA, 무한 스크롤 등)는 HTML에 포함되지 않으므로 추출할 수 없습니다.

상황도구
정적 HTMLrequests + BeautifulSoup
JavaScript 동적 로딩Selenium, Playwright
API 제공requests로 API 직접 호출 (가장 효율적)

API가 있다면 크롤링보다 API를 우선 사용합니다. 구조화된 데이터를 정확하게 받을 수 있고, 서버에도 부담이 적습니다.


핵심 흐름 요약

text
1. requests.get(url) → HTML 텍스트
2. BeautifulSoup(html) → 파싱된 트리
3. soup.select("CSS 선택자") → 원하는 요소
4. element.text / element["attr"] → 데이터 추출

에러 처리

실제 크롤링에서는 다양한 에러가 발생합니다:

python
import requests
from bs4 import BeautifulSoup
def safe_fetch(url, retries=3):
for attempt in range(retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
except requests.exceptions.Timeout:
print(f"타임아웃 ({attempt + 1}/{retries})")
except requests.exceptions.HTTPError as e:
print(f"HTTP 에러: {e}")
return None
except requests.exceptions.ConnectionError:
print(f"연결 실패 ({attempt + 1}/{retries})")
return None

timeout을 설정하지 않으면 응답 없는 서버에서 프로그램이 무한 대기합니다. raise_for_status()는 4xx/5xx 응답에서 예외를 발생시킵니다.


여러 페이지 크롤링

python
import time
base_url = "https://example.com/articles?page="
all_titles = []
for page in range(1, 11):
html = safe_fetch(f"{base_url}{page}")
if html is None:
continue
soup = BeautifulSoup(html, "html.parser")
titles = soup.select("h2.article-title")
all_titles.extend([t.text.strip() for t in titles])
print(f"페이지 {page}: {len(titles)}개 수집")
time.sleep(1)
print(f"총 {len(all_titles)}개 수집 완료")

크롤링 결과 저장

python
import json
import csv
# JSON으로 저장
with open("articles.json", "w", encoding="utf-8") as f:
json.dump(all_titles, f, ensure_ascii=False, indent=2)
# CSV로 저장
with open("articles.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["번호", "제목"])
for i, title in enumerate(all_titles, 1):
writer.writerow([i, title])

수집한 데이터는 JSON이나 CSV로 저장해서 pandas로 바로 분석할 수 있게 합니다.



링크 추출 — href 속성

python
# 페이지 내 모든 링크 추출
links = soup.select("a[href]")
for link in links:
url = link["href"]
text = link.text.strip()
print(f"{text}: {url}")
# 특정 패턴의 링크만
article_links = [
a["href"] for a in soup.select("a[href]")
if "/article/" in a.get("href", "")
]

크롤링은 "데이터를 구하는 기술"의 첫 걸음입니다. 데이터 분석, 머신러닝, 모니터링 등 모든 곳에서 데이터 수집이 출발점이 됩니다.