카테고리: 업무 자동화

파이썬 업무 자동화#2웹 크롤링 입문 (requests + BeautifulSoup)!

목차9개 항목

안녕하세요! Mojangcam입니다 😎

지난 파이썬 업무 자동화 #1 - 엑셀 자동화 입문 (openpyxl)에서는 엑셀 파일 3개를 파이썬이 알아서 합치고 보고서까지 만들어봤죠. 그런데 보고서에 들어갈 자료를 매일 웹사이트에서 눈으로 보고 손으로 복사해온다면… 그것부터 자동화해야겠죠?

그래서 이번 편은 웹 크롤링(Web Crawling) 입문입니다! requests로 웹 페이지를 통째로 가져오고, BeautifulSoup으로 원하는 부분만 쏙쏙 뽑아낼 거예요. 마지막엔 여러 페이지를 돌면서 수집한 데이터를 CSV 파일로 저장하는 것까지 해봅니다. 매일 아침 특정 사이트의 목록·시세·공지를 확인하는 일이 있다면, 오늘 글 하나로 그 일이 스크립트 실행 한 번으로 끝나요 💡

📌 준비물

  • Python 3.10 이상 (requests 최신 버전이 3.10+를 요구해요)
  • requests 2.34.x (2026-07 기준 최신 2.34.2)
  • beautifulsoup4 4.15.x (2026-07 기준 최신 4.15.0)
터미널
pip install requests beautifulsoup4

연습 대상은 quotes.toscrape.com 이라는 사이트예요. 크롤링 연습용으로 공개된 사이트라서 마음껏 긁어도 법적·매너 문제가 전혀 없어요. 실제 사이트를 크롤링할 때 주의할 점은 글 끝에서 따로 짚어드릴게요!

📌 1단계 - requests로 HTML 가져오기

웹 페이지는 결국 HTML이라는 텍스트 덩어리예요. requests는 브라우저 대신 그 텍스트를 받아오는 역할을 해요.

Python
# requests 2.34.2 / Python 3.10+
import requests

url = "https://quotes.toscrape.com/"
headers = {"User-Agent": "Mozilla/5.0"}

response = requests.get(url, headers=headers, timeout=10)
print(response.status_code)   # 200이면 성공!
print(response.text[:300])    # HTML 앞부분 살짝 보기

하나씩 뜯어볼게요 💡

  • requests.get(url) : 브라우저 주소창에 url을 치고 엔터 누르는 것과 같아요. 결과가 response에 담겨요.
  • headers의 User-Agent : "나 브라우저예요"라고 알려주는 신분증이에요. 이게 없으면 일부 사이트가 봇으로 판단해서 403 에러로 거절해요.
  • timeout=10 : 10초 안에 응답이 없으면 포기해요. 안 걸어두면 스크립트가 무한 대기할 수 있어서 습관처럼 넣는 게 좋아요.
  • status_code : 200이면 성공, 404는 페이지 없음, 403은 접근 거부예요.

📌 2단계 - BeautifulSoup으로 원하는 데이터 뽑기

받아온 HTML에서 원하는 부분만 골라내는 게 BeautifulSoup의 역할이에요. CSS 선택자를 쓰는 select() 하나만 익히면 웬만한 건 다 뽑아요.

Python
# beautifulsoup4 4.15.0 / Python 3.10+
from bs4 import BeautifulSoup

soup = BeautifulSoup(response.text, "html.parser")

quotes = soup.select("div.quote")   # class가 quote인 div 전부
for q in quotes:
    text = q.select_one("span.text").get_text()
    author = q.select_one("small.author").get_text()
    print(f"{text} - {author}")

하나씩 뜯어볼게요 💡

  • BeautifulSoup(html, "html.parser") : HTML 텍스트를 검색 가능한 객체(soup)로 바꿔요. "html.parser"는 파이썬 내장 파서라 추가 설치가 필요 없어요.
  • select("div.quote") : CSS 선택자로 요소를 전부 찾아 리스트로 돌려줘요. 태그.클래스명 형태만 기억하면 돼요.
  • select_one(...) : 조건에 맞는 첫 번째 요소 하나만 가져와요.
  • get_text() : 태그를 벗겨내고 순수 텍스트만 꺼내요.

어떤 선택자를 써야 할지는 크롬에서 F12(개발자 도구) → 원하는 부분 우클릭 → 검사를 눌러 HTML 구조를 보면 알 수 있어요. 이 과정이 크롤링의 8할이에요!

