FA-TOOLS — Header Component

آموزش aiohttp و async requests در پایتون

سلام رفیق برنامه‌نویس! اگه تا حالا با پایتون کلی اسکریپت زدی که درخواست‌های وب ارسال می‌کنه و حس می‌کردی کارات یواش پیش میره، یا دنبال یه راهکار برای مدیریت حجم زیادی از درخواست‌ها بودی، این مقاله دقیقاً برای توئه. قراره با هم بریم سراغ دنیای پرسرعت و پرقدرت `aiohttp` و برنامه‌نویسی غیرهمزمان (Async) در پایتون. این تکنیک‌ها نه تنها سرعت برنامه‌هات رو چند برابر می‌کنن، بلکه باعث می‌شن منابع سیستم رو بهینه‌تر استفاده کنی. پس اگه دوست داری کد نویسی‌هات جون بگیرن و کارایی‌شون بره بالا، بزن بریم که این مهارت رو با هم یاد بگیریم.

راستی، اگه دنبال کدهای آماده و اسنیپت‌های خفن پایتونی برای تسریع پروژه‌هات هستی، حتماً یه سر به بخش اسنیپت‌های پایتون در fa-tools.ir بزن. کلی کد باحال و کاربردی اونجا منتظرته!

برای سوالات فوری و مشاوره می‌تونی مستقیم با ما تماس بگیری: 09202232789

🗺️ نقشه راه سریع: Async Requests با aiohttp

آموزش aiohttp و async requests در پایتون — تصویر 1

خلاصه کل ماجرا در یک نگاه:

  • 🚀 Async چیست؟
    پردازش همزمان، نه موازی

    یاد می‌گیریم چطور برنامه‌ها رو بدون بلاک شدن اجرا کنیم.
  • 📦 aiohttp چیست؟
    کتابخانه HTTP غیرهمزمان

    ابزار قدرتمند پایتون برای درخواست‌های وب سریع و مقیاس‌پذیر.
  • 🛠️ نصب و راه‌اندازی
    pip install aiohttp

    با یک دستور ساده آماده کار می‌شی.
  • 💡 نمونه کدها
    GET, POST, Headers, Errors

    از درخواست‌های ساده تا مدیریت خطا رو با هم کد می‌زنیم.
  • ⚡ بهینه‌سازی
    Session, Pooling, Rate Limiting

    چطور پرفورمنس رو به حداکثر برسونیم.
  • 🐛 عیب‌یابی
    مشکلات رایج و راه‌حل‌ها

    هر مشکلی داشتی، اینجا راه حلش هست.

فهرست مطالب

آموزش aiohttp و async requests در پایتون — تصویر 2

چرا باید سراغ برنامه‌نویسی غیرهمزمان (Async) بریم؟

آموزش aiohttp و async requests در پایتون — تصویر 3

فکر کن یه رستوران داری و سفارشات مشتری‌ها رو می‌گیری. اگه هر سفارشی که می‌رسه، صبر کنی تا کامل آماده شه، بعد بری سراغ بعدی، خب خیلی کند پیش می‌ری. این همون کاریه که پایتون به صورت “همزمان” (Synchronous) انجام میده. یعنی وقتی یه درخواست HTTP ارسال می‌کنی یا داری یه فایل رو از دیسک می‌خونی، برنامه تا وقتی که اون عملیات تموم نشده، وایمیسته و هیچ کار دیگه‌ای نمی‌کنه.

این انتظار کشیدن برای عملیات‌های I/O (ورودی/خروجی) مثل درخواست‌های شبکه یا کار با دیسک، می‌تونه کلی از وقت برنامه رو هدر بده. حالا اگه بتونی در حالی که منتظر تموم شدن یه کار I/O هستی، بری سراغ یه کار دیگه، چقدر بهتر میشه؟ اینجاست که برنامه‌نویسی “غیرهمزمان” (Asynchronous) وارد گود میشه. به جای اینکه بلاک بشی، برنامه‌ات می‌تونه عملیات رو به سیستم عامل بسپاره و تا اون کار داره انجام میشه، بقیه تسک‌هاش رو انجام بده. این مکانیسم رو بهش می‌گیم `Event Loop`.

مفهوم Async/Await به زبان ساده

پایتون با کلمات کلیدی `async` و `await` یه راهکار ساده و تمیز برای مدیریت برنامه‌نویسی غیرهمزمان ارائه داده.

  • `async def`: هر تابعی که با `async def` تعریف بشه، یه تابع Coroutine (روال همکار) محسوب میشه. این تابع می‌تونه موقتاً اجرای خودش رو متوقف کنه و کنترل رو به `Event Loop` برگردونه تا کارهای دیگه انجام بشن.
  • `await`: وقتی داخل یه تابع `async` از `await` استفاده می‌کنی، در واقع داری به پایتون میگی: “صبر کن تا این عملیات غیرهمزمان تموم بشه، اما در این مدت بیکار نمون، برو سراغ کارهای دیگه!”. وقتی عملیات `await` شده تموم میشه، کنترل دوباره برمی‌گرده به همون نقطه‌ای که متوقف شده بود.

