codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›Python · FastAPI · 데이터 파이프라인›5단계

5단계

5단계 — 외부 API · 크롤러 윤리

0회 조회

5단계 — 외부 API · 크롤러 윤리

외부 사이트의 데이터를 가져올 때 지켜야 할 예의 와 기술 이 있어요. 안 지키면 차단되거나, 법적 문제가 생겨요.

크롤러 5계명

  1. robots.txt 를 먼저 본다 — 사이트 루트의 /robots.txt 가 크롤 가능 영역 을 알려줍니다
  2. 요청 간격을 둔다 — 1초당 1회 이하 (rate limit)
  3. User-Agent 를 명시한다 — 어떤 봇인지 밝힘
  4. 캐시한다 — 같은 데이터를 두 번 요청하지 않음
  5. 공식 API 를 우선 시도 — RSS/Atom/Open API 가 있으면 그쪽

http_client 패턴

# utils/http_client.py
import time
import httpx

class RateLimitedClient:
    def __init__(self, base_url: str, requests_per_second: float = 1.0):
        self.client = httpx.Client(
            base_url=base_url,
            headers={"User-Agent": "codingstairs-crawler/1.0 (https://codingstairs.duckdns.org)"},
            timeout=10.0,
        )
        self.min_interval = 1.0 / requests_per_second
        self.last_request_at = 0.0

    def get(self, path: str, **kwargs):
        elapsed = time.time() - self.last_request_at
        if elapsed < self.min_interval:
            time.sleep(self.min_interval - elapsed)
        self.last_request_at = time.time()
        return self.client.get(path, **kwargs)

이 한 클래스를 거치면 자동으로 rate limit + User-Agent 가 적용돼요.

robots.txt 파싱

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()

if not rp.can_fetch("codingstairs-crawler/1.0", "https://example.com/some-page"):
    raise PermissionError("robots.txt 에서 차단된 경로")

기본은 허락받지 않은 곳은 가지 않음.

캐싱은 DB 또는 Redis

def get_with_cache(url: str, ttl_seconds: int = 3600):
    with get_conn() as conn, conn.cursor() as cur:
        cur.execute(
            "SELECT body, fetched_at FROM http_cache WHERE url = %s",
            (url,),
        )
        row = cur.fetchone()
        if row and (datetime.now(tz=UTC) - row[1]).total_seconds() < ttl_seconds:
            return row[0]

    body = client.get(url).text
    with get_conn() as conn, conn.cursor() as cur:
        cur.execute("""
            INSERT INTO http_cache (url, body, fetched_at)
            VALUES (%s, %s, NOW())
            ON CONFLICT (url) DO UPDATE SET body = EXCLUDED.body, fetched_at = NOW()
        """, (url, body))
    return body

Playwright — JavaScript 가 필요한 페이지

정적 HTML 만으론 안 되는 사이트는 Playwright 같은 진짜 브라우저 가 필요. 단, 비용이 100배 비싸므로 최후의 수단.

직접 해 보기

공개 API (예: https://jsonplaceholder.typicode.com) 의 게시물 목록을 1초 간격으로 5번 가져오는 스크립트를 만들어 보세요. RateLimitedClient 가 정확히 1초 간격을 지키는지 확인.

더 깊이

  • 크롤러 윤리 노트
  • Playwright 크롤러 노트 — 외부데이터 SSOT

다음 단계

6단계에서는 가져온 데이터를 변환·저장 하는 ETL 파이프라인을 만들어요.

← 4단계

4단계 — APScheduler 로 정기 작업

6단계 →

6단계 — 데이터 파이프라인