---
title: "مقایسه HTTPX، Requests و AIOHTTP"
description: "Requests ساده و همگام است، AIOHTTP ناهمگام است و HTTPX هر دو حالت را دارد. استفاده از پروکسی، کارایی و انتخاب مناسب برای هر پروژه را با کد نشان می‌دهیم."
url: https://proxynet.io/fa/blog/httpx-vs-requests-vs-aiohttp
date: 2026-09-13
author: "Acar Diveroli"
category: "مقایسه, وب اسکرپینگ"
lang: fa
---

# مقایسه HTTPX، Requests و AIOHTTP

برای ارسال درخواست HTTP در Python سه کتابخانه برجسته‌اند: **Requests**، **HTTPX** و **AIOHTTP**. هر سه کار اصلی یکسانی انجام می‌دهند، اما تفاوت آن‌ها هنگام نوشتن یک اسکریپت کوچک دیده نمی‌شود؛ این تفاوت وقتی آشکار می‌شود که باید هزاران درخواست را هم‌زمان مدیریت کنید. در این نوشته سه کتابخانه را از نظر سادگی استفاده، هم‌زمانی، پشتیبانی از پروکسی، مدیریت خطا و اکوسیستم مقایسه می‌کنیم، برای هرکدام نمونه‌ای کارا می‌آوریم و مسیر گذر از Requests به کد ناهمگام را نشان می‌دهیم.

کدهای نمونه با Python 3.13 و نسخه‌های Requests 2.32، HTTPX 0.28 و AIOHTTP 3.13 آزموده شده‌اند.

> **نکته: پاسخ کوتاه**
>
> برای اسکریپت‌های کوچک و یکپارچه‌سازی با API از Requests استفاده کنید؛ برای پروژه‌هایی که امروز همگام شروع می‌شوند و ممکن است فردا ناهمگام شوند و نیز برای `HTTP/2` از HTTPX؛ و برای کارهای بسیار پرحجمی که از ابتدا ناهمگام طراحی شده‌اند از AIOHTTP. هر سه از پروکسی HTTP به‌صورت درون‌ساخت پشتیبانی می‌کنند.

## سه کتابخانه به اختصار

- **Requests:** رایج‌ترین کتابخانه HTTP در Python. به‌صورت همگام کار می‌کند، یادگیری آن بسیار آسان است و مستندات و نمونه‌هایش همه‌جا پیدا می‌شود. به یاد داشته باشید که برنامه تا پایان هر درخواست روی همان خط منتظر می‌ماند.
- **HTTPX:** رابطی بسیار شبیه به Requests را هم به‌صورت همگام و هم ناهمگام ارائه می‌دهد. از `HTTP/2` نیز پشتیبانی می‌کند. کم‌زحمت‌ترین راه برای انتقال کد موجود Requests به حالت ناهمگام معمولاً HTTPX است.
- **AIOHTTP:** کتابخانه‌ای که از ابتدا ناهمگام طراحی شده است. علاوه بر کلاینت، یک وب‌سرور هم دارد. برای کارهایی که هم‌زمانی بسیار بالا می‌خواهند انتخابی بالغ و پرکاربرد است.

## همگام و ناهمگام یعنی چه؟

چون این مفهوم در مرکز مقایسه قرار دارد، کوتاه توضیحش می‌دهیم. کلاینت **همگام** درخواست را می‌فرستد و تا رسیدن پاسخ منتظر می‌ماند؛ در این مدت برنامه هیچ کار دیگری انجام نمی‌دهد. کلاینت **ناهمگام** اما پس از ارسال درخواست، کنترل را به حلقه رویداد می‌سپارد؛ تا پاسخ برسد، درخواست‌های دیگر هم می‌توانند راه بیفتند.