در واقع، `async/await` به ما این قدرت رو میده که برنامه‌هایی بنویسیم که در ظاهر همزمان (Sequential) به نظر میرسن، اما در باطن به صورت غیرهمزمان و بدون بلاک شدن منابع، کار می‌کنن. این ترکیبصص برنامه‌های پایتون رو به شدت برای عملیات‌های I/O-bound (مثل درخواست‌های شبکه، کار با دیتابیس یا فایل) بهینه‌سازی می‌کنه.

معرفی aiohttp: غول ریکوئست‌های غیرهمزمان

حالا که با مفهوم Async آشنا شدیم، وقتشه بریم سراغ `aiohttp`. اگه قبلاً با `requests` کار کرده باشی، `aiohttp` رو میشه نسخه Async و فوق‌العاده سریع اون دونست، اما با قابلیت‌های بیشتر برای توسعه برنامه‌های وب سمت سرور. `aiohttp` یه کتابخونه قدرتمند برای ارسال درخواست‌های HTTP (Client) و همچنین ساخت سرورهای HTTP غیرهمزمان (Server) در پایتونه. تمرکز ما اینجا روی بخش Client (ارسال درخواست) هست.

نصب و راه‌اندازی aiohttp

نصب `aiohttp` مثل بقیه کتابخانه‌های پایتون خیلی ساده‌اس. کافیه ترمینال رو باز کنی و این دستور رو بزنی:

pip install aiohttp

ساخت یک Client Session برای درخواست‌های HTTP

برخلاف `requests` که برای هر درخواست می‌تونی یکبار تابع `get` یا `post` رو صدا بزنی، در `aiohttp` بهترین کار اینه که یک `ClientSession` بسازی و همه درخواست‌ها رو با اون انجام بدی. این کار باعث میشه منابع شبکه (مثل Connection Pool) بهینه‌تر استفاده بشن و عملکرد کلی بهتر بشه. `ClientSession` رو باید با `async with` استفاده کنی تا مطمئن شی که منابعش بعد از اتمام کار، به درستی بسته میشن.


import aiohttp
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        # حالا می‌تونی از این 'session' برای ارسال درخواست‌ها استفاده کنی
        print("Session created successfully!")

if __name__ == '__main__':
    asyncio.run(main())

نمونه کد: ارسال درخواست GET ساده

حالا بیایید با `ClientSession` یه درخواست `GET` ساده بفرستیم. مثلاً می‌خوایم اطلاعات یه صفحه وب رو بگیریم.


import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        # بررسی می‌کنیم که وضعیت پاسخ (status code) 200 (موفقیت‌آمیز) باشه
        response.raise_for_status() # اگه خطا بود، استثنا پرتاب می‌کنه
        
        # می‌تونیم نوع پاسخ رو بر اساس نیاز بگیریم:
        # await response.text()  # برای گرفتن متن صفحه
        # await response.json() # برای گرفتن پاسخ JSON
        # await response.read() # برای گرفتن بایت‌های خام
        
        content = await response.text()
        print(f"Status for {url}: {response.status}")
        print(f"Content length for {url}: {len(content)} characters")
        return content

async def main():
    async with aiohttp.ClientSession() as session:
        google_content = await fetch_url(session, 'https://www.google.com')
        # print(google_content[:100]) # نمایش 100 کاراکتر اول

        # می‌تونی همزمان چندین درخواست رو بفرستی:
        urls = [
            'https://api.github.com/users/fa-tools-ir',
            'https://fa-tools.ir/' # لینک به صفحه اصلی fa-tools
        ]
        
        tasks = []
        for url in urls:
            tasks.append(fetch_url(session, url))
        
        results = await asyncio.gather(*tasks)
        print("nFetched multiple URLs concurrently:")
        for res in results:
            print(f"Part of result: {res[:50]}...") # نمایش بخشی از هر پاسخ

if __name__ == '__main__':
    asyncio.run(main())

اگه کد بالا رو اجرا کنی، می‌بینی که چقدر سریع هر دو URL رو به صورت همزمان (Concurrently) فچ می‌کنه. این قدرت `asyncio` و `aiohttp` در کنار همه! برای دیدن اسنیپت‌های بیشتر در مورد پایتون می‌تونی به fa-tools.ir/snippets/python/ مراجعه کنی.

ارسال درخواست POST و داده‌های JSON

