"내결함성 크롤러 설계: 데이터 수집의 속도와 안정성을 잡는 팁"
웹 크롤러를 구축하는 일은 많은 데이터를 자동으로 수집할 수 있다는 점에서 흥미롭습니다. 하지만 단계가 많고 대규모 데이터를 다뤄야 할 경우, 크롤러가 중단되거나 데이터가 중복되는 문제를 겪을 수 있습니다. 이번 글에서는 내결함성(Fault Tolerance)이 높은 크롤러를 설계하고, 속도와 안정성을 동시에 잡는 방법에 대해 알아보겠습니다.
💡 크롤러 설계의 주요 과제
중단 상황에서의 복구
크롤링 도중 인터넷 연결 문제나 코드 오류로 인해 작업이 중단되는 경우가 잦습니다. 이를 해결하려면 진행 상황을 저장하고 중단 시점부터 이어서 실행할 수 있어야 합니다.속도 향상
키워드, 제품, 리뷰 등 다단계 데이터를 수집하려면 속도는 크롤러의 핵심 요소가 됩니다. 작업을 병렬화하여 효율성을 극대화할 필요가 있습니다.데이터 중복 방지
이미 수집한 데이터를 또다시 가져오지 않도록, 고유한 기준을 설정해 중복을 방지해야 합니다.오류 복구와 재시도
네트워크 오류나 HTTP 시간 초과는 피할 수 없는 문제입니다. 이러한 오류를 자동으로 복구하거나 재시도할 수 있는 메커니즘이 필요합니다.
🛠️ 단계별 크롤러 설계 방법
1. 단계별 모듈화
크롤러를 하나의 거대한 스크립트로 작성하는 대신, 각 기능을 독립적인 모듈로 나눕니다. 예를 들어:
모듈 1: 키워드로 제품 목록 및 상점 링크 수집
모듈 2: 상점 방문 및 리뷰 필터링 후 리뷰 수집
모듈 3: 데이터 저장 및 중복 방지
모듈 4: 상태 저장 및 복구 기능
이처럼 모듈화를 하면 유지보수가 쉬워지고, 각 단계에서 발생하는 문제를 쉽게 디버깅할 수 있습니다.
2. 상태 저장과 복구
작업 중단 시 복구가 가능하려면 진행 상태를 저장해야 합니다. 이를 위해 간단한 파일 형식(JSON)이나 데이터베이스(SQLite)를 사용할 수 있습니다. 예를 들어:
import json
def save_state(state, filename="crawler_state.json"):
with open(filename, "w") as f:
json.dump(state, f)
def load_state(filename="crawler_state.json"):
try:
with open(filename, "r") as f:
return json.load(f)
except FileNotFoundError:
return {"completed_keywords": [], "completed_stores": {}}
진행 상태 저장: 완료된 키워드와 상점 URL을 저장해 중단된 시점에서 재시작 가능.
복구 프로세스: 상태를 로드한 뒤, 완료되지 않은 항목만 처리.
3. 병렬 처리로 속도 향상
concurrent.futures
의 ThreadPoolExecutor
를 활용해 작업을 병렬 처리하면 속도가 크게 향상됩니다.
from concurrent.futures import ThreadPoolExecutor
def fetch_reviews(store_url, page):
# 상점 URL과 페이지 번호에 따라 리뷰 크롤링
response = requests.get(f"{store_url}/reviews?page={page}")
return response.json() # 예시
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(fetch_reviews, store_url, page) for page in range(1, 21)]
results = [future.result() for future in futures]
병렬 처리: 여러 페이지에서 동시에 데이터를 가져와 속도를 극대화.
최대 작업자 수 조정: 서버에 과부하를 주지 않도록
max_workers
를 적절히 설정.
4. 오류 복구 및 재시도
네트워크 오류나 요청 실패는 일반적입니다. 이를 해결하려면 자동 재시도 메커니즘을 구현합니다. retry
라이브러리는 이를 간단하게 해결할 수 있는 도구입니다.
pip install retry
from retry import retry
@retry(tries=3, delay=5)
def fetch_with_retry(url):
response = requests.get(url, timeout=10)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
return response.json()
재시도 횟수:
tries
를 통해 몇 번까지 재시도할지 설정.지연 시간:
delay
로 재시도 간격을 설정해 서버에 부담을 주지 않음.
5. 데이터 저장 및 중복 방지
수집한 데이터를 저장할 때 중복을 방지해야 효율성이 올라갑니다. SQLite를 사용하면 간단하게 구현할 수 있습니다.
import sqlite3
def save_review_to_db(db_conn, review):
cursor = db_conn.cursor()
cursor.execute(
"INSERT OR IGNORE INTO reviews (review_id, content) VALUES (?, ?)",
(review['id'], review['content']),
)
db_conn.commit()
중복 방지:
INSERT OR IGNORE
를 사용해 이미 저장된 데이터는 무시.데이터베이스 사용 이유: SQLite는 가볍고 동시 접근에 강점이 있음.
6. 로그 및 모니터링
크롤링 작업이 진행되는 동안 로그를 기록해 문제를 쉽게 추적할 수 있도록 합니다.
import logging
logging.basicConfig(filename="crawler.log", level=logging.INFO)
def log_info(message):
logging.info(message)
진행 상황 기록: 성공한 작업, 실패한 URL 등을 로그에 기록.
디버깅 지원: 오류 발생 시 로그를 통해 원인 분석.
🚀 최적화 팁 요약
모듈화: 각 작업 단계를 독립적으로 설계.
상태 저장: JSON 또는 SQLite에 진행 상태를 기록해 중단 시 복구 가능.
병렬 처리:
ThreadPoolExecutor
로 작업 속도 향상.오류 복구:
retry
를 사용해 네트워크 오류를 자동으로 처리.중복 방지: 데이터베이스를 활용해 중복 데이터를 저장하지 않음.
로그 기록: 작업 진행 상태를 기록해 디버깅과 모니터링을 용이하게.
마무리
내결함성 높은 크롤러는 데이터 수집의 안정성과 효율성을 동시에 보장합니다. 위의 설계 방법을 활용하면 대규모 데이터를 빠르고 안정적으로 수집할 수 있으며, 중단 상황에서도 복구가 용이합니다. 크롤링은 단순히 데이터를 가져오는 일을 넘어, 데이터 수집과 관리의 기술력을 보여주는 작업입니다. 이제 여러분도 견고한 크롤러를 만들어 보세요! 😊
여러분의 크롤러 설계 팁이나 경험이 있다면 댓글로 공유해주세요!