بیشتر زمان یک درخواست وب در شبکه سپری می‌شود، یعنی در انتظار پاسخ سرور مقصد. در کد همگام این انتظار هدر می‌رود؛ در کد ناهمگام در همان مدت می‌توان منتظر ده‌ها درخواست دیگر ماند. این تفاوت هنگام دریافت صد صفحه احساس نمی‌شود؛ هنگام دریافت ده هزار صفحه به تفاوت میان ساعت‌ها و دقیقه‌ها تبدیل می‌شود.

بهای این برتری پیچیدگی است: نحو `async`/`await`، حلقه رویداد و ناسازگاری با کتابخانه‌هایی که ناهمگام نیستند. به همین دلیل گفتن اینکه «ناهمگام همیشه بهتر است» نادرست است؛ اگر نیازی نباشد، کد همگام خواناتر است و نگهداری آن آسان‌تر.

## تفاوت‌های اصلی در یک جدول

| ویژگی | Requests | HTTPX | AIOHTTP |
|---|---|---|---|
| استفاده همگام | دارد | دارد | ندارد |
| استفاده ناهمگام | ندارد | دارد | دارد |
| `HTTP/2` | ندارد | دارد (با بسته اضافه) | ندارد |
| پروکسی HTTP و HTTPS | درون‌ساخت | درون‌ساخت | درون‌ساخت |
| پروکسی SOCKS | با `requests[socks]` | با `httpx[socks]` | با بسته بیرونی |
| مهلت پیش‌فرض | ندارد (بی‌پایان منتظر می‌ماند) | 5 ثانیه | 5 دقیقه (کل) |
| دنبال کردن تغییرمسیر | به‌طور پیش‌فرض فعال | به‌طور پیش‌فرض غیرفعال | به‌طور پیش‌فرض فعال |
| استفاده دوباره از اتصال | با `Session` | با `Client` | با `ClientSession` |
| منحنی یادگیری | بسیار آسان | آسان | متوسط |
| مناسب‌ترین کار | اسکریپت ساده، حجم کم | پروژه‌های ترکیبی همگام و ناهمگام | هم‌زمانی بالا |

ردیف «مهلت پیش‌فرض» در جدول، تفاوتی است که میان این سه کتابخانه بیش از همه نادیده گرفته می‌شود. اگر در Requests مهلت تعیین نکنید، درخواست ممکن است تا ابد منتظر بماند؛ به همین دلیل افزودن `timeout=` به هر فراخوانی Requests باید به عادت تبدیل شود. تفاوت `ConnectTimeout` و `ReadTimeout` و شکل هر کدام در پیام خطا را در نوشته [خطای Max Retries Exceeded With URL](/fa/blog/max-retries-exceeded-with-url) نشان داده‌ایم.

## Requests: ساده و همگام

نصب:

```bash
pip install requests
```

یک درخواست از طریق پروکسی:

```python
import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"
proxies = {"http": PROXY, "https": PROXY}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=20)
print(response.status_code, response.json())
```

کلید `https` در دیکشنری `proxies` پروکسی‌ای است که هنگام رفتن به نشانی‌های HTTPS به کار می‌رود. اینکه مقدار آن با `http://` شروع می‌شود درست است: ترافیک HTTPS درون اتصالی که با پروکسی برقرار شده تونل می‌شود.

اگر چند درخواست می‌فرستید از `Session` استفاده کنید؛ اتصال‌ها دوباره به کار می‌روند، کوکی‌ها حفظ می‌شوند و پروکسی فقط یک بار تنظیم می‌شود:

```python
import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"

with requests.Session() as session:
    session.proxies.update({"http": PROXY, "https": PROXY})
    session.headers.update({"User-Agent": "data-collector-bot/1.0"})
    for path in ["ip", "headers", "user-agent"]:
        r = session.get(f"https://httpbin.org/{path}", timeout=20)
        print(path, r.status_code)
```