ارسال داده با متد `POST` هم در `aiohttp` کار راحتیه. برای ارسال داده‌های JSON، کافیه از پارامتر `json` استفاده کنی:


import aiohttp
import asyncio

async def send_post_request():
    url = 'https://jsonplaceholder.typicode.com/posts' # یک API تست برای POST
    payload = {
        'title': 'foo',
        'body': 'bar',
        'userId': 1
    }
    
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload) as response:
            response.raise_for_status()
            data = await response.json()
            print("POST Request Response:")
            print(f"Status: {response.status}")
            print(f"Received data: {data}")

if __name__ == '__main__':
    asyncio.run(send_post_request())

اگه بخوای داده‌های فرمی (form data) بفرستی، از پارامتر `data` یا `form` استفاده می‌کنی.

کار با Headerها و Query Parameterها

برای ارسال `Header`ها و `Query Parameter`ها، دقیقاً مثل `requests` عمل می‌کنی:


import aiohttp
import asyncio

async def customized_request():
    url = 'https://httpbin.org/get' # یه سایت برای تست درخواست‌ها
    
    headers = {
        'User-Agent': 'MyCustomAsyncApp/1.0',
        'Accept': 'application/json'
    }
    
    params = {
        'key1': 'value1',
        'key2': 'value2'
    }
    
    async with aiohttp.ClientSession(headers=headers) as session: # هدرها رو می‌تونی به سشن هم بدی
        async with session.get(url, params=params) as response: # یا فقط به همین درخواست
            response.raise_for_status()
            data = await response.json()
            print("Customized GET Request Response:")
            print(f"Status: {response.status}")
            print("Headers sent:", data['headers'])
            print("Query params received:", data['args'])

if __name__ == '__main__':
    asyncio.run(customized_request())

دانلود فایل‌های بزرگ به صورت غیرهمزمان

دانلود فایل‌های بزرگ نیاز به مدیریت دقیق‌تری داره تا همه فایل یکجا در حافظه لود نشه. `aiohttp` بهت اجازه میده محتوا رو به صورت Stream بخونی و همزمان بنویسی:


import aiohttp
import asyncio
import os

async def download_file(session, url, filename):
    async with session.get(url) as response:
        response.raise_for_status()
        print(f"Downloading {url} to {filename}...")
        
        with open(filename, 'wb') as f:
            async for chunk in response.content.iter_chunked(1024): # تکه تکه خوندن فایل (1KB در هر بار)
                f.write(chunk)
        print(f"Finished downloading {filename}.")

async def main():
    async with aiohttp.ClientSession() as session:
        # یک URL فایل تستی - مثلاً یک فایل PDF کوچک
        file_url = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
        output_filename = 'dummy_downloaded.pdf'
        
        await download_file(session, file_url, output_filename)
        
        if os.path.exists(output_filename):
            print(f"File {output_filename} downloaded successfully. Size: {os.path.getsize(output_filename)} bytes.")
        else:
            print(f"Failed to download {output_filename}.")

if __name__ == '__main__':
    asyncio.run(main())

مدیریت خطاها و Timeouts

تو دنیای واقعی، همیشه همه چیز خوب پیش نمیره. ممکنه شبکه قطع بشه، سرور پاسخ نده یا خیلی طول بکشه. `aiohttp` ابزارهای خوبی برای مدیریت این شرایط داره.

  • Timeouts: می‌تونی برای هر درخواست یا برای کل `ClientSession` یه مهلت زمانی (Timeout) تعیین کنی.
  • مدیریت استثناها: `aiohttp` استثناهای خاص خودش رو برای خطاهای شبکه یا HTTP داره.

import aiohttp
import asyncio
from aiohttp import ClientTimeout, ClientError

async def handle_errors_timeouts():
    # یک URL که شاید وجود نداشته باشه یا پاسخ نده
    bad_url = 'http://nonexistent-domain.com'
    slow_url = 'http://httpbin.org/delay/5' # این URL 5 ثانیه تاخیر داره
    
    # تنظیم Timeout: 1 ثانیه برای کل درخواست، 0.5 ثانیه برای اتصال (connection)
    timeout = ClientTimeout(total=1, connect=0.5)
    
    async with aiohttp.ClientSession(timeout=timeout) as session:
        # مثال 1: URL ناموجود
        try:
            async with session.get(bad_url) as response:
                print(f"Status for {bad_url}: {response.status}")
        except ClientError as e:
            print(f"Error fetching {bad_url}: {e}")
        except asyncio.TimeoutError:
            print(f"Timeout occurred for {bad_url} (or connection failed).")
            
        # مثال 2: URL با تاخیر زیاد
        try:
            async with session.get(slow_url) as response:
                print(f"Status for {slow_url}: {response.status}")
                await response.text()
        except asyncio.TimeoutError:
            print(f"Timeout occurred for {slow_url} (total timeout exceeded).")
        except ClientError as e:
            print(f"Error fetching {slow_url}: {e}")

