← 파이썬 강의 목록으로
🚀
고급 (Advanced)
requests · BeautifulSoup · session · robots.txt

7주차 — 웹 스크래핑

requests 로 HTTP 요청, BeautifulSoup 로 HTML 파싱, 세션·쿠키 관리, 그리고 robots.txt 와 매너까지 — 윤리적 스크래핑의 기본을 익힙니다.

requestsBeautifulSoupscrapingethics
소요 시간
2시간
난이도
📊 고급
선수 조건
🎯 고급 6주차
결과물
정중하고 견고한 스크래퍼 작성

이 강의에서 배우는 것

  • 1requests 로 HTTP GET/POST 요청을 한다
  • 2BeautifulSoup 로 HTML 을 파싱한다
  • 3헤더·세션·쿠키를 다룬다
  • 4robots.txt 와 매너를 지킨다

1. requests

python
import requests

r = requests.get("https://example.com")
print(r.status_code)        # 200
print(r.text[:200])
print(r.headers["Content-Type"])

# JSON API
r = requests.get("https://api.github.com/users/python")
data = r.json()
print(data["public_repos"])

2. POST 요청

python
r = requests.post("https://httpbin.org/post",
    data={"key": "value"},
    headers={"User-Agent": "MyBot 1.0"},
)

3. BeautifulSoup

bash
pip install beautifulsoup4 lxml
python
from bs4 import BeautifulSoup

html = """<html><body>
    <h1>제목</h1>
    <ul>
        <li class="item">A</li>
        <li class="item">B</li>
    </ul>
</body></html>"""

soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.find_all("li")])
print([li.text for li in soup.select("li.item")])   # CSS 선택자

4. 세션과 쿠키

python
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})

# 로그인
session.post("https://example.com/login", data={"user": "x", "pass": "y"})

# 같은 세션 → 쿠키 유지
r = session.get("https://example.com/me")

5. 매너와 윤리

  • robots.txt 확인: https://example.com/robots.txt
  • 요청 간격: time.sleep(1) 정도 권장
  • 사용자 에이전트: 정직하게 표시
  • 저작권·약관 준수
⚠️

스크래핑은 합법성과 윤리가 중요한 영역입니다. 사이트의 이용 약관과 robots.txt 를 반드시 확인하세요.

6. 실전 패턴

python
import requests
from bs4 import BeautifulSoup

URL = "https://news.ycombinator.com"
HEADERS = {"User-Agent": "Mozilla/5.0 (study)"}

def fetch_titles():
    r = requests.get(URL, headers=HEADERS, timeout=10)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    titles = [a.get_text() for a in soup.select(".titleline > a")]
    return titles[:10]

for i, title in enumerate(fetch_titles(), 1):
    print(f"{i}. {title}")

자주 하는 실수

  1. timeout 누락 — 무한 대기. 항상 timeout=10
  2. raise_for_status() 안 함 — 404 못 알아챔
  3. HTML 파서 미지정 — 명시적으로 'html.parser'
  4. 선택자 깨지기 쉬움 — id/class 변동에 취약
  5. 차단당함 — 너무 빠른 요청. time.sleep 으로 간격

FAQ

Q1. JS 렌더링 페이지? — Selenium, Playwright 사용.

Q2. 동적 사이트? — 보통 내부 API 직접 호출(개발자 도구 → 네트워크 탭).

Q3. 로그인 후 페이지? — Session 사용. CSRF 토큰까지.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_requests_basic.pyGET, JSON
CODE
import requests

r = requests.get("https://httpbin.org/get", params={"q": "python"}, timeout=10)
print(r.status_code)
print(r.json()["args"])
▶ 실행 결과
200
{'q': 'python'}
02_bs4_parse.pyHTML 파싱
CODE
from bs4 import BeautifulSoup

html = """<html><body>
<h1>제목</h1>
<ul>
  <li class="item">A</li>
  <li class="item">B</li>
  <li class="item">C</li>
</ul>
</body></html>"""

soup = BeautifulSoup(html, "html.parser")
print(soup.h1.text)
print([li.text for li in soup.select("li.item")])
▶ 실행 결과
제목
['A', 'B', 'C']
03_session.py세션 + 쿠키
CODE
import requests

s = requests.Session()
s.headers.update({"User-Agent": "study/1.0"})

# httpbin이 보낸 쿠키를 받아서 그대로 사용
s.get("https://httpbin.org/cookies/set/sessionid/abc123")
r = s.get("https://httpbin.org/cookies")
print(r.json())
▶ 실행 결과
{'cookies': {'sessionid': 'abc123'}}
04_scrape_demo.py실제 스크래핑 (httpbin)
CODE
import requests
from bs4 import BeautifulSoup

r = requests.get("https://httpbin.org/html", timeout=10)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
print("h1:", soup.h1.get_text(strip=True))
print("p 개수:", len(soup.find_all("p")))
▶ 실행 결과
h1: Herman Melville - Moby-Dick
p 개수: 1

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

JSON API 호출

목표: GitHub API 로 사용자 저장소 정보를 가져온다.

요구사항
  • username 입력 받기
  • public_repos 개수 출력
  • 예외 처리 (timeout, raise_for_status)
💡 힌트

https://api.github.com/users/{username}

입출력 예시
사용자: torvalds
공개 저장소: 7
채점
  • · timeout 사용
  • · raise_for_status 사용
정답 코드 펼치기 / 접기
SOLUTION
import requests

def info(username):
    url = f"https://api.github.com/users/{username}"
    r = requests.get(url, timeout=10)
    r.raise_for_status()
    data = r.json()
    return data.get("public_repos", 0)

name = "torvalds"
print(f"사용자: {name}")
print(f"공개 저장소: {info(name)}")
▶ 실행 결과
사용자: torvalds
공개 저장소: 7
과제 2

HTML 헤딩 추출

목표: 주어진 URL 의 모든 h1, h2 텍스트를 추출한다.

요구사항
  • BeautifulSoup 사용
  • h1, h2 모두
💡 힌트

soup.find_all('h1')

입출력 예시
H1: ['Herman Melville - Moby-Dick']
H2: []
채점
  • · 파서 명시
  • · 결과 list
정답 코드 펼치기 / 접기
SOLUTION
import requests
from bs4 import BeautifulSoup

r = requests.get("https://httpbin.org/html", timeout=10)
soup = BeautifulSoup(r.text, "html.parser")
h1s = [t.get_text(strip=True) for t in soup.find_all("h1")]
h2s = [t.get_text(strip=True) for t in soup.find_all("h2")]
print(f"H1: {h1s}")
print(f"H2: {h2s}")
▶ 실행 결과
H1: ['Herman Melville - Moby-Dick']
H2: []
과제 3

robots.txt 확인

목표: 주어진 도메인의 robots.txt 를 가져와 출력한다.

요구사항
  • https://{domain}/robots.txt
  • 200 이 아니면 안내 메시지
💡 힌트

status_code 확인

입출력 예시
User-agent: *
Disallow: ...
채점
  • · timeout 사용
  • · 비정상 응답 처리
정답 코드 펼치기 / 접기
SOLUTION
import requests

def show_robots(domain):
    url = f"https://{domain}/robots.txt"
    r = requests.get(url, timeout=10)
    if r.status_code != 200:
        print(f"robots.txt 없음 (status {r.status_code})")
        return
    print(r.text[:200])

show_robots("github.com")
▶ 실행 결과
# If you would like to crawl GitHub contact us via https://support.github.com/contact?tags=dotcom-robots
# We also provide an extensive API: https://docs.github.com
User-agent: *
Disallow: /*/pulls
Disallow: /*/pull/
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