محدودیت Requests هم‌زمانی است. اگر 1,000 صفحه را پشت سر هم دریافت کنید، زمان کل به مجموع زمان تک‌تک درخواست‌ها نزدیک می‌شود. می‌توان این مشکل را با `concurrent.futures.ThreadPoolExecutor` کاهش داد، اما در حجم‌های بسیار بالا یک کتابخانه ناهمگام کارآمدتر است. نمونه‌های این بخش فقط درخواست GET می‌فرستند؛ نوشتن درخواست POST با بدنه JSON، پارامترهای کوئری با `params=` و هدرها در Requests را در مقاله [ارسال JSON با POST در Python Requests: معادل‌های cURL](/fa/blog/python-requests-post-json) توضیح داده‌ایم.

## HTTPX: میان دو دنیا

نصب:

```bash
pip install httpx
```

استفاده همگام تقریباً همانند Requests است:

```python
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"

with httpx.Client(proxy=PROXY, timeout=20) as client:
    response = client.get("https://httpbin.org/ip")
    print(response.json())
```

> **نکته**
>
> در نسخه 0.28 کتابخانه HTTPX پارامتر قدیمی `proxies=` حذف شد. برای یک پروکسی از `proxy=` استفاده کنید؛ اگر برای نشانی‌های مختلف پروکسی متفاوت لازم دارید، با `mounts=` انتقال‌دهنده تعریف کنید. به همین دلیل بسیاری از نمونه‌های قدیمی اینترنت خطا می‌دهند.

نسخه ناهمگام همین کد، چند درخواست را هم‌زمان می‌فرستد:

```python
import asyncio
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"
URLS = ["https://httpbin.org/ip"] * 10

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        responses = await asyncio.gather(*(client.get(url) for url in URLS))
        print([r.status_code for r in responses])

asyncio.run(main())
```

در اینجا 10 درخواست پشت سر هم نمی‌روند، بلکه هم‌زمان راه می‌افتند. زمان کل به زمان کندترین درخواست نزدیک می‌شود.

برای استفاده از `HTTP/2` کافی است `pip install "httpx[http2]"` را نصب کنید و `http2=True` را به کلاینت بدهید. هنگام ارسال درخواست‌های زیاد به یک سرور، چون چندگانه‌سازی روی یک اتصال انجام می‌شود، هزینه برقراری اتصال کاهش می‌یابد. هنگام استفاده از پروکسی، `HTTP/2` درون تونل `CONNECT` میان شما و سایت مقصد کار می‌کند؛ لازم نیست پروکسی از `HTTP/2` پشتیبانی کند.

## AIOHTTP: برای هم‌زمانی بالا

نصب:

```bash
pip install aiohttp
```

```python
import asyncio
import aiohttp

PROXY = "http://pr.proxynet.io:8000"
AUTH = aiohttp.BasicAuth("user", "pass")
URLS = ["https://httpbin.org/ip"] * 10

async def fetch(session, url):
    async with session.get(url, proxy=PROXY, proxy_auth=AUTH) as response:
        return response.status

async def main():
    async with aiohttp.ClientSession() as session:
        statuses = await asyncio.gather(*(fetch(session, url) for url in URLS))
        print(statuses)

asyncio.run(main())
```

در AIOHTTP پروکسی به‌جای نشست، در هر درخواست با پارامتر `proxy=` داده می‌شود. اطلاعات ورود را می‌توانید جداگانه با `proxy_auth` بدهید یا درون نشانی (`http://user:pass@...`) بنویسید. با این طراحی، دادن پروکسی متفاوت در هر درخواست به‌طور طبیعی ممکن است؛ اگر می‌خواهید فهرست IP خودتان را بچرخانید، AIOHTTP این کار را آسان می‌کند.

تفاوت دیگر AIOHTTP این است که بدنه پاسخ باید درون مدیر زمینه خوانده شود. فراخوانی `await response.text()` پس از خروج از بلوک `async with` خطا می‌دهد؛ بدنه را درون بلوک بخوانید و در یک متغیر نگه دارید.

## محدود کردن هم‌زمانی