if __name__ == '__main__':
    asyncio.run(handle_errors_timeouts())

همونطور که می‌بینی، با استفاده از بلوک‌های `try…except` می‌تونی به راحتی خطاهای احتمالی رو مدیریت کنی و برنامه‌ات رو resilientتر (مقاوم‌تر) بسازی.

مقایسه aiohttp با کتابخانه‌های همزمان

برای اینکه بهتر درک کنی چرا `aiohttp` در بعضی سناریوها یه انتخاب عالیه، بیا یه مقایسه سریع با کتابخانه‌های همزمان مثل `requests` داشته باشیم.

ویژگی aiohttp (غیرهمزمان) requests (همزمان)
مدل عملیاتی غیرهمزمان (Asynchronous) با Event Loop همزمان (Synchronous)
کارایی (هنگام I/O) بسیار بالا، قابلیت مدیریت هزاران درخواست همزمان متوسط، هر درخواست به صورت نوبتی پردازش می‌شود
سادگی کدنویسی نیاز به درک مفاهیم Async/Await، کمی پیچیده‌تر برای مبتدیان بسیار ساده، ایده‌آل برای درخواست‌های تکی و سریع
کاربرد اصلی اسکرپینگ پرسرعت، API گیت‌وی، برنامه‌های با ترافیک بالا اکثر نیازهای روزمره، ارتباط با APIهای کوچک، تست
زمان بلاک شدن تقریباً صفر (برنامه در حین انتظار، کارهای دیگر را انجام می‌دهد) بلاک می‌شود (تا پاسخ از سرور برسد، برنامه متوقف می‌شود)

این جدول نشون میده که `aiohttp` برای سناریوهایی که نیاز به عملکرد بالا و مدیریت تعداد زیادی درخواست همزمان داری، یک برتری مشهود داره. اما برای کارهای ساده و سریع، `requests` همچنان انتخابیه که سادگی خودش رو حفظ کرده.

بهینه‌سازی و نکات پیشرفته در aiohttp

صرفاً استفاده از `aiohttp` به معنی حداکثر کارایی نیست. باید از قابلیت‌هاش به درستی استفاده کنی تا بهترین پرفورمنس رو بگیری.

استفاده از Connection Pool

همونطور که قبلاً اشاره کردم، استفاده از یک `ClientSession` برای تمام درخواست‌هات خیلی مهمه. `ClientSession` یک Connection Pool داخلی داره که اتصالات TCP رو باز نگه می‌داره و برای درخواست‌های بعدی دوباره ازشون استفاده می‌کنه. این کار باعث میشه هزینه باز و بسته کردن اتصال (که زمان‌بر هست) حذف بشه.


import aiohttp
import asyncio
import time

async def fetch_multiple_urls(session, urls):
    tasks = []
    for url in urls:
        tasks.append(session.get(url))
    
    start_time = time.time()
    responses = await asyncio.gather(*tasks)
    
    for response in responses:
        await response.text() # باید محتوا رو بخونیم
        response.close() # مهمه که ریسورس رو ببندی
        print(f"Fetched {response.url} with status {response.status}")
        
    end_time = time.time()
    print(f"Total time taken: {end_time - start_time:.2f} seconds")

async def main():
    urls_to_fetch = [
        'https://httpbin.org/delay/1', 
        'https://httpbin.org/delay/1', 
        'https://httpbin.org/delay/1',
        'https://fa-tools.ir/snippets/css/', # لینک به اسنیپت‌های CSS
        'https://fa-tools.ir/snippets/js/' # لینک به اسنیپت‌های JS
    ] * 2 # برای اینکه تعداد درخواست‌ها بیشتر بشه و تاثیر pool مشخص شه

    # استفاده از یک session واحد
    print("Using a single ClientSession for multiple requests:")
    async with aiohttp.ClientSession() as session:
        await fetch_multiple_urls(session, urls_to_fetch)
        
    # حالا اگه هر درخواست یک session جدید بسازه:
    print("nCreating a new ClientSession for each request (LESS EFFICIENT):")
    start_time = time.time()
    tasks_separate = []
    for url in urls_to_fetch:
        # اینجا برای هر درخواست یک session جدید ساخته میشه که توصیه نمیشه
        async def _single_fetch(u):
            async with aiohttp.ClientSession() as s:
                async with s.get(u) as resp:
                    await resp.text()
                    print(f"Fetched {resp.url} with status {resp.status}")
        tasks_separate.append(_single_fetch(url))
    
    await asyncio.gather(*tasks_separate)
    end_time = time.time()
    print(f"Total time taken with separate sessions: {end_time - start_time:.2f} seconds")


