خانه/مقالات/ارسال POST با aiohttp برای اسکریپینگ
برنامه نویسی
API
برگشت به مقاله‌ها

ارسال POST با aiohttp برای اسکریپینگ

ارسال POST با aiohttp برای اسکریپینگ
این مقاله روش‌های عملی ارسال درخواست‌های POST با کتابخانهٔ aiohttp را برای اسکریپینگ توضیح می‌دهد. شامل مثال‌های کد برای JSON، فرم و آپلود فایل، مدیریت خطا و timeout، الگوهای همزمانی و نکات امنیتی برای نوشتن اسکریپ‌های پایدار است.
آسان اسکریپ آسان اسکریپ
1405-04-18

مقدمه

در این راهنما یاد می‌گیریم چگونه با Python aiohttp درخواست‌های POST ارسال کنیم. تمرکز روی سناریوهای رایج در اسکریپینگ است: ارسال JSON، ارسال فرم و فایل، سفارشی‌سازی هدرها، مدیریت خطا، و الگوهای همزمان‌سازی. پس از مطالعهٔ این مطلب قادر خواهید بود sessionها را به‌درستی مدیریت کنید، timeout و retry را به کد اضافه کنید و از مشکلات رایج هنگام اسکریپینگ جلوگیری نمایید.

ایجاد جلسه و ارسال POST پایه

برای شروع از aiohttp.ClientSession() استفاده می‌کنیم. توصیه می‌شود از async with برای مدیریت چرخهٔ عمر جلسه بهره ببرید تا کانکشن‌ها به‌درستی بسته شوند و نشتی اتصال نداشته باشیم.

مثال پایه:

import aiohttp
import asyncio

async def post_request():
    url = "https://httpbin.org/post"
    data = {"key": "value"}
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=data) as resp:
            print(await resp.text())

asyncio.run(post_request())

توضیح: ورودی این تابع url و data است. خروجی، متن پاسخ سرور است که با await resp.text() خوانده می‌شود. اگر انتظار JSON دارید از await resp.json() استفاده کنید. استفاده از async with علاوه بر خوانایی، تضمین می‌کند که session و اتصال‌ها به‌صورت امن بسته شوند.

ارسال JSON با json

وقتی می‌خواهید یک ساختار دیکشنری را به‌صورت JSON ارسال کنید، ساده‌ترین و کارا‌ترین راه استفاده از پارامتر json در متد session.post است؛ aiohttp داده را سریالایز می‌کند و هدر Content-Type: application/json را می‌گذارد.

import aiohttp
import asyncio

async def post_json():
    url = "https://httpbin.org/post"
    payload = {"name": "alice", "age": 30}
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload) as resp:
            print(await resp.json())

asyncio.run(post_json())

وقتی از json استفاده می‌کنید نیازی به json.dumps یا تنظیم دستی هدرها نیست. این روش برای APIهای REST مرسوم و خوانا است؛ اما اگر نیاز به کنترل دقیق سریالایزر (مثل تبدیل تاریخ‌ها یا حذف فیلدها) دارید، ممکن است بخواهید داده را خودتان سریالایز کنید.

ارسال فرم و فایل‌ها با data و FormData

برای ارسال فرم‌های ساده (x-www-form-urlencoded) از پارامتر data استفاده کنید. برای آپلود فایل یا multipart از aiohttp.FormData بهره ببرید.

import aiohttp
import asyncio

async def post_form():
    url = "https://httpbin.org/post"
    data = {"field": "value"}
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=data) as resp:
            print(await resp.text())

async def post_file():
    url = "https://httpbin.org/post"
    form = aiohttp.FormData()
    # توجه: باز کردن فایل به‌صورت sync می‌تواند بلوکه کند؛ برای فایل‌های بزرگ از aiofiles استفاده کنید
    form.add_field('file', open('example.jpg','rb'), filename='example.jpg', content_type='image/jpeg')
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=form) as resp:
            print(await resp.json())

نکات: هنگام ارسال فایل با FormData، اگر فایل‌ها بزرگ‌اند از خواندن به‌صورت استریم یا کتابخانه‌ای مانند aiofiles استفاده کنید تا event loop بلوکه نشود. برای فرم‌های ساده، aiohttp به‌طور خودکار هدر application/x-www-form-urlencoded را قرار می‌دهد.

سفارشی‌سازی Content-Type و ارسال دادهٔ سفارشی

اگر لازم است هدرها یا نوع محتوا را به‌صورت دستی تنظیم کنید (برای مثال پروتکل سفارشی یا فرمت غیرمعمول)، کافیست بدنهٔ سریالایزشده را در data قرار داده و هدر Content-Type را اضافه کنید.