هنگام کار ناهمگام، هم‌زمانی را نامحدود رها نکنید. شروع یکباره ده هزار URL با `asyncio.gather` هم سقف توصیفگر فایل سیستم خودتان را تحت فشار می‌گذارد و هم سایت مقصد را فوراً به اعمال محدودیت نرخ وامی‌دارد. محدود کردن تعداد درخواست‌های باز هم‌زمان با `asyncio.Semaphore` از هر دو طرف محافظت می‌کند:

```python
import asyncio
import httpx

PROXY = "http://user:pass@pr.proxynet.io:8000"
URLS = [f"https://httpbin.org/get?i={i}" for i in range(100)]
LIMIT = asyncio.Semaphore(10)

async def fetch(client, url):
    async with LIMIT:
        r = await client.get(url)
        return r.status_code

async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        results = await asyncio.gather(*(fetch(client, u) for u in URLS))
        print(sum(1 for s in results if s == 200), "successful")

asyncio.run(main())
```

در این نمونه صد درخواست در صف قرار می‌گیرند، اما هم‌زمان حداکثر ده درخواست باز است. سقف را متناسب با تحمل سایت مقصد و تعداد اتصال هم‌زمانی که بسته پروکسی شما مجاز می‌داند تنظیم کنید.

## مدیریت خطا و تلاش دوباره

در هر سه کتابخانه خطاهای شبکه به شکل استثنا می‌رسند؛ اما کدهای خطای HTTP (404، 429، 500) به‌طور پیش‌فرض استثنا **نیستند**. بررسی پاسخ بر عهده شماست:

- در Requests و HTTPX فراخوانی `response.raise_for_status()` برای پاسخ‌های 4xx و 5xx استثنا ایجاد می‌کند.
- در AIOHTTP همین کار با `response.raise_for_status()` یا با `raise_for_status=True` هنگام باز کردن نشست انجام می‌شود.

کدهایی که هنگام کار با پروکسی بیش از همه می‌بینید این‌ها هستند:

| کد | معنا | چه باید کرد؟ |
|---|---|---|
| 407 | احراز هویت پروکسی ناموفق بود | نام کاربری، رمز عبور و کدگذاری نویسه‌های ویژه را بررسی کنید |
| 429 | سایت مقصد محدودیت نرخ اعمال می‌کند | هم‌زمانی را کم کنید، صبر کنید و دوباره تلاش کنید، توزیع IP را بیشتر کنید |
| 403 | سایت مقصد درخواست را رد می‌کند | User-Agent و نوع IP را بازبینی کنید |
| 502 / 504 | پروکسی به مقصد نرسید | با IP خروجی دیگری دوباره تلاش کنید |

منطق تلاش دوباره را با انتظار نمایی بنویسید: در تلاش اول یک ثانیه، سپس دو، سپس چهار. پافشاری در فاصله‌های ثابت ممکن است به مسدود شدن کامل IP‌ای بینجامد که 429 گرفته است.

## کارایی: کدام سریع‌تر است؟

در انتخاب کتابخانه، پرسش «کدام سریع‌تر است» معمولاً به جای اشتباهی نگاه می‌کند. بیشتر زمان یک درخواست وب در شبکه سپری می‌شود؛ زمان پردازش خود کتابخانه در کنار آن ناچیز است.

عامل تعیین‌کننده **تعداد درخواست‌هایی است که می‌توانید هم‌زمان منتظرشان بمانید**:

- کلاینت همگام منتظر هر درخواست به نوبت می‌ماند.
- کلاینت ناهمگام می‌تواند هم‌زمان منتظر صدها درخواست بماند.

یعنی برای کاری با 10 صفحه، Requests کافی است. برای کاری با 10,000 صفحه، کلاینت ناهمگام HTTPX یا AIOHTTP زمان را به‌طور چشمگیری کوتاه می‌کند. در یک دسته (میان HTTPX ناهمگام و AIOHTTP) تفاوت‌های قابل اندازه‌گیری وجود دارد و AIOHTTP در توان خام معمولاً جلوتر است؛ اما در بیشتر پروژه‌ها این تفاوت زیر سایه زمان پاسخ سایت مقصد و تأخیر پروکسی قرار می‌گیرد.