if __name__ == '__main__':
    asyncio.run(main())

اگه این کد رو اجرا کنی، تفاوت زمان اجرای بین استفاده از یک `ClientSession` و ساختن سشن برای هر درخواست رو به وضوح می‌بینی. استفاده از سشن واحد، زمان رو به شکل قابل توجهی کاهش میده.

محدود کردن درخواست‌ها (Rate Limiting)

وقتی داری از یه API استفاده می‌کنی یا اسکرپینگ انجام میدی، خیلی مهمه که پروتکل‌های سایت مقصد رو رعایت کنی و سرور رو با درخواست‌های زیاد، overburden نکنی. اینجاست که Rate Limiting به کارت میاد. می‌تونی تعداد درخواست‌ها در یک بازه زمانی مشخص رو محدود کنی. `asyncio.Semaphore` یه ابزار عالی برای این کار در `asyncio` هست.


import aiohttp
import asyncio
import time

# اجازه می‌دهیم حداکثر 5 درخواست همزمان انجام شود
MAX_CONCURRENT_REQUESTS = 5
semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)

async def throttled_fetch(session, url):
    async with semaphore: # منتظر میشیم تا یک "slot" خالی بشه
        async with session.get(url) as response:
            response.raise_for_status()
            content = await response.text()
            print(f"Fetched {url} (size: {len(content)}). Active tasks: {MAX_CONCURRENT_REQUESTS - semaphore._value}")
            return content

async def main():
    urls = [f'https://httpbin.org/delay/{i % 2 + 1}' for i in range(20)] # 20 درخواست با تاخیر 1 یا 2 ثانیه
    
    async with aiohttp.ClientSession() as session:
        tasks = [throttled_fetch(session, url) for url in urls]
        await asyncio.gather(*tasks)

if __name__ == '__main__':
    asyncio.run(main())

در کد بالا، `semaphore` تضمین می‌کنه که همیشه حداکثر 5 درخواست به صورت همزمان فعال باشن. این برای احترام به سرورهای مقصد و جلوگیری از بلاک شدن IP شما خیلی کاربردیه.

Caching و افزایش کارایی

برای درخواست‌هایی که مکرراً به یک URL مشخص ارسال می‌شن و محتوای اون‌ها زیاد تغییر نمی‌کنه، می‌تونی از Cache استفاده کنی. این یعنی به جای اینکه هر بار درخواست رو به سرور بفرستی، پاسخ رو برای یه مدت مشخص در حافظه ذخیره کنی و در صورت نیاز، از همون حافظه استفاده کنی.
`aiohttp` خودش Caching داخلی نداره، اما می‌تونی از کتابخانه‌های کمکی یا یک دیکشنری ساده پایتون به همراه `asyncio.Lock` استفاده کنی.


import aiohttp
import asyncio
import time

class SimpleAsyncCache:
    def __init__(self, ttl_seconds=60):
        self._cache = {}
        self._ttl = ttl_seconds
        self._lock = asyncio.Lock()

    async def get(self, key):
        async with self._lock:
            if key in self._cache:
                data, timestamp = self._cache[key]
                if time.time() - timestamp < self._ttl:
                    return data
        return None

    async def set(self, key, value):
        async with self._lock:
            self._cache[key] = (value, time.time())

async def cached_fetch(session, url, cache):
    cached_data = await cache.get(url)
    if cached_data:
        print(f"Cache hit for {url}")
        return cached_data
    
    print(f"Cache miss for {url}, fetching...")
    async with session.get(url) as response:
        response.raise_for_status()
        data = await response.text()
        await cache.set(url, data)
        return data

async def main():
    cache = SimpleAsyncCache(ttl_seconds=10) # 10 ثانیه Cache
    urls = ['https://httpbin.org/anything?param=1', 'https://httpbin.org/anything?param=2']
    
    async with aiohttp.ClientSession() as session:
        # اولین بار - از شبکه دریافت میشه
        await cached_fetch(session, urls[0], cache)
        await cached_fetch(session, urls[1], cache)
        
        # بلافاصله دوباره - از Cache دریافت میشه
        await cached_fetch(session, urls[0], cache)
        
        # بعد از 10 ثانیه - دوباره از شبکه دریافت میشه
        print("Waiting for cache to expire...")
        await asyncio.sleep(11)
        await cached_fetch(session, urls[0], cache)

if __name__ == '__main__':
    asyncio.run(main())

این یه نمونه ساده از Caching هست. برای پروژه‌های بزرگتر، می‌تونی از کتابخانه‌های Cache پیشرفته‌تر یا سرویس‌هایی مثل Redis استفاده کنی.

