Python 3.7 asyncio 기반 크롤러 성능 개선
문제 상황
레거시 크롤러가 requests 라이브러리로 순차 처리하다보니 1000개 URL 크롤링에 20분 이상 소요되었다. I/O 대기 시간이 대부분이라 비동기 처리로 개선할 여지가 있었다.
asyncio + aiohttp 도입
Python 3.7부터 asyncio가 안정화되면서 본격적으로 프로덕션에 도입할 수 있게 되었다.
import asyncio
import aiohttp
from typing import List
async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict:
async with session.get(url) as response:
return {
'url': url,
'status': response.status,
'content': await response.text()
}
async def crawl_urls(urls: List[str]) -> List[dict]:
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
# 실행
results = asyncio.run(crawl_urls(url_list))
결과
- 처리 시간: 20분 → 4분 (5배 개선)
- 동시 요청 수를 semaphore로 제한해 서버 부담 조절
- 예외 처리를 return_exceptions=True로 일괄 관리
주의사항
- TCP 커넥터 풀 크기 조정 필요 (기본 100)
- timeout 설정 필수 (ClientTimeout 사용)
- 메모리 사용량 증가하므로 배치 크기 조절
비동기 처리가 만능은 아니지만, I/O 바운드 작업에서는 확실한 성능 개선을 보였다.