13.Python으로 웹 크롤링하기 — requests와 BeautifulSoup 입문 가이드
이 글에서는 웹 크롤링의 기본 원리부터 requests로 웹 페이지를 가져오고 BeautifulSoup으로 원하는 데이터를 추출하는 방법, 그리고 실제로 데이터를 CSV로 저장하는 실전 예제까지 단계별로 안내해 드리겠습니다.
웹 크롤링이란 무엇인가 — 도서관 사서 비유로 이해하기
웹 크롤링(Web Crawling)은 프로그램이 자동으로 웹 페이지에 접속해서 필요한 정보를 수집하는 기술입니다.
비유하자면 도서관 사서와 같습니다. 내가 직접 책장 사이를 돌아다니며 필요한 책을 찾는 대신, 사서에게 "경제학 관련 책 목록을 전부 뽑아줘"라고 부탁하면 사서가 알아서 정리해 가져다주는 것입니다. 웹 크롤러는 그 사서 역할을 Python 코드로 구현한 것입니다.
웹 스크래핑 vs 웹 크롤링
두 용어가 혼용되지만 엄밀히는 다릅니다.
- 웹 크롤링: 여러 페이지를 자동으로 이동하며 링크를 따라다니는 행위 (구글 봇이 하는 일)
- 웹 스크래핑: 특정 페이지에서 원하는 데이터만 추출하는 행위
이 글에서 다루는 것은 실질적으로 스크래핑에 가깝지만, 실무에서는 두 용어를 구분 없이 '크롤링'으로 통칭하는 경우가 많습니다.
크롤링 전 반드시 확인해야 할 사항
⚠️ 크롤링은 강력한 도구인 만큼 사용에 주의가 필요합니다.
- robots.txt 확인: 사이트 주소 뒤에
/robots.txt를 붙이면 해당 사이트가 크롤링을 허용하는 범위를 확인할 수 있습니다. - 이용약관 확인: 일부 사이트는 자동 수집을 명시적으로 금지합니다.
- 과도한 요청 금지: 짧은 시간에 너무 많은 요청을 보내면 서버에 부담을 주고 IP 차단으로 이어질 수 있습니다.
- 개인정보 포함 데이터 주의: 수집한 데이터를 상업적으로 이용하거나 무단 배포하는 것은 법적 문제가 될 수 있습니다.
크롤링의 작동 원리 — 브라우저가 하는 일을 코드로
우리가 웹사이트를 열면, 브라우저는 서버에 "이 페이지의 HTML을 보내줘"라고 요청합니다. 서버가 HTML을 보내면, 브라우저는 그걸 해석해서 화면에 보기 좋게 표시합니다.
크롤링은 이 과정에서 브라우저 대신 Python이 요청을 보내고 HTML을 받아오는 것입니다. 받아온 HTML에서 원하는 부분만 골라내는 작업이 스크래핑입니다.
전체 흐름 요약:
requests→ 서버에 페이지 요청- 서버 → HTML 반환
BeautifulSoup→ HTML에서 원하는 데이터 추출- 추출한 데이터 → CSV 또는 DB에 저장
환경 설정 — 라이브러리 설치
가상환경이 활성화된 상태에서 두 라이브러리를 설치합니다.
pip install requests beautifulsoup4
- requests: 웹 페이지의 HTML을 가져오는 역할
- beautifulsoup4: 가져온 HTML에서 원하는 데이터를 찾아내는 역할
requests로 웹 페이지 가져오기
기본 요청 보내기
import requests
url = "https://books.toscrape.com"
response = requests.get(url)
print(f"상태 코드: {response.status_code}")
print(f"인코딩: {response.encoding}")
print(response.text[:500]) # HTML 앞부분 500자만 출력
status_code가 200이면 요청 성공입니다. 자주 만나는 코드들은 아래와 같습니다.
| 상태 코드 | 의미 |
|---|---|
| 200 | 요청 성공 |
| 403 | 접근 거부 (크롤링 차단) |
| 404 | 페이지 없음 |
| 429 | 요청이 너무 많음 (속도 제한) |
| 500 | 서버 오류 |
User-Agent 설정하기
일부 사이트는 Python 코드의 자동 요청을 감지해 차단합니다. 이때 브라우저처럼 보이게 헤더를 설정하면 우회할 수 있습니다.
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(url, headers=headers)
BeautifulSoup으로 데이터 추출하기
기본 파싱 구조
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
print(soup.prettify()[:1000]) # 보기 좋게 정리된 HTML 출력
핵심 선택자 — 원하는 요소 찾기
BeautifulSoup에서 데이터를 찾는 방법은 크게 두 가지입니다.
# 1. find() — 조건에 맞는 첫 번째 요소 하나만 반환
title = soup.find("h1")
print(title.text)
# 2. find_all() — 조건에 맞는 모든 요소를 리스트로 반환
all_links = soup.find_all("a")
for link in all_links:
print(link.get("href")) # href 속성값 추출
CSS 선택자 활용 — 더 정밀하게 찾기
# class로 찾기
items = soup.find_all("article", class_="product_pod")
# CSS 선택자 문법 사용 (select)
prices = soup.select("p.price_color")
for price in prices:
print(price.text.strip())
실전 예제 — 도서 정보 수집 후 CSV 저장
books.toscrape.com은 크롤링 연습용으로 공개된 사이트입니다. 책 제목과 가격을 수집해 CSV로 저장하는 완성 코드를 작성해 보겠습니다.
import requests
from bs4 import BeautifulSoup
import csv
import time
BASE_URL = "https://books.toscrape.com/catalogue/"
results = []
# 3페이지만 수집 (과도한 요청 방지)
for page in range(1, 4):
url = f"https://books.toscrape.com/catalogue/page-{page}.html"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"{page}페이지 요청 실패: {response.status_code}")
break
soup = BeautifulSoup(response.text, "html.parser")
books = soup.find_all("article", class_="product_pod")
for book in books:
title = book.find("h3").find("a")["title"]
price = book.find("p", class_="price_color").text.strip()
rating = book.find("p", class_="star-rating")["class"][1]
results.append({
"제목": title,
"가격": price,
"평점": rating
})
print(f"{page}페이지 수집 완료 ({len(books)}권)")
time.sleep(1) # 1초 간격으로 요청 — 서버 부하 방지
# CSV 저장
with open("books.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["제목", "가격", "평점"])
writer.writeheader()
writer.writerows(results)
print(f"\n총 {len(results)}권 수집 완료. books.csv에 저장되었습니다.")
💡
time.sleep(1): 요청 사이에 1초 간격을 두는 코드입니다. 서버에 과도한 부하를 주지 않기 위한 최소한의 예의이자, IP 차단을 방지하는 실용적인 방법입니다. 크롤링 코드에는 항상 포함하시길 권장합니다.
자주 발생하는 오류와 해결법
오류 1 — AttributeError: 'NoneType' object has no attribute 'text'
원인: find()가 해당 요소를 찾지 못해 None을 반환한 뒤 .text를 호출
해결: 찾기 전에 None 여부를 먼저 확인합니다.
element = soup.find("h2")
if element:
print(element.text)
else:
print("요소를 찾을 수 없습니다.")
오류 2 — 한글이 깨져서 출력되는 경우
원인: 사이트 인코딩과 Python 해석 인코딩 불일치
해결: response.encoding을 확인하고 직접 지정합니다.
response.encoding = "utf-8" # 또는 "euc-kr"
오류 3 — 403 Forbidden — 접근이 거부되는 경우
원인: 사이트가 봇 요청을 차단
해결: User-Agent 헤더를 브라우저 값으로 설정하거나, 요청 간격을 늘립니다. 그래도 차단된다면 해당 사이트는 크롤링을 허용하지 않는 것으로 판단하고 중단합니다.
마무리 — 핵심 요약
✅ Python 웹 크롤링 체크리스트
- 크롤링 전 robots.txt와 이용약관을 확인합니다
requests.get(url)으로 HTML을 가져옵니다- 상태 코드 200을 확인 후 파싱을 진행합니다
BeautifulSoup으로find()또는find_all()로 데이터를 추출합니다time.sleep()으로 요청 간격을 반드시 설정합니다- 수집 데이터는 CSV 또는 pandas로 저장합니다
다음으로 알아두면 좋은 것
정적 HTML 기반 사이트는 requests + BeautifulSoup으로 충분합니다. 하지만 JavaScript로 동적으로 데이터를 불러오는 사이트(예: 무한 스크롤, 로그인 후 접근)는 이 방법으로 데이터를 가져올 수 없습니다. 이런 경우에는 브라우저를 직접 제어하는 Selenium 또는 Playwright 를 사용해야 합니다. 다음 글에서는 이 두 도구를 다룰 예정입니다.
댓글
댓글 쓰기