سناریوهای واقعی و کاربردهای aiohttp

`aiohttp` فقط برای ارسال درخواست‌های ساده نیست؛ تو سناریوهای پیچیده‌تر هم خیلی قدرتمنده.

اسکرپینگ وب پرسرعت

اگه نیاز داری اطلاعات زیادی رو از چندین صفحه وب جمع‌آوری کنی، `aiohttp` بهترین دوست توئه. می‌تونی صدها یا هزاران صفحه رو به صورت همزمان اسکرپ کنی بدون اینکه برنامه بلاک بشه. این کار با کتابخانه‌های همزمان مثل `requests` یا به شدت کند میشه یا نیاز به پیاده‌سازی پیچیده Multi-threading/Multi-processing داره که خودش دردسرهای خاص خودشو داره.
برای مثال، اگه بخوای لیست تمام اسنیپت‌های موجود در fa-tools.ir/snippets/ رو با سرعت بالا استخراج کنی، `aiohttp` کمک بزرگیه.

API گیت‌وی‌ها و میکرو سرویس‌ها

در معماری میکرو سرویس‌ها، گاهی اوقات یه سرویس (مثلاً API گیت‌وی) باید درخواست‌های ورودی رو دریافت کنه و بعد اون رو به چند سرویس داخلی دیگه بفرسته، پاسخ‌ها رو جمع‌آوری کنه و بعد به کاربر برگردونه. `aiohttp` با قابلیت‌های Async خودش، عالی عمل می‌کنه چون می‌تونه این درخواست‌های داخلی رو همزمان ارسال کنه و منتظرشون بمونه بدون اینکه خودش بلاک بشه. اینجوری latency (تاخیر) پاسخ‌دهی به کاربر نهایی به شدت کاهش پیدا می‌کنه.

بات‌های تلگرام و دیسکورد

بات‌های تلگرام، دیسکورد یا هر پلتفرم چت دیگه، معمولاً با APIهای مبتنی بر HTTP کار می‌کنن. بات باید بتونه به سرعت به پیام‌های جدید پاسخ بده و درخواست‌های متعدد رو به API پلتفرم ارسال کنه. با `aiohttp` می‌تونی بات‌هایی بسازی که همزمان به چندین کاربر سرویس بدن و با تأخیر کمتری کار کنن. این خصوصیت برای بات‌هایی که نیاز به ارتباط با چندین سرویس خارجی دارن (مثلاً یه بات آب‌وهوا که از APIهای مختلف هواشناسی اطلاعات می‌گیره)، حیاتیه.

عیب‌یابی سریع: مشکلات رایج در aiohttp و Async

گاهی اوقات ممکنه با مشکلاتی در استفاده از `aiohttp` مواجه بشی. اینجا چند تا از رایج‌ترین اون‌ها و راه‌حل‌هاشون رو با هم بررسی می‌کنیم:

❌ مشکل: RuntimeWarning: coroutine '...' was never awaited

توضیح: این یعنی یه تابع `async` رو صدا زدی ولی از کلمه کلیدی `await` برای منتظر موندن نتیجه‌اش استفاده نکردی. در نتیجه، اون Coroutine شروع به اجرا نشده یا نتیجه‌اش هرگز مصرف نشده.

✅ راه‌حل: مطمئن شو که هر جا یه تابع `async` رو صدا می‌زنی (به جز در مواردی مثل `asyncio.create_task` یا `asyncio.ensure_future` که به صورت صریح تسک رو زمان‌بندی می‌کنی)، حتماً قبلش `await` رو گذاشتی.


# غلط
# my_async_func() 

# صحیح
# await my_async_func()
    

❌ مشکل: AttributeError: 'ClientSession' object has no attribute 'get'

توضیح: این خطا معمولاً وقتی رخ میده که `ClientSession` رو به درستی با `async with` نساخته باشی و قبل از اینکه Session آماده استفاده بشه، بخوای ازش متدهای `get`، `post` و… رو صدا بزنی. یا اینکه سعی کردی از `session` خارج از بلوک `async with` استفاده کنی.

✅ راه‌حل: همیشه `ClientSession` رو با `async with aiohttp.ClientSession() as session:` بساز و تمام درخواست‌ها رو داخل اون بلوک انجام بده.

❌ مشکل: Timeouts زیاد یا ClientConnectorError

توضیح: این خطاها ممکنه به دلایل مختلفی مثل ناپایدار بودن شبکه شما، سرور مقصد که خیلی کند پاسخ میده یا اصلا در دسترس نیست، یا Rate Limiting شدن توسط سرور مقصد، رخ بده.

