Python Programming

تسلط بر Asyncio در پایتون: ساخت وب‌اسکراب‌های ناهمگام آماده تولید با aiohttp و asyncio

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

چرا ناهمگام عمل کنیم؟

برنامه‌نویسی ناهمگام در پایتون بر پایه کوروتین‌ها (coroutines) استوار است—توابعی که می‌توانند اجرای خود را متوقف و از سر بگیرند. برخلاف رشته‌ها (threads) که از نظر حافظه و تعویض زمینه سنگین هستند، کوروتین‌ها سبک‌وزن بوده و توسط حلقه رویداد مدیریت می‌شوند. برای وب‌اسکرابینگ، این بدان معناست که برنامه شما می‌تواند یک درخواست را به سرور A آغاز کند، در حالی که منتظر پاسخ A است به سرور B تغییر وضعیت دهد، و در حالی که منتظر پاسخ B است به سرور C تغییر وضعیت دهد. این همزمانی ورودی/خروجی زمان اجرای کل را به طور چشمگیری کاهش می‌دهد.

با این حال، سرعت خام با چالش‌هایی همراه است: شرایط مسابقه (race conditions)، نشت حافظه و محدودیت‌های نرخ API. یک اسکرابر آماده تولید باید این موارد را به آرامی مدیریت کند.

راه‌اندازی محیط ناهمگام

اول، مطمئن شوید که کتابخانه‌های ضروری نصب شده‌اند. شما به asyncio (داخلی) و aiohttp (سوم شخص) نیاز دارید.

pip install aiohttp aiohttp_socks

ما با تعریف جلسه مشتری ناهمگام خود شروع می‌کنیم. استفاده از ClientSession اجازه می‌دهد تا از انباشت اتصال استفاده شود که برای عملکرد هنگام ارسال چندین درخواست به میزبان یکسان حیاتی است.

پیاده‌سازی اصلی: اسکرابر ناهمگام

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

import asyncio
import aiohttp
import time

# تعریف یک سمافور برای محدود کردن همزمانی و احترام به محدودیت‌های نرخ
# این اطمینان حاصل می‌کند که ما بیش از 10 اتصال را همزمان باز نمی‌کنیم
semaphore = asyncio.Semaphore(10)

async def fetch_url(session, url):
    async with semaphore:
        try:
            # تنظیم زمان‌بندی برای جلوگیری از درخواست‌های معلق
            timeout = aiohttp.ClientTimeout(total=10)
            async with session.get(url, timeout=timeout) as response:
                if response.status == 200:
                    return await response.text()
                else:
                    print(f"Failed to fetch {url}: Status {response.status}")
                    return None
        except asyncio.TimeoutError:
            print(f"Timeout fetching {url}")
            return None
        except aiohttp.ClientError as e:
            print(f"Client error for {url}: {e}")
            return None

async def main():
    urls = [
        "https://httpbin.org/html",
        "https://httpbin.org/json",
        "https://httpbin.org/headers",
    ] * 50  # شبیه‌سازی 150 URL

    start_time = time.time()

    # ایجاد یک جلسه واحد برای کل فرآیند
    async with aiohttp.ClientSession() as session:
        # جمع‌آوری تمام وظایف به صورت همزمان
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)

    end_time = time.time()
    print(f"Completed {len(urls)} requests in {end_time - start_time:.2f} seconds")

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

ملاحظات پیشرفته برای تولید

در حالی که کد بالا کار می‌کند، اسکراب‌های تولید به پیچیدگی بیشتری نیاز دارند. در اینجا سه بهبود حیاتی وجود دارد:

  1. چرخش User-Agent: بسیاری از سرورها User-Agentهای پیش‌فرض پایتون را مسدود می‌کنند. از یک استخر از رشته‌های User-Agent واقعی برای جلوگیری از مسدود شدن استفاده کنید.
  2. منطق تلاش مجدد: از بازگشت نمایی با استفاده از کتابخانه‌هایی مانند tenacity یا منطق سفارشی در داخل کوروتین خود استفاده کنید. این زمانی کمک می‌کند که سرور به طور موقت خطای 503 را بازگرداند.
  3. مدیریت حافظه: از ذخیره تمام محتوای HTML در حافظه خودداری کنید. اگر پردازش سنگین است، پاسخ‌ها را مستقیماً روی دیسک بنویسید یا آن‌ها را از طریق یک تجزیه‌گر مانند lxml به صورت افزایشی پخش کنید.

نتیجه‌گیری

تسلط بر asyncio و aiohttp وب‌اسکرابینگ را از یک وظیفه ترتیبی گلوگاه‌دار به یک پایپلاین با ظرفیت بالا تبدیل می‌کند. با بهره‌گیری از سمافورها برای محدودیت نرخ و مدیریت استثناهای مناسب، اسکرابرهایی می‌سازید که نه تنها سریع، بلکه محترمانه نسبت به منابع سرور و مقاوم در برابر شکست‌ها هستند. هنگامی که مقیاس را بیشتر افزایش می‌دهید، در نظر بگیرید که این ابزارها را با چارچوب‌های توزیع‌شده مانند Scrapy-Redis یا کارگران ناهمگام برای پروژه‌های استخراج داده‌های واقعاً عظیم ادغام کنید.

امروز شروع به بازسازی اسکراب‌های همگام خود کنید و ببینید چگونه کارایی استخراج داده‌های شما به شدت افزایش می‌یابد.

Share: