요청 vs. HTTPX vs. AIOHTTP: 상세 비교

Python에서 가장 널리 사용되는 HTTP 클라이언트인 Requests, HTTPX, AIOHTTP를 살펴보고 데이터 수집 요구사항에 적합한 도구를 선택하세요.
4 분 읽기
Requests vs. HTTPX vs. AIOHTTP blog image

파이썬에는 다양한 HTTP 클라이언트가 존재합니다. HTTP(Hypertext Transfer Protocol)에 익숙하지 않은 분들을 위해 설명하자면, 이는 웹 전체의 기반이 되는 프레임워크입니다.

오늘은 파이썬에서 가장 널리 쓰이는 세 가지 HTTP 클라이언트인 Requests, HTTPX, AIOHTTP를 비교해 보겠습니다. 다른 클라이언트에 대해 알고 싶다면 여기를 참고하세요.

간략한 개요

Requests는 파이썬의 표준 HTTP 클라이언트입니다. 사용 편의성을 위해 차단형 및 동기식 작업을 활용합니다. HTTPX는 속도와 사용 편의성을 모두 고려해 설계된 새로운 비동기 클라이언트입니다. AIOHTTP는 10년 이상 사용되어 온 클라이언트로, 파이썬이 제공하는 최초이자 가장 잘 지원되는 비동기 HTTP 클라이언트 중 하나입니다.

기능 Requests HTTPX AIOHTTP
구조 동기/차단 비동기/비차단 비동기/비차단
세션
동시성 아니요
HTTP/2 지원 아니요
성능 낮음 높음 높음
재시도 자동 자동 수동
타임아웃 지원 요청당 전체 지원 전체 지원
프록시 지원
사용 편의성 쉬움 어려움 어려움
사용 사례 간단한 프로젝트/프로토타이핑 고성능 고성능

Python Requests

Python Requests는 매우 직관적이고 사용하기 쉽습니다. 최첨단 성능이 필요하지 않다면, Python에서 HTTP 요청을 수행하기 위한 최적의 선택입니다. 널리 사용되고 이해하기 쉬우며, 세계에서 가장 잘 문서화된 Python HTTP 클라이언트입니다.

아래 명령어로 설치할 수 있습니다.

설치

pip install requests

Requests는 표준 HTTP 프로토콜을 지원하며 일부 세션 관리 기능도 제공합니다. 세션을 사용하면 서버에 대한 지속적 연결을 생성할 수 있습니다. 이를 통해 단일 요청 시보다 훨씬 빠르고 효율적으로 데이터를 가져올 수 있습니다. 기본적인 요청(GET, POST, PUT, DELETE)만 수행하고 고성능이 중요하지 않다면 Requests 라이브러리로 모든 HTTP 요구사항을 충족할 수 있습니다.

웹 전체에서 프록시 통합, 사용자 에이전트 및 기타 모든 종류의 것에 대한 가이드를 찾을 수 있습니다.

아래에서 기본 사용 예시를 확인할 수 있습니다.

import requests

# 간단한 요청 수행
response = requests.get("https://jsonplaceholder.typicode.com/posts")
print(response.status_code)

# 동일 서버에 대한 다중 요청을 위해 세션 사용
with requests.Session() as client:
    for get_request in range(1000):
        response = client.get("https://jsonplaceholder.typicode.com/posts")
        print(response.status_code)

비동기 작업에서는 Requests의 한계가 드러납니다. 비동기 지원이 있다면 일괄 요청을 한 번에 수행한 후 모든 결과를 기다릴 수 있습니다. 반면 동기식 요청에서는 한 번에 하나의 요청만 가능하며, 서버 응답이 돌아올 때까지 기다려야 다음 요청을 할 수 있습니다. 프로그램에서 많은 HTTP 요청이 필요한 경우 Requests 사용은 코드에 본질적인 제약을 가져옵니다.

HTTPX

HTTPX는 이 세 라이브러리 중 가장 최신이자 현대적인 라이브러리입니다. 기본적으로 비동기 작업을 완벽하게 지원합니다. 그럼에도 불구하고 여전히 사용자 친화적이고 직관적인 구문을 제공합니다. Requests로는 부족하고 학습 곡선 없이 성능을 개선해야 한다면 HTTPX를 사용하세요.

asyncio (비동기 입출력) 사용하면 await 키워드로 비동기 응답을 완전히 활용하는 코드를 작성할 수 있습니다. 이를 통해 작업이 완료될 때까지 기다리는 동안 코드의 다른 부분을 차단하지 않고 작업을 계속 진행할 수 있습니다. 이러한 비동기 작업을 통해 대량의 요청을 한 번에 처리할 수 있습니다 . 한 번에 하나의 요청을 처리하는 대신 5개, 50개, 심지어 100개까지 동시에 요청할 수 있습니다!

설치

pip install httpx

HTTPX 시작을 위한 몇 가지 예시입니다.

import httpx
import asyncio

# 동기식 응답
response = httpx.get("https://jsonplaceholder.typicode.com/posts")
print(response.status_code)

# 기본 비동기 세션 사용법
async def main():
    async with httpx.AsyncClient() as client:
        for get_request in range(1000):
            response = await client.get("https://jsonplaceholder.typicode.com/posts")
            print(response.status_code)
asyncio.run(main())

HTTPX는 새로운 코드를 작성할 때 탁월한 선택이지만, 자체적인 한계점도 존재합니다. 구문 특성상 기존 Requests 코드베이스를 HTTPX로 교체하는 작업은 어려울 수 있습니다. 비동기 코드 작성에는 어느 정도의 보일러플레이트 코드가 필요하며, 수천 건의 요청을 처리하지 않는 한 개발에 추가 시간을 투자할 가치가 없는 경우가 많습니다.

AIOHTTP와 비교했을 때(곧 알게 되겠지만) HTTPX는 속도가 다소 느립니다. 웹 서버나 복잡한 네트워크를 구축하려는 경우, HTTPX의 생태계가 아직 성숙하지 않아 적합하지 않습니다. HTTPX는 HTTP/2 같은 최신 기능을 갖춘 새로운 클라이언트 측 애플리케이션에 가장 적합합니다.

AIOHTTP

비동기 프로그래밍 분야에서 AIOHTTP는 오랫동안 파이썬에서 널리 사용되어 왔습니다. 서버 운영, 클라이언트 측 애플리케이션, 분산 네트워크 등 어떤 용도든 AIOHTTP로 해결할 수 있습니다. 다만 Requests, HTTPX, AIOHTTP 세 가지 중 AIOHTTP의 학습 곡선이 가장 가파릅니다.

우리는 간단한 클라이언트 측 사용에 초점을 맞추고 있으므로 AIOHTTP의 복잡한 부분까지 깊이 파고들지는 않을 것입니다. HTTPX와 마찬가지로 AIOHTTP로도 요청을 일괄 처리할 수 있습니다. 그러나 HTTPX와 달리 AIOHTTP는 동기식 요청을 지원하지 않습니다. 한 번에 너무 많은 요청을 보내지 마세요… 서버에서 차단당하고 싶지 않을 테니까요.

설치

pip install aiohttp

아래 기본 사용법을 살펴보세요.

import aiohttp
import asyncio

# 단일 요청 생성
async def main():
    async with aiohttp.ClientSession() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/posts")
        print(response)
        
asyncio.run(main())


# 기본 비동기 세션 사용법
async def main():
    with aiohttp.ClientSession() as client:
        for response in range(1000):
            response = await client.get("https://jsonplaceholder.typicode.com/posts")
            print(response)
            
asyncio.run(main())

AIOHTTP는 엄격히 비동기적입니다. 위 코드에서 볼 수 있듯이, 단일 요청 예제는 처음 두 예제보다 훨씬 많은 코드가 필요합니다. 요청 횟수와 무관하게 비동기 세션을 설정해야 하므로, AIOHTTP와 프록시를 결합하는 것이 권장됩니다. 단일 요청에는 분명히 과잉 설계입니다.

AIOHTTP는 많은 장점을 지녔지만, 동시에 엄청난 양의 인바운드 및 아웃바운드 요청을 처리하는 경우가 아니라면 Python Requests를 완전히 대체하지는 못할 것입니다. 그런 경우라면 성능이 크게 향상될 것입니다. 복잡한 애플리케이션을 구축하고 번개처럼 빠른 통신이 필요한 경우 AIOHTTP를 사용하세요. 이 라이브러리는 서버, 분산 네트워크, 그리고 매우 복잡한 웹 스크래핑 애플리케이션에 가장 적합합니다.

성능 비교

이제 각 라이브러리를 사용해 간단한 프로그램을 구축해 보겠습니다. 요구사항은 간단합니다: 클라이언트 세션을 열고 1000개의 API 요청을 수행합니다.

먼저 서버와 세션을 생성합니다. 다음으로 1000개의 요청을 수행합니다. 두 개의 배열을 사용합니다: 하나는 성공 응답용, 다른 하나는 실패 응답용입니다. 실행이 완료되면 성공 요청과 실패 요청의 전체 개수를 출력합니다. 실패 요청이 발생하면 해당 상태 코드를 콘솔에 출력합니다.

비동기 예제(HTTPX, AIOHTTP)에서는 chunkify() 함수를 사용합니다. 이 함수는 배열을 청크(chunk)로 분할하는 데 사용됩니다. 그런 다음 요청을 배치(batch) 단위로 수행합니다. 예를 들어, 요청을 50개씩 배치로 수행하려면 chunkify()를 사용하여 배치를 생성하고, process_chunk()를 사용하여 50개 요청을 한 번에 수행합니다.

아래 함수들을 살펴보세요.

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk
        
        
async def process_chunk(client, urls, retries=3):
    tasks = [fetch(client, url, retries) for url in urls]
    return await asyncio.gather(*tasks)

Requests

다음은 Requests를 사용한 코드입니다. 나중에 사용할 두 개의 비동기 예제와 비교하면 매우 간단합니다. 세션을 열고 for 루프를 사용하여 요청을 반복합니다.

import requests
import json
from datetime import datetime

start_time = datetime.now()
good_responses = []
bad_responses = []

with requests.Session() as client:

    for get_request in range(1000):
        response = client.get("https://jsonplaceholder.typicode.com/posts")
        status_code = response.status_code
        if status_code == 200:
            good_responses.append(status_code)
        else:
            bad_responses.append(status_code)

end_time = datetime.now()

print("----------------요청------------------")
print(f"소요 시간: {end_time - start_time}")
print(f"정상 응답: {len(good_responses)}")
print(f"오류 응답: {len(bad_responses)}")

for status_code in set(bad_responses):
    print(status_code)
Python requests results

Requests는 작업을 51초 조금 넘게 완료했습니다. 이는 초당 약 20개의 요청에 해당합니다. 세션을 사용하지 않으면 요청당 2초가 소요될 것으로 예상됩니다. 상당히 성능이 좋습니다.

HTTPX

다음은 HTTPX 코드입니다. 앞서 언급한 chunkify()process_chunk() 를 활용합니다.

import httpx
import asyncio
from datetime import datetime
from itertools import islice

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk

async def fetch(client, url, retries=3):
    """재시도 기능을 사용하여 URL을 가져옵니다."""
    for attempt in range(retries):
        try:
            response = await client.get(url)
            return response.status_code
        except httpx.RequestError as e:
            if attempt < retries - 1:
                await asyncio.sleep(1)
            else:
                return f"오류: {e}"