## پروکسی و هم‌زمانی را چگونه با هم در نظر بگیریم؟

کتابخانه‌های ناهمگام می‌توانند درخواست‌های زیادی را هم‌زمان بفرستند؛ اما اگر همه این درخواست‌ها از یک IP خارج شوند، سایت مقصد به‌زودی محدودیت نرخ اعمال می‌کند. با افزایش هم‌زمانی، باید به توزیع IP هم فکر کنید:

- **IP متفاوت در هر درخواست:** با [پروکسی چرخشی](https://proxynet.io/fa/rotating-proxy) از یک نشانی ورودی استفاده می‌کنید و در هر درخواست IP خروجی متفاوتی می‌گیرید. نیازی به تغییر کد نیست؛ همه نمونه‌های بالا همان‌طور که هستند کار می‌کنند.
- **IP ثابت در طول نشست:** در جریان‌هایی که وضعیت نگه می‌دارند، مانند ورود به حساب یا سبد خرید، [پروکسی با نشست ثابت](https://proxynet.io/fa/sticky-proxy) همان IP را برای مدت معینی حفظ می‌کند.
- **چرخاندن فهرست خودتان:** اگر فهرست ثابتی از IP دارید، می‌توانید چرخش را در کد انجام دهید؛ برای نمونه گام‌به‌گام نوشته [چرخاندن پروکسی‌ها در Python](/fa/blog/how-to-rotate-proxies-in-python) را ببینید.
- **نوع IP:** در مقصدهای محافظت‌شده، نوع IP پیش از هم‌زمانی تعیین‌کننده است؛ دلیلش را در نوشته [تفاوت پروکسی مسکونی و دیتاسنتر](/fa/blog/residential-vs-datacenter-proxy) توضیح داده‌ایم.

## گذر از Requests به HTTPX

اگر می‌خواهید یک پروژه موجود Requests را به حالت ناهمگام منتقل کنید، HTTPX کوتاه‌ترین راه است. تفاوت‌هایی که در این گذر باید در نظر بگیرید:

| Requests | HTTPX | یادداشت |
|---|---|---|
| `requests.get(url)` | `httpx.get(url)` | یکسان |
| `proxies={"http": p, "https": p}` | `proxy=p` | یک پارامتر |
| پیش‌فرض `timeout=None` | پیش‌فرض `timeout=5` | در HTTPX مهلت فعال است |
| تغییرمسیر به‌طور پیش‌فرض دنبال می‌شود | `follow_redirects=True` لازم است | HTTPX به‌طور پیش‌فرض دنبال نمی‌کند |
| `Session()` | `Client()` / `AsyncClient()` | منطق یکسان |
| `response.json()` | `response.json()` | یکسان |

بخش بزرگی از کد بدون تغییر کار می‌کند؛ تفاوت‌ها در پنج ردیف بالا جمع شده‌اند. اینکه ابتدا با `Client` همگام منتقل شوید و آزمون‌ها را اجرا کنید و سپس به `AsyncClient` بروید، خطر را کمتر می‌کند.

## کدام را انتخاب کنید؟

- **Requests را انتخاب کنید:** برای اسکریپت‌های کوچک، کارهای خودکارسازی، یکپارچه‌سازی با API و کارهایی که هم‌زمانی در آن‌ها مهم نیست.
- **HTTPX را انتخاب کنید:** اگر امروز همگام شروع می‌کنید و احتمال دارد فردا به ناهمگام بروید، اگر `HTTP/2` لازم دارید یا می‌خواهید هر دو شیوه را در یک پروژه داشته باشید.
- **AIOHTTP را انتخاب کنید:** برای کارهای جمع‌آوری داده بسیار پرحجم که از ابتدا ناهمگام طراحی شده‌اند و برای برنامه‌ای که هم کلاینت و هم سرور لازم دارد.

اگر دریافت صفحه‌ها فقط با HTTP کافی نیست، یعنی محتوا با JavaScript بارگذاری می‌شود، این کتابخانه‌ها به‌تنها کافی نیستند و به خودکارسازی مرورگر نیاز دارید. این تمایز را در نوشته [اسکرپینگ وب: JavaScript یا Python؟](/fa/blog/web-scraping-javascript-vs-python) بررسی کرده‌ایم. برای خودکارسازی مرورگر در Python راهنماهای [Selenium](/fa/blog/selenium) و [پروکسی در SeleniumBase](/fa/blog/how-to-use-proxy-with-seleniumbase) را ببینید.

## پرسش‌های متداول

### گذر از Requests به HTTPX دشوار است؟

معمولاً نه. نوشتن `httpx.get` به‌جای `requests.get` در بیشتر کدهای ساده مستقیم کار می‌کند. تفاوت‌ها در جزئیاتی مانند فعال بودن پیش‌فرض مهلت، دنبال نشدن پیش‌فرض تغییرمسیرها و نام پارامتر پروکسی است. جدول گذر در بالا این تفاوت‌ها را فهرست می‌کند.

### از پروکسی SOCKS5 چگونه استفاده می‌شود؟

برای Requests بسته `pip install "requests[socks]"` و برای HTTPX بسته `pip install "httpx[socks]"` را نصب کنید و در نشانی پروکسی از طرح `socks5://` استفاده کنید. اگر می‌خواهید تفکیک DNS هم در سمت پروکسی انجام شود، در Requests بنویسید `socks5h://`. برای تفاوت‌های پروتکل‌ها نوشته [تفاوت پروکسی SOCKS و HTTP](/fa/blog/socks-vs-http-proxy) را ببینید.

### کد ناهمگام همیشه بهتر است؟

نه. کد ناهمگام پیچیده‌تر است و اشکال‌زدایی آن دشوارتر. برای کاری که واقعاً از هم‌زمانی سود نمی‌برد، کد همگام خواناتر است و نگهداری آن آسان‌تر.

### استفاده از thread با Requests جایگزینی برای ناهمگام است؟

برای حجم‌های متوسط بله. با `ThreadPoolExecutor` می‌توان ده‌ها درخواست را موازی اجرا کرد و کد همگام می‌ماند. در صدها درخواست هم‌زمان، هزینه حافظه threadها بالا می‌رود؛ در آن نقطه کلاینت ناهمگام کارآمدتر است.

### باید اطلاعات ورود پروکسی را در کد بنویسم؟

نه. هر سه کتابخانه متغیرهای محیطی `HTTP_PROXY` و `HTTPS_PROXY` را می‌خوانند (در HTTPX گزینه `trust_env` به‌طور پیش‌فرض فعال است). نگهداری اطلاعات ورود در متغیر محیطی خطر نشت آن‌ها را هنگام ارسال کد به مخزن کاهش می‌دهد.

### هم‌زمان چند درخواست باز کنم؟

عدد درست یکتایی وجود ندارد. تحمل سایت مقصد، سقف اتصال هم‌زمان بسته پروکسی شما و محدودیت‌های دستگاه خودتان آن را تعیین می‌کنند. شروع با ده و افزایش آن تا زمانی که پاسخ 429 نبینید روشی مطمئن است.

## جمع‌بندی

هر سه کتابخانه کار خود را به‌خوبی انجام می‌دهند؛ انتخاب به مقیاس پروژه بستگی دارد. Requests برای کارهای ساده، HTTPX برای انعطاف و آمادگی برای آینده و AIOHTTP برای هم‌زمانی بالا برجسته است. هر کتابخانه‌ای که انتخاب کنید، مهلت تعیین کنید، هم‌زمانی را محدود کنید و تلاش دوباره را با انتظار نمایی بنویسید. با افزایش حجم، راهبرد IP هم‌پای کتابخانه اهمیت پیدا می‌کند. برای زیرساخت جمع‌آوری داده در مقیاس بزرگ، [راهکارهای استخراج داده](/fa/data-scraping) ما را ببینید.