📌 3단계 - 여러 페이지 돌면서 CSV로 저장하기

실전에서는 1페이지만 긁는 경우가 거의 없죠. 페이지를 순회하면서 수집하고, 결과를 CSV로 저장해볼게요.

Python
# requests 2.34.2 / beautifulsoup4 4.15.0 / Python 3.10+
import csv
import time

all_quotes = []

for page in range(1, 4):   # 1~3페이지
    url = f"https://quotes.toscrape.com/page/{page}/"
    response = requests.get(url, headers=headers, timeout=10)
    soup = BeautifulSoup(response.text, "html.parser")

    for q in soup.select("div.quote"):
        all_quotes.append({
            "명언": q.select_one("span.text").get_text(strip=True),
            "작가": q.select_one("small.author").get_text(strip=True),
        })
    time.sleep(1)   # 페이지마다 1초 쉬기 (크롤링 매너!)

with open("quotes.csv", "w", newline="", encoding="utf-8-sig") as f:
    writer = csv.DictWriter(f, fieldnames=["명언", "작가"])
    writer.writeheader()
    writer.writerows(all_quotes)

하나씩 뜯어볼게요 💡

  • f"...page/{page}/" : 페이지 번호만 바뀌는 URL 패턴을 f-string으로 만들어 순회해요. 대부분의 게시판형 사이트가 이 패턴이에요.
  • time.sleep(1) : 요청 사이에 1초씩 쉬어요. 쉬지 않고 긁으면 서버에 부담을 주고 차단당할 수 있어요.
  • encoding="utf-8-sig" : 이걸 빼면 엑셀에서 CSV를 열 때 한글이 깨져요. 꼭 -sig까지 붙여주세요!
  • get_text(strip=True) : 텍스트 앞뒤의 공백·줄바꿈을 자동으로 정리해요.

📌 전체 코드 (복붙 OK)

Python
# requests 2.34.2 / beautifulsoup4 4.15.0 / Python 3.10+
import csv
import time

import requests
from bs4 import BeautifulSoup

HEADERS = {"User-Agent": "Mozilla/5.0"}

def crawl_quotes(max_page: int = 3) -> list[dict]:
    """quotes.toscrape.com에서 명언을 수집해 리스트로 반환"""
    results = []
    for page in range(1, max_page + 1):
        url = f"https://quotes.toscrape.com/page/{page}/"
        response = requests.get(url, headers=HEADERS, timeout=10)
        response.raise_for_status()   # 200이 아니면 에러 발생

        soup = BeautifulSoup(response.text, "html.parser")
        for q in soup.select("div.quote"):
            results.append({
                "명언": q.select_one("span.text").get_text(strip=True),
                "작가": q.select_one("small.author").get_text(strip=True),
                "태그": ", ".join(t.get_text() for t in q.select("a.tag")),
            })
        print(f"{page}페이지 수집 완료 ({len(results)}개 누적)")
        time.sleep(1)
    return results

def save_csv(rows: list[dict], filename: str = "quotes.csv") -> None:
    """수집 결과를 CSV로 저장 (엑셀에서 한글 안 깨짐)"""
    with open(filename, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=["명언", "작가", "태그"])
        writer.writeheader()
        writer.writerows(rows)
    print(f"저장 완료 → {filename}")

if __name__ == "__main__":
    quotes = crawl_quotes(max_page=3)
    save_csv(quotes)

📌 실행 결과

텍스트
1페이지 수집 완료 (10개 누적)
2페이지 수집 완료 (20개 누적)
3페이지 수집 완료 (30개 누적)
저장 완료 → quotes.csv

30개의 명언이 작가·태그와 함께 quotes.csv에 저장돼요. 엑셀에서 열면 표로 깔끔하게 정리되어 있어요. 지난 편의 openpyxl과 조합하면 "웹에서 긁어와서 → 엑셀 보고서로 만들기"가 완전 자동이 되는 거죠!

