13.Python으로 웹 크롤링하기 — requests와 BeautifulSoup 입문 가이드

처음엔 저도 여러분처럼 필요한 데이터를 모으기 위해 수많은 웹사이트를 클릭하며 일일이 복사했습니다. 하지만 반복되는 단순 업무에 지쳐 자동화를 결심했고, 크롤링을 배운 후에는 수백, 수천 개의 데이터를 단 몇 초 만에 수집할 수 있게 되었습니다. 그때의 경험을 바탕으로, 여러분이 겪고 계신 불필요한 시간 낭비를 확실하게 줄여드리겠습니다

이 글에서는 웹 크롤링의 기본 원리부터 requests로 웹 페이지를 가져오고 BeautifulSoup으로 원하는 데이터를 추출하는 방법, 그리고 실제로 데이터를 CSV로 저장하는 실전 예제까지 단계별로 안내해 드리겠습니다.


웹 크롤링이란 무엇인가 — 도서관 사서 비유로 이해하기

웹 크롤링(Web Crawling)은 프로그램이 자동으로 웹 페이지에 접속해서 필요한 정보를 수집하는 기술입니다.

비유하자면 도서관 사서와 같습니다. 내가 직접 책장 사이를 돌아다니며 필요한 책을 찾는 대신, 사서에게 "경제학 관련 책 목록을 전부 뽑아줘"라고 부탁하면 사서가 알아서 정리해 가져다주는 것입니다. 웹 크롤러는 그 사서 역할을 Python 코드로 구현한 것입니다.

웹 스크래핑 vs 웹 크롤링

두 용어가 혼용되지만 엄밀히는 다릅니다.

  • 웹 크롤링: 여러 페이지를 자동으로 이동하며 링크를 따라다니는 행위 (구글 봇이 하는 일)
  • 웹 스크래핑: 특정 페이지에서 원하는 데이터만 추출하는 행위

이 글에서 다루는 것은 실질적으로 스크래핑에 가깝지만, 실무에서는 두 용어를 구분 없이 '크롤링'으로 통칭하는 경우가 많습니다.

크롤링 전 반드시 확인해야 할 사항

⚠️ 크롤링은 강력한 도구인 만큼 사용에 주의가 필요합니다.

  • robots.txt 확인: 사이트 주소 뒤에 /robots.txt를 붙이면 해당 사이트가 크롤링을 허용하는 범위를 확인할 수 있습니다.
  • 이용약관 확인: 일부 사이트는 자동 수집을 명시적으로 금지합니다.
  • 과도한 요청 금지: 짧은 시간에 너무 많은 요청을 보내면 서버에 부담을 주고 IP 차단으로 이어질 수 있습니다.
  • 개인정보 포함 데이터 주의: 수집한 데이터를 상업적으로 이용하거나 무단 배포하는 것은 법적 문제가 될 수 있습니다.

크롤링의 작동 원리 — 브라우저가 하는 일을 코드로

우리가 웹사이트를 열면, 브라우저는 서버에 "이 페이지의 HTML을 보내줘"라고 요청합니다. 서버가 HTML을 보내면, 브라우저는 그걸 해석해서 화면에 보기 좋게 표시합니다.

크롤링은 이 과정에서 브라우저 대신 Python이 요청을 보내고 HTML을 받아오는 것입니다. 받아온 HTML에서 원하는 부분만 골라내는 작업이 스크래핑입니다.

전체 흐름 요약:

  1. requests → 서버에 페이지 요청
  2. 서버 → HTML 반환
  3. BeautifulSoup → HTML에서 원하는 데이터 추출
  4. 추출한 데이터 → 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_code200이면 요청 성공입니다. 자주 만나는 코드들은 아래와 같습니다.

상태 코드 의미
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 웹 크롤링 체크리스트

  1. 크롤링 전 robots.txt와 이용약관을 확인합니다
  2. requests.get(url)으로 HTML을 가져옵니다
  3. 상태 코드 200을 확인 후 파싱을 진행합니다
  4. BeautifulSoup으로 find() 또는 find_all()로 데이터를 추출합니다
  5. time.sleep()으로 요청 간격을 반드시 설정합니다
  6. 수집 데이터는 CSV 또는 pandas로 저장합니다

다음으로 알아두면 좋은 것

정적 HTML 기반 사이트는 requests + BeautifulSoup으로 충분합니다. 하지만 JavaScript로 동적으로 데이터를 불러오는 사이트(예: 무한 스크롤, 로그인 후 접근)는 이 방법으로 데이터를 가져올 수 없습니다. 이런 경우에는 브라우저를 직접 제어하는 Selenium 또는 Playwright 를 사용해야 합니다. 다음 글에서는 이 두 도구를 다룰 예정입니다.



댓글

이 블로그의 인기 게시물

1.Python 설치부터 실행까지 10분 만에 끝내기 — 초보자도 바로 따라 하는 완벽 가이드

30.Python pyautogui로 마우스와 키보드 자동화하기: VSCode 실전 가이드

Python 오류 해결: pip install 오류 해결하기 완벽 가이드