✅ راه‌حل:

  • تنظیم `ClientTimeout`: با تنظیم یک `ClientTimeout` مناسب، می‌تونی مدت زمان انتظار برای پاسخ رو کنترل کنی.
  • بررسی شبکه: مطمئن شو اتصال اینترنت خودت مشکلی نداره.
  • Rate Limiting: اگه داری تعداد زیادی درخواست به یک سرور می‌فرستی، احتمال داره توسط اون سرور Rate Limit شده باشی. از `asyncio.Semaphore` برای محدود کردن درخواست‌ها استفاده کن (همونطور که بالا توضیح دادیم).
  • بررسی URL: مطمئن شو URL که درخواست می‌فرستی درست و فعال هست.

❌ مشکل: برنامه تموم نمیشه (Hangs)

توضیح: این معمولاً وقتی اتفاق می‌افته که یه Coroutine رو شروع کردی ولی هرگز `await` نکردی، یا `Event Loop` به درستی بسته نشده. گاهی وقتا هم مشکل از منابعیه که باز موندن (مثل Session یا فایل‌ها).

✅ راه‌حل:

  • استفاده از `async with`: همیشه `ClientSession` و هر منبع قابل بستن دیگه رو با `async with` استفاده کن.
  • `asyncio.run()`: مطمئن شو که تابع `main` خودت رو با `asyncio.run(main())` اجرا می‌کنی. این تابع به صورت خودکار `Event Loop` رو مدیریت و بعد از اتمام همه Coroutineها، اونو می‌بنده.
  • `response.close()`: اگر از `async with` برای `response` استفاده نمی‌کنی، حتماً بعد از اتمام کار با پاسخ، `await response.close()` رو صدا بزن.

با رعایت این نکات، بیشتر مشکلات رایج رو می‌تونی حل کنی و از قدرت `aiohttp` به بهترین شکل بهره ببری.

سوالات متداول (FAQ) درباره aiohttp و Async

آیا aiohttp جایگزین requests است؟

نه دقیقاً. `aiohttp` و `requests` هر دو کتابخانه‌های HTTP هستند اما برای سناریوهای مختلفی طراحی شده‌اند. `requests` برای کارهای همزمان و درخواست‌های تکی ساده و سریع عالی است. `aiohttp` برای برنامه‌نویسی غیرهمزمان (Async) و مدیریت حجم بالایی از درخواست‌ها به صورت همزمان، مثل اسکرپینگ وب، API گیت‌وی‌ها یا برنامه‌های شبکه‌ای پرسرعت، برتری دارد. انتخاب بین آن‌ها به نیازهای پروژه شما بستگی دارد.

آیا aiohttp فقط برای Client استفاده می‌شود؟

خیر، `aiohttp` هم برای Client (ارسال درخواست) و هم برای Server (ساخت سرور وب) کاربرد دارد. این مقاله روی بخش Client تمرکز داشت، اما می‌توانید از آن برای ساخت وب‌سرورهای پرسرعت و غیرهمزمان هم استفاده کنید.

آیا می‌توانم aiohttp را با Flask یا Django استفاده کنم؟

Flask و Django فریم‌ورک‌های وب همزمان (Synchronous) هستند. شما نمی‌توانید کد `asyncio` را مستقیماً داخل یک ویو Flask یا Django اجرا کنید مگر اینکه از کتابخانه‌هایی مثل `ASGI` یا ابزارهایی برای ران کردن Coroutineها در Threadهای جداگانه استفاده کنید. اما می‌توانید از `aiohttp` در یک اسکریپت جداگانه برای انجام کارهای بک‌گراند یا فراخوانی‌های غیرهمزمان استفاده کنید و نتایج را به Flask/Django برگردانید.

چرا باید از ClientSession استفاده کنم و هر بار یک درخواست ساده نفرستم؟

استفاده از `ClientSession` باعث می‌شود که `aiohttp` بتواند Connection Pool بسازد و اتصالات TCP را دوباره استفاده کند. این کار هزینه باز و بسته کردن مجدد اتصالات را حذف کرده و عملکرد را به خصوص در ارسال تعداد زیادی درخواست به شدت افزایش می‌دهد. همچنین، مدیریت کوکی‌ها و هدرهای پیش‌فرض نیز با `ClientSession` راحت‌تر است.

آیا Async I/O موازی (Parallel) است؟

نه، Async I/O (غیرهمزمان) و Parallelism (موازی‌سازی) دو مفهوم متفاوت هستند. Async I/O (که به آن Concurrency یا همزمانی هم می‌گویند) به این معنی است که برنامه شما می‌تواند در حین انتظار برای تکمیل عملیات‌های I/O، به کارهای دیگر بپردازد و CPU را آزاد نگه دارد. این اتفاق روی یک Thread (و معمولاً روی یک هسته CPU) رخ می‌دهد. موازی‌سازی اما یعنی چندین عملیات به صورت کاملاً همزمان روی چندین Thread یا چندین هسته CPU اجرا شوند. پایتون به دلیل GIL (Global Interpreter Lock) موازی‌سازی واقعی Thread-based را محدود می‌کند، اما برای I/O-bound tasks، Async I/O بسیار مؤثر است.