📌 자주 나는 에러

  1. ModuleNotFoundError: No module named 'bs4'
    • 원인: beautifulsoup4가 설치 안 됐거나, 설치한 곳과 다른 파이썬(가상환경)으로 실행한 경우예요.
    • 해결: pip install beautifulsoup4 재실행. 가상환경을 쓴다면 활성화부터 확인하세요. 패키지 이름은 bs4가 아니라 beautifulsoup4로 설치해야 해요! (가상환경이 헷갈리면 파이썬 가상환경(venv) 완벽 정리 편을 참고하세요)
  2. 403 Forbidden (HTTPError)
    • 원인: 사이트가 요청을 봇으로 판단해 거절한 거예요. User-Agent 없이 요청하면 자주 발생해요.
    • 해결: 위 예제처럼 headers에 User-Agent를 넣어주세요. 그래도 막히면 그 사이트가 크롤링을 원치 않는 것이니 공식 API가 있는지 찾아보는 게 정답이에요.
  3. AttributeError: 'NoneType' object has no attribute 'get_text'
    • 원인: select_one()이 아무것도 못 찾아서 None을 돌려줬는데 거기에 .get_text()를 호출한 경우예요. 선택자가 틀렸거나 페이지 구조가 바뀐 거죠.
    • 해결: F12로 HTML 구조를 다시 확인하고 선택자를 고치세요. 값이 없을 수도 있는 요소라면 if q.select_one(...): 처럼 None 체크를 먼저 해주세요.

💬 한 가지 더 — 실제 사이트를 크롤링하기 전엔 크롤링 매너 3가지를 꼭 기억하세요. ① 사이트 주소 뒤에 /robots.txt를 붙여 열어보고 금지된 경로는 긁지 않기, ② time.sleep()으로 요청 간격 두기, ③ 수집한 데이터는 개인 용도로만 쓰고 무단 재배포하지 않기. 또 네이버·인스타그램처럼 자바스크립트로 화면을 그리는 사이트는 requests로 HTML을 받아도 내용이 비어 있는데, 이런 동적 페이지는 #7 Selenium 편에서 다룰 예정이에요!

📌 마무리

오늘은 requests로 HTML을 받아오고, BeautifulSoup의 select()로 원하는 데이터만 뽑아 CSV로 저장하는 것까지 해봤어요. 핵심은 딱 두 가지예요. F12로 구조 확인 → select()로 뽑기. 이 패턴만 익히면 어떤 사이트든 응용할 수 있어요!

다음 편은 파이썬 업무 자동화 #3 - 파일 정리 자동화예요. 다운로드 폴더에 쌓인 파일 수백 개를 규칙에 따라 착착 정리하고 이름까지 일괄 변경하는 스크립트를 만들어볼게요. 기대해주세요 🙌

📚 파이썬 업무 자동화 시리즈

연재 · 14편 중 2번째

파이썬 업무 자동화

  1. #1엑셀 자동화 입문 (openpyxl)!
  2. #2웹 크롤링 입문 (requests + BeautifulSoup)!지금 읽는 글
  3. #3파일 정리 자동화 (이름 일괄 변경)!
  4. #4디스코드 웹훅(Webhook)으로 알림 보내기 (requests)!
  5. #5로컬 LLM(LM Studio)으로 고객 문의 자동 분류·요약하기! (무료 AI)
  6. #6예약 실행 자동화! 매일 정해진 시간에 스크립트 돌리기 (schedule·APScheduler)
  7. #7동적 페이지·로그인 자동화! requests로 안 되는 사이트 긁기 (Selenium)
  8. #8PDF 자동 처리! 여러 파일 병합·분할부터 비밀번호까지 (pypdf) 📄
  9. #9마우스·키보드 GUI 자동화! 클릭·타이핑을 파이썬이 대신하게 하기 (PyAutoGUI)
  10. #10워드(Word) 문서 자동 생성! 엑셀 명단으로 계약서·안내문 100장 뽑기 (python-docx) 📄
  11. #11개인화 대량 메일 자동 발송! 명단별 첨부파일·발송 로그·재시도까지 (smtplib 실전편) 📬
  12. #12구글 시트 연동하기! 팀원과 같이 보는 자동 시트 (gspread) 📗
  13. #13파이썬 자동화 로그·재시도·디스코드 실패 알림 만들기 - 조용히 멈추는 스크립트 잡기
  14. #14파워포인트(PowerPoint) 보고서 자동 생성! 엑셀 숫자만 바꾸면 발표자료 완성 (python-pptx) 📊
전체 14편 보기접기

글쓴이

Mojangcam

직접 코드를 짜고, 서버를 돌리고, 막힌 에러를 해결하며 배운 것들을 기록하는 개발·IT 실전 기록 블로그