async def process_chunk(client, urls, retries=3):
    tasks = [fetch(client, url, retries) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    url = "https://jsonplaceholder.typicode.com/posts"
    total_requests = 1000
    chunk_size = 50

    good_responses = []
    bad_responses = []

    async with httpx.AsyncClient(timeout=10) as client:
        start_time = datetime.now()

        urls = [url] * total_requests
        for chunk in chunkify(urls, chunk_size):
            results = await process_chunk(client, chunk)
            for status in results:
                if isinstance(status, int) and status == 200:
                    good_responses.append(status)
                else:
                    bad_responses.append(status)

        end_time = datetime.now()

        print("----------------HTTPX------------------")
        print(f"소요 시간: {end_time - start_time}")
        print(f"정상 응답: {len(good_responses)}")
        print(f"오류 응답: {len(bad_responses)}")

        if bad_responses:
            print("오류 상태 코드 또는 오류:")
            for error in set(bad_responses):
                print(error)

asyncio.run(main())

HTTPX 사용 시 출력 결과입니다. Requests와 비교하면 놀라운 수준입니다. 총 소요 시간은 7초 조금 넘었습니다. 이는 초당 139.47 요청에 해당합니다. HTTPX는 Requests 대비 약 7배의 성능을 보여주었습니다.

HTTPX results

AIOHTTP

이제 AIOHTTP를 사용해 동일한 작업을 수행해 보겠습니다. HTTPX 예제에서 사용한 기본 구조를 그대로 따릅니다. 유일한 주요 차이점은 AIOHTTP 클라이언트가 HTTPX 클라이언트를 대체하는 부분입니다.

import aiohttp
import asyncio
from datetime import datetime
from itertools import islice

def chunkify(iterable, size):
    iterator = iter(iterable)
    while chunk := list(islice(iterator, size)):
        yield chunk

async def fetch(session, url, retries=3):
    for attempt in range(retries):
        try:
            async with session.get(url) as response:
                return response.status
        except aiohttp.ClientError as e:
            if attempt < retries - 1:
                await asyncio.sleep(1)
            else:
                return f"Error: {e}"

async def process_chunk(session, urls):
    tasks = [fetch(session, url) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    url = "https://jsonplaceholder.typicode.com/posts"
    total_requests = 1000
    chunk_size = 50

    good_responses = []
    bad_responses = []

    async with aiohttp.ClientSession() as session:
        start_time = datetime.now()

        urls = [url] * total_requests
        for chunk in chunkify(urls, chunk_size):
            results = await process_chunk(session, chunk)
            for status in results:
                if isinstance(status, int) and status == 200:
                    good_responses.append(status)
                else:
                    bad_responses.append(status)

        end_time = datetime.now()

        print("----------------AIOHTTP------------------")
        print(f"소요 시간: {end_time - start_time}")
        print(f"정상 응답: {len(good_responses)}")
        print(f"오류 응답: {len(bad_responses)}")

        if bad_responses:
            print("잘못된 상태 코드 또는 오류:")
            for error in set(bad_responses):
                print(error)

asyncio.run(main())

AIOHTTP는 4초 조금 넘게 걸리는 번개 같은 속도를 보였습니다. 이 HTTP 클라이언트는 초당 241건 이상의 요청을 처리했습니다! AIOHTTP는 Requests보다 약 10배 빠르며 HTTPX보다 거의 50% 더 빠릅니다. Python에서 성능 면에서 AIOHTTP는 독보적인 위치를 차지합니다.

AIOHTTP results

Bright Data 제품이 제공하는 지원

Bright Data는 웹 스크래핑, API 요청, 고성능 통합과 같은 데이터 집약적 작업에 특히 유용한 다양한 솔루션을 제공하여 HTTP 클라이언트 기반 워크플로우를 강화할 수 있습니다. 각 제품의 활용 방법은 다음과 같습니다:

  • 레지던셜 프록시
  • 웹 스크레이퍼 API
  • 레디메이드 데이터셋
  • 웹 언락커
  • SERP API

Bright Data의 도구를 Python HTTP 클라이언트와 통합하면 웹 스크래핑 및 데이터 수집의 일반적인 문제를 극복하면서 데이터 수집을 단순화하는 강력하고 고성능의 시스템을 구축할 수 있습니다.

결론

HTTP 클라이언트 세계에서 Requests가 표준인 이유는 사용 편의성 때문입니다. Requests와 비교하면 HTTPX는 마차에서 현대식 자동차로 옮겨 타는 것과 같습니다. 고성능과 사용 편의성 사이의 균형을 제공합니다. AIOHTTP는 로켓선과 같습니다. 꼭 필요한 경우가 아니면 사용하지 않겠지만, 여전히 가장 빠른 HTTP 클라이언트입니다.

지금 바로 Bright Data에 가입하여 강력한 데이터 솔루션을 활용하고 비즈니스에 필요한 경쟁 우위를 확보하세요. 모든 제품에 무료 체험판이 제공됩니다!