import aiohttp
import asyncio
import json

async def custom_type():
    url = "https://httpbin.org/post"
    payload = {"k": "v"}
    headers = {"Content-Type": "application/custom-type"}
    body = json.dumps(payload)
    async with aiohttp.ClientSession() as session:
        async with session.post(url, data=body, headers=headers) as resp:
            print(await resp.json())

asyncio.run(custom_type())

نکتهٔ مهم: زمانی که دستی سریالایز می‌کنید از encoding درست (معمولاً UTF-8) و تطابق هدر با بدنهٔ ارسال‌شده مطمئن شوید.

مدیریت خطا، timeout و ری‌تراي‌ها

برای اسکریپینگ پایدار باید خطاها و تایم‌اوت‌ها را مدیریت کنید. aiohttp.ClientError شامل خطاهای شبکه‌ای است و asyncio.TimeoutError نیز ممکن است رخ دهد. از ClientTimeout برای تعیین محدودیت زمانی استفاده کنید و سیاست ری‌تراي با backoff مناسب پیاده کنید.

import aiohttp
import asyncio

async def post_with_retry(session, url, **kwargs):
    max_attempts = 4
    for attempt in range(1, max_attempts + 1):
        try:
            timeout = aiohttp.ClientTimeout(total=10)
            async with session.post(url, timeout=timeout, **kwargs) as resp:
                resp.raise_for_status()
                return await resp.json()
        except (aiohttp.ClientError, asyncio.TimeoutError) as e:
            if attempt == max_attempts:
                raise
            await asyncio.sleep(2 ** attempt)  # exponential backoff

خروجی: یا دادهٔ JSON برگشتی یا بالا رفتن استثنای نهایی پس از تلاش‌های ناموفق. لاگ‌کردن جزئیات خطا و وضعیت HTTP به شما کمک می‌کند علت شکست را سریع‌تر تشخیص دهید.

همزمانی، pooling و نکات عملکردی

برای ارسال همزمان چندین درخواست از یک جلسهٔ مشترک استفاده کنید تا reuse کانکشن‌ها (keep-alive) به کار رود. با استفاده از TCPConnector می‌توانید حداکثر کانکشن‌ها را کنترل کنید و با Semaphore تعداد همزمانی داخلی را محدود نمایید.

import aiohttp
import asyncio

async def bulk_post(tasks):
    connector = aiohttp.TCPConnector(limit=20)
    sem = asyncio.Semaphore(10)
    async with aiohttp.ClientSession(connector=connector) as session:
        async def worker(url, payload):
            async with sem:
                async with session.post(url, json=payload) as resp:
                    return await resp.json()
        return await asyncio.gather(*[worker(u,p) for u,p in tasks])

نکات عملکردی: نگه داشتن یک session طولانی معمولاً بهتر از باز/بستن مکرر است، اما مراقب نشتی منابع باشید. برای دانلود فایل‌های بزرگ از خواندن به‌صورت chunked (مثلاً resp.content.iter_chunked()) استفاده کنید تا از مصرف حافظهٔ زیاد جلوگیری شود.

نکات امنیتی و رعایت قوانین

همیشه قوانین سایت و محدودیت‌های قانونی را در نظر بگیرید. از ارسال توکن‌ها و اطلاعات حساس در URL خودداری کنید؛ به‌جای آن از هدر Authorization استفاده کنید. بررسی SSL و اعتبارسنجی گواهی را غیرفعال نکنید مگر در شرایط کاملاً کنترل‌شده.

همچنین نرخ‌سنجی مناسب (rate limiting)، ذخیرهٔ امن توکن‌ها و رعایت robots.txt/شرایط استفادهٔ سایت‌ها از الزامات اخلاقی و عملی در اسکریپینگ است.

جمع‌بندی

با aiohttp می‌توانید به‌سادگی انواع درخواست‌های POST را در پروژه‌های اسکریپینگ پیاده کنید: از پارامتر json برای ارسال سریع JSON، از data و FormData برای فرم و آپلود فایل، و در صورت نیاز از هدرهای سفارشی برای فرم‌های خاص استفاده کنید. همیشه sessionها را بازدهی‌دار نگه دارید، timeout و retry مناسب تعریف کنید، و نکات امنیتی و حقوقی را رعایت نمایید تا اسکریپ‌های شما پایدار و قابل‌اعتماد باشند.

مطالب مرتبط

مقاله‌های مرتبط