당근마켓 크롤러 개발기: 셀레니움부터 aiohttp까지
프로젝트 개요
이번 프로젝트는 당근마켓에서 특정 키워드와 관련된 게시글을 체계적으로 수집하는 크롤러를 개발하는 것이었습니다. 단순한 데이터 수집을 넘어서, 안정성과 효율성을 모두 갖춘 프로덕션 레벨의 크롤러를 만드는 것이 목표였습니다.
프로젝트 규모
항목 | 수치 | 비고 |
---|---|---|
크롤링 대상 지역 | 12,000개 | 전국 시/군/구 단위 |
병렬 워커 수 | 3-10개 | 서버 성능에 따라 조정 |
기본 딜레이 | 2.5초 | 봇 탐지 회피 |
예상 소요 시간 | 4시간 | 10워커 기준 |
개발 과정 타임라인
1. 기본 구조 설계
BaseCrawler 추상 클래스와 Selenium, aiohttp 크롤러 구현체 설계
2. 핵심 기능 구현
크롤러 방식 선택, 데이터 수집, 데이터베이스 저장, JSON 내보내기 기능 구현
3. 진행상황 관리
진행상황 저장/복구, 중단/재시작 기능 구현
4. 트러블슈팅
중단 기능, 진행상황 표기, 딜레이 적용 문제 해결, 403 error 대책
5. 최적화 및 완성
성능 최적화, 사용자 경험 개선, 최종 테스트
핵심 기능 설계
1. 크롤러 아키텍처
크롤러는 BaseCrawler 추상 클래스를 기반으로 하여, Selenium과 aiohttp 두 가지 구현체를 제공합니다.
- Selenium: JavaScript 렌더링이 필요한 복잡한 페이지
- aiohttp: 빠른 속도가 필요한 대량 크롤링
두 가지 목적성을 가지고 사용자의 니즈에 맞는 구현체를 선택할 수 있도록 했습니다.
class BaseCrawler(ABC):
"""크롤러 기본 클래스 - 추상화를 통한 확장성 확보"""
def __init__(self, max_workers: int = 1, base_delay: float = 2.5):
# 기본 URL 설정
self.base_url = "https://www.daangn.com/kr/buy-sell/?in="
# 크롤링 설정
self.max_workers = max_workers # 동시 처리 워커 수
self.base_delay = base_delay # 기본 딜레이 시간
# 진행 상황 관리
self.current_location = 0 # 현재 처리 중인 지역 ID
self.total_locations = 0 # 전체 지역 수
# 시간 추적
self.start_time = None # 크롤링 시작 시간
self.end_time = None # 크롤링 종료 시간
self.crawling_duration = None # 총 소요 시간
2. 진행상황 관리 시스템
크롤링 중단 시 진행상황을 자동으로 저장하고, 재시작 시 이전 위치에서 계속할 수 있는 시스템을 구현했습니다. 이는 대용량 크롤링에서 필수적인 기능입니다.
3. 실시간 모니터링
진행률, 수집된 게시글 수, 예상 남은 시간, 처리 속도 등을 실시간으로 표시하여 사용자가 크롤링 상태를 쉽게 확인할 수 있습니다.
주요 트러블슈팅
문제 1: 중단 기능이 작동하지 않음
증상: Ctrl+C를 눌러도 크롤링이 중단되지 않음
원인: 시그널 핸들러가 제대로 등록되지 않음
해결책: 강화된 시그널 핸들러 구현
def _signal_handler(self, signum, frame):
"""강화된 시그널 핸들러 - 안전한 중단 처리"""
print(f"\n[중단 신호 수신] 시그널: {signum}")
print("크롤링을 안전하게 중단하고 진행 상황을 저장합니다...")
# 중단 신호 설정
self.stop_event.set()
# 현재 진행 상황을 정확하게 파악
current_progress = self._get_current_progress()
# 진행 상황 저장 (데이터 손실 방지)
if current_progress['current'] > 0:
self.save_progress(current_progress['current'], current_progress['total'])
print(f"진행 상황 저장 완료: {current_progress['current']:,}/{current_progress['total']:,}")
문제 2: 진행상황이 항상 1/12000으로 표시
증상: 1,100개 지역을 크롤링했는데 진행상황이 1/12000으로 표시
원인: current_location 업데이트 로직 오류
해결책: 정확한 진행상황 추적 시스템 구현
# 중단 시점의 정확한 진행상황 저장
if self.stop_event.is_set():
print(f"\n크롤링이 중단되었습니다.")
# 현재 처리 중이던 지역의 이전 지역까지 완료된 것으로 설정
self.current_location = location_id - 1
self.save_progress(self.current_location, self.total_locations)
print(f"중단 시점 진행상황 저장: {self.current_location:,}/{self.total_locations:,}")
break
문제 3: 딜레이가 적용되지 않음
증상: 딜레이 5초로 설정했는데 로그가 너무 빠르게 출력 (403 error 발생)
원인: 동시성 크롤링에서 딜레이 적용 위치 오류
해결책: 각 지역 처리 전에 딜레이 적용
async def process_location(location_id: int):
"""단일 지역 처리 (세마포어 제한)"""
async with semaphore:
try:
# 각 지역 처리 전에 딜레이 적용 (봇 탐지 회피)
delay = self.get_random_delay()
await asyncio.sleep(delay) # ← 딜레이가 실제로 적용됨
# 실제 크롤링 작업 수행
articles = await self.scrape_location(location_id, keyword)
return articles
except Exception as e:
logger.error(f"지역 {location_id} 처리 실패: {e}")
return []
성능 최적화 결과
최적화 전 vs 최적화 후
항목 | 최적화 전 | 최적화 후 |
---|---|---|
중단 기능 | 작동 안함 | Ctrl+C로 안전한 중단 |
진행상황 | 1/12000으로만 표시 | 정확한 진행상황 표시 (1,100/12,000) |
딜레이 | 적용 안됨 | 딜레이 정확히 적용 |
사용자 경험 | 부족 | 직관적인 UI/UX |
에러 처리 | 미흡 | 강력한 에러 처리 |
크롤링 성능 지표 (10 Worker 기준)
항목 | 수치 |
---|---|
시작 시간 | 2025-08-21 10:00:05 |
종료 시간 | 2025-08-21 10:44:00 |
총 경과 시간 | 00:43:54 |
처리된 지역 | 12,000개 |
처리 속도 | 273.3 지역/분 |
핵심 기술 포인트
1. 비동기 프로그래밍 (asyncio)
aiohttp 크롤러에서 asyncio를 활용하여 동시성 크롤링을 구현했습니다. 세마포어를 사용하여 동시 요청 수를 제한하고, 각 요청에 적절한 딜레이를 적용하여 서버 부하를 최소화했습니다.
2. 진행상황 지속성
JSON 파일을 통한 진행상황 저장으로, 크롤링이 중단되어도 이전 위치에서 재시작할 수 있습니다. 이는 대용량 크롤링에서 필수적인 기능입니다.
403 error의 경우에도 크롤러가 작동하는 문제로 중단 시점을 기준으로 진행상황을 관리했습니다.
3. 적응형 딜레이 시스템
기본 딜레이를 기준으로 랜덤한 범위를 설정하여, 봇 탐지를 우회하면서도 효율적인 크롤링이 가능하도록 구현했습니다.
4. 강력한 에러 처리
- 403 Forbidden 에러 감지 시 자동 중단
- 네트워크 오류 시 재시도
- 데이터베이스 오류 시 롤백
다양한 에러 상황에 대응하도록 예외처리를 구현했습니다.
향후 개선 방향
1. 분산 크롤링
여러 서버에 크롤러를 분산 배치하여 더 빠른 데이터 수집이 가능합니다. Redis나 RabbitMQ를 활용한 작업 큐 시스템을 구축할 예정입니다.
2. 머신러닝 기반 최적화
서버 응답 시간, 에러 발생 패턴 등을 분석하여 최적의 딜레이와 워커 수를 자동으로 조정하는 시스템을 개발할 예정입니다.
스마트 딜레이 개념을 도입해보았지만, 응답 이후 적용하는 방식으로 즉각적인 대응이 어려웠습니다.
3. 실시간 모니터링 대시보드
웹 기반 대시보드를 통해 크롤링 상태를 실시간으로 모니터링하고, 알림 기능을 추가할 예정입니다!
개발 후기
이번 프로젝트를 통해 단순한 크롤러를 넘어서, 프로덕션 환경에서 안정적으로 동작하는 시스템을 만드는 것의 중요성을 깨달았습니다. 특히 진행상황 관리, 에러 처리, 사용자 경험 등이 얼마나 중요한지 실감할 수 있었고, 빠른 성능과 안정성 두 가지 목표를 나름 실현시킨 것 같아 의미있었습니다.
또한 비동기 프로그래밍의 복잡성과 동시성 제어의 어려움도 체험할 수 있었습니다. 단순히 빠른 속도를 위해 만드는 것이 아니라, 안정적이고 사용자 친화적인 시스템을 만드는 것이 진정한 개발의 재미가 아닐까 싶은 마음도 들었습니다.
핵심 교훈: 고도화된 서비스를 개발할 때 기획단계가 단단해야 속도감 있게 진행할 수 있다는 점을 배웠습니다.