برای یادگیریی بیشتر Async/Await چه منابعی پیشنهاد می‌شود؟

بهترین منابع شامل مستندات رسمی `asyncio` پایتون و مستندات رسمی `aiohttp` است. همچنین، مقالات و آموزش‌های خوبی در مورد الگوهای طراحی Asynchronous در پایتون وجود دارد که با یک جستجوی ساده می‌توانید به آن‌ها دسترسی پیدا کنید. و یادت نره که برای اسنیپت‌های کد و نمونه‌های عملی، fa-tools.ir همیشه در کنارته!

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

اگه سوالی داشتی یا جایی گیر کردی، حتماً تو بخش نظرات بپرس. ما هم اینجا هستیم تا بهت کمک کنیم. و یه بار دیگه، فراموش نکن که کلی کدهای آماده و اسنیپت‌های کاربردی تو fa-tools.ir/snippets/ منتظرته تا پروژه‌هات رو سریع‌تر پیش ببری.

موفق باشی و کدنویسی‌های پرسرعتی داشته باشی!

📞 نیاز به کمک فوری داری؟ با ما در تماس باش: 09202232789

/* Global styles for better readability and responsiveness */
body {
font-family: ‘Vazirmatn’, sans-serif;
line-height: 1.8;
color: #34495E;
font-size: 1.1em;
margin: 0;
padding: 0;
background-color: #f0f2f5;
direction: rtl; /* Ensure right-to-left for Persian text */
text-align: right;
}

h1, h2, h3 {
color: #2C3E50;
margin-top: 40px;
margin-bottom: 20px;
line-height: 1.4;
}

h1 {
font-size: 2.8em;
font-weight: bold;
text-align: center;
color: #2C3E50;
}

h2 {
font-size: 2.2em;
font-weight: bold;
color: #2C3E50;
border-bottom: 2px solid #ECF0F1;
padding-bottom: 10px;
}

h3 {
font-size: 1.6em;
font-weight: bold;
color: #34495E;
}

a {
color: #3498DB;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

p {
margin-bottom: 1em;
}

ul {
margin-bottom: 1em;
padding-right: 20px;
}

li {
margin-bottom: 0.5em;
}

pre {
background-color: #ECF0F1;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
font-family: ‘Fira Code’, ‘Cascadia Code’, monospace;
font-size: 0.95em;
direction: ltr; /* Ensure code blocks are left-to-right */
text-align: left;
margin: 20px 0;
}

code {
font-family: ‘Fira Code’, ‘Cascadia Code’, monospace;
background-color: #D6EFED;
padding: 2px 5px;
border-radius: 4px;
font-size: 0.9em;
color: #2C3E50;
direction: ltr; /* Ensure inline code is left-to-right */
}

table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}

th, td {
padding: 12px 15px;
text-align: right;
border-bottom: 1px solid #ECF0F1;
}

th {
background-color: #3498DB;
color: #FFFFFF;
font-weight: bold;
}

tr:nth-child(even) {
background-color: #F8F9FA;
}

/* Responsive adjustments */
@media (max-width: 768px) {
body {
font-size: 1em;
padding: 10px;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.7em;
}
h3 {
font-size: 1.3em;
}
div[style*=”max-width: 800px”] {
max-width: 100% !important;
padding: 15px !important;
}
pre {
padding: 10px;
font-size: 0.85em;
}
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr {
border: 1px solid #ECF0F1;
margin-bottom: 10px;
border-radius: 8px;
overflow: hidden;
}
td {
border: none;
position: relative;
padding-right: 50%;
text-align: right;
}
td:before {
position: absolute;
top: 6px;
right: 6px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
}
td:nth-of-type(1):before { content: “ویژگی”; }
td:nth-of-type(2):before { content: “aiohttp (غیرهمزمان)”; }
td:nth-of-type(3):before { content: “requests (همزمان)”; }
}

/* Tablet adjustments */
@media (min-width: 769px) and (max-width: 1024px) {
body {
font-size: 1.05em;
padding: 15px;
}
h1 {
font-size: 2.4em;
}
h2 {
font-size: 1.9em;
}
h3 {
font-size: 1.4em;
}
}

/* General styling for block editor compatibility */
/* These styles are meant to be embedded directly or easily copied to a CSS file. */
/* Ensure your block editor (e.g., Gutenberg) supports custom HTML blocks and CSS. */

Table of Contents

آخرین نوشته‌ها