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 바운드 작업에서는 확실한 성능 개선을 보였다.