بهترین کتابخانه های پایتون برای کار با http

امیرحسین بیگدلو 5 ماه قبل

تعداد زیادی کتابخانه برای کار با http در پایتون وجود دارد. اگر یک جتسجوی سریع در گیتهاب انجام دهید به 17000 نتیجه میرسید. در این مقاله می‌خواهیم پنج مورد از بهترین کتابخانه های HTTP را که در حال حاضر برای پایتون در دسترس هستند، بررسی کنیم و توضیح دهیم که چرا هر یک از آنها ممکن است برای شما مناسب باشد.

 

برای تمام مثال های این مقاله از api فیلم جنگ ستارگان استفاده خواهیم کرد که در آدرس swapi.dev در دسترس است. به طور مثال با یک درخواست GET میتوانیم اطلاعات مربوط به افراد، سیارات و داده های دنیای جنگ ستارگان به دست آوریم:

{
  "name": "Death Star",
  "model": "DS-1 Orbital Battle Station",
  "manufacturer": "Imperial Department of Military Research, Sienar Fleet Systems",
  "cost_in_credits": "1000000000000",
  ...
}

 

برای درخواست های POST از httpbin.org استفاده خواهیم کرد و اطلاعات مربوط به Obi Wan را به آن ارسال میکنیم:

{
	"name": "Obi-Wan Kenobi",
	"height": "182",
	"mass": "77",
	"hair_color": "auburn, white",
	"skin_color": "fair",
	"eye_color": "blue-gray",
	"birth_year": "57BBY",
	"gender": "male"
}

 

دوره پیشنهادی: دوره آموزش پایتون (python)

 

 #  کتابخانه استاندارد پایتون

اگر با کتابخانه استاندارد پایتون آشنا باشید میدانید که از ماژول urllib نیز میتوانید برای ارسال درخواست http استفاده کنید. برای مقایسه با بقیه کتابخانه ها، بیایید ببینیم که چطور میتوانیم فقط با کتابخانه استاندارد پایتون با http کار کنیم:

import json
import urllib.request

response = urllib.request.urlopen('https://swapi.dev/api/starships/9/')
text = response.read()
print(json.loads(text.decode('utf-8')))

 

به کد بالا دقت کنید و ببیند که چطور مجبوریم از کتابخانه json پایتون برای تبدیل داده ها استفاده کنیم. زیر urllib اطلاعات را به شکل byte برمیگرداند.

 

ارسال درخواست POST نیز به شکل زیر است:

import json
from urllib import request, parse

data = {"name": "Obi-Wan Kenobi", ...}

encoded_data = json.dumps(data).encode()

req = request.Request('https://httpbin.org/post', data=encoded_data)
req.add_header('Content-Type', 'application/json')
response = request.urlopen(req)

text = response.read()

print(json.loads(text.decode('utf-8')))

 

همچنین باید اطلاعاتی که میخواهیم ارسال کنیم را نیز رمزگذاری کرده و header ها را نیز تنظیم کنیم. ممکن است به این نتیجه برسید که چرا باید برای دریافت یکسری داده این همه کار انجام دهم؟ خب، به همین خاطر است که برنامه نویسان پایتون شروع به توسعه کتابخانه های دیگر برای کار با http کردند.

 

دوره پیشنهادی: دوره آموزش web scraping در پایتون

 

 #  کتابخانه urllib3

پکیج urllib3 به طرز عجیبی در کتابخانه استاندارد نیست، بلکه یک پکیج HTTP جداگانه است که بر اساس urllib ساخته شده است. ویژگی‌های گمشده‌ای مانند جمع‌بندی اتصال، تأیید TLS و thread safe را فراهم می‌کند. این ویژگی ها در برنامه هایی مفید هستند که قرار است تعداد اتصالات زیادی را به سرور داشته باشند، که در این حالت به جای ایجاد یک اتصال جدید از همان اتصال قدیمی استفاده میکند.

 

این پکیج در ماه 150 میلیون دانلود دارد. برای ارسال یک درخواست GET میتوانید به شکل زیر کار کنید:

import urllib3
import json

http = urllib3.PoolManager()
r = http.request('GET', 'https://swapi.dev/api/starships/9/')

print(json.loads(r.data.decode('utf-8')))

 

مشابه کتابخانه استاندارد پایتون مجبورید اطلاعات را با json رمزگذاری کنیم.

 

برای ارسال درخواست POST با کتابخانه urllib3 باید به شکل زیر کار کنید. دقت کنید که باید پارامترها را نیز رمزگذاری کنیم:

import json
import urllib3

data = {"name": "Obi-Wan Kenobi", ...}

http = urllib3.PoolManager()

encoded_data = json.dumps(data).encode('utf-8')

r = http.request(
    'POST',
    'https://httpbin.org/post',
    body=encoded_data,
    headers={'Content-Type': 'application/json'}
)

print(json.loads(r.data.decode('utf-8')))

 

آبجکت PoolManager وظیفه ادغام اتصالات و thread safe بودن را دارد. urllib3 همچنین رفتارهای پیچیده ای را ارائه می دهد.ویژگی retry واقعاً مهم است - ما نمی‌خواهیم اتصالمان به دلیل بارگیری تصادفی یک سرور به پایان برسد و سپس از آن صرفنظر کنیم. مایلیم قبل از اینکه داده را غیرقابل دسترس بدانیم، چندین بار امتحان کنیم.

 

نقطه ضعف urllib3 اینست که کار با کوکی ها در آن دشوار است. این کتابخانه به طور مستقیم از کوکی ها پشتیبانی نمیکند و ما محبوریم به صورت دستی به عنوان یک هدر آن را تنظیم کنیم یا از چیزی مانند ماژول http.cookies برای مدیریت آن‌ها استفاده کنیم. مثلا:

headers={'Cookie': 'foo=bar; hello=world'}

 

با توجه به اینکه بسیاری از کتابخانه های دیگر به urllib3 وابسته هستند، احتمالا برای مدت طولانی در آینده وجود خواهد داشت.

 

مقاله پیشنهادی: 3 روش برای دانلود تصاویر با پایتون

 

 #  کتابخانه requests

پکیج Requests در جامعه Python بسیار مورد علاقه است و طبق گفته PePy ماهانه بیش از 110 میلیون بار دانلود می شود. همچنین به عنوان یک "رابط HTTP سطح بالاتر" در اسناد اصلی urllib.request توصیه می شود. کار با requests بسیار ساده است و به همین دلیل اکثر توسعه دهندگان در جامعه پایتون از آن به عنوان کتابخانه HTTP انتخابی خود استفاده می کنند.

 

برای ارسال درخواست GET فقط کافیست مانند زیر عمل کنید:

import requests

r = requests.get('https://swapi.dev/api/starships/9/')

print(r.json())

 

به طور مشابه، ارسال داده ها نیز ساده است - فقط باید روش دریافت خود را به POST تغییر دهیم:

import requests

data = {"name": "Obi-Wan Kenobi", ...}

r = requests.post('https://httpbin.org/post', json=data)

print(r.json())

 

در اینجا می توانید ببینید که چرا requests بسیار محبوب است. طراحی آن بسیار زیبا است! Requests افعال HTTP را به عنوان متد (GET، POST) ترکیب می‌کند و ما حتی می‌توانیم اطلاعات را مستقیماً به JSON تبدیل کنیم. به‌عنوان یک توسعه‌دهنده، این بدان معناست که کار با آن و درک آن بسیار ساده است، تنها دو فراخوانی متد برای دریافت داده‌هایی که از API می‌خواهیم لازم است.

 

مقاله پیشنهادی: آشنایی با مفهوم باگ و دیباگ در پایتون

 

 #  کتابخانه aiohttp

AIOHTTP پکیجی است که شامل هر دو چارچوب کلاینت و سرور است، به این معنی که با آن هم میتوانید api بسازید و هم درخواست http ارسال کنید. دارای 11 هزار ستاره در Github است و تعدادی کتابخانه شخص ثالث بر روی آن ساخته شده است. درخواست GET با آن به شرح زیر است:

import aiohttp
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://swapi.dev/api/starships/9/') as response:
            print(await response.json())

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

 

و ارسال درخواست POST:

import aiohttp
import asyncio

data = {"name": "Obi-Wan Kenobi", ...}

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.post('https://httpbin.org/post', json=data) as response:
            print(await response.json())

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

 

می‌بینید که ()aiohttp.ClientSession از دستورات مشابه requests استفاده می‌کند - اما کد کلی بسیار طولانی‌تر از نمونه‌های قبلی است و اکنون فراخوانی‌های متد با استفاده از async را داریم، همراه با یک وارد کردن ماژول اضافی برای asyncio.

 

مستندات AIOHTTP نمای کلی خوبی از اینکه چرا این همه کد اضافی در مقایسه با requests ضروری است، ارائه می‌کند. اگر با مفاهیم برنامه‌نویسی ناهمزمان آشنا نباشید، درک آن‌ها زمان می‌برد، اما در نهایت معنای آن این است که می‌توان تعدادی درخواست http را به طور همزمان ارسال کرد بدون انیکه منتظر پاسخ درخواست های قبلی بمانیم. برای مواقعی که ما فقط یک درخواست داریم، ممکن است این موضوع نگران کننده نباشد، اما اگر نیاز به ارائه ده‌ها یا حتی هزاران درخواست http داشته باشیم، تمام زمانی که CPU منتظر پاسخ است بهتر است صرف انجام کار دیگری شود (مثل ایجاد درخواست دیگری). به عنوان مثال، اجازه دهید نگاهی به کدهایی بیندازیم که در جستجوی داده‌های 50 کشتی ستاره اول از Star Wars API هستند.

import aiohttp
import asyncio
import time

async def get_starship(ship_id: int):
    async with aiohttp.ClientSession() as session:
        async with session.get(f'https://swapi.dev/api/starships/{ship_id}/') as response:
            print(await response.json())

async def main():
    tasks = []
    for ship_id in range(1, 50):
        tasks.append(get_starship(ship_id))
    await asyncio.gather(*tasks)

asyncio.run(main())

 

این کد به طور مداوم کمتر از 2 ثانیه طول می کشد تا روی دستگاه من اجرا شود، در حالی که درخواست داده های مشابه با استفاده از requests، بیش از 4 ثانیه طول می کشد. بنابراین اگر بتوانیم با پیچیدگی های اضافی که به کدمان وارد می کند مقابله کنیم، می توانیم زمان بازیابی داده هایمان را تسریع کنیم.

 

مقاله پیشنهادی: همه چیز درباره متغیرهای محیطی در پایتون

 

 #  کتابخانه GRequests

GRequests ویژگی Gevent را به Requests می‌آورد تا اجازه دهد درخواست‌های http به صورت ناهمزمان انجام شوند. GRequests یک کتابخانه قدیمی است که برای اولین بار در سال 2012 منتشر شد و از ماژول استاندارد asyncio پایتون استفاده نمی کند. درخواست‌های تکی را می‌توان مانند کتابخانه Requests انجام داد، اما می‌توانیم از ماژول Gevent برای ایجاد تعدادی درخواست مانند موارد زیر استفاده کنیم:

import grequests

reqs = []

for ship_id in range(0, 50):
    reqs.append(grequests.get(f'https://swapi.dev/api/starships/{ship_id}/'))

for r in grequests.map(reqs):
    print(r.json())

 

اسناد GRequests پراکنده است و حتی تا آنجا پیش می رود که کتابخانه های دیگر را در صفحه Github خود توصیه می کند. تنها با 165 خط کد، هیچ عملکرد پیشرفته ای را نسبت به خود Requests ارائه نمی دهد. در طول 9 سال، مجموعاً 6 نسخه منتشر شده است، بنابراین اگر برنامه‌نویسی همگام را گیج‌کننده می‌دانید احتمالاً واقعاً ارزش بررسی دارد.

 

ویدیو پیشنهادی: ویدیو آموزش آبجکت ellipsis در پایتون

 

 #  کتابخانه httpx

HTTPX جدیدترین پکیج موجود در این لیست است (برای اولین بار در سال 2015 منتشر شد) و در زمان نگارش این مقاله هنوز در مرحله بتا است، نسخه 1 انتظار می رود در سال 2021 عرضه شود. httpx یک "API سازگار با درخواست های گسترده" را ارائه می دهد، تنها نمونه در اینجا برای پشتیبانی از HTTP2 و همچنین APIهای غیر همگام را ارائه می دهد.

 

استفاده از HTTPX بسیار شبیه به requests است:

import httpx

r = httpx.get('https://swapi.dev/api/starships/9/')

print(r.json())

 

و برای درخواست POST:

import httpx

data = {"name": "Obi-Wan Kenobi", ...}

r = httpx.post('https://httpbin.org/post', json=data)

print(r.json())

 

 

 #  مقایسه این کتابخانه ها

معیارهایی که ما گرفته ایم جامع نیستند و فقط به عنوان یک راهنما عمل می کنند. هر یک از مثال‌های ما از ماژول time پایتون برای اندازه گیری یک درخواست، آن را 100 بار در یک حلقه اجرا می‌کند و زمان اجرای متوسط را برمی‌گرداند. برای درخواست‌های ناهمزمان، کل زمان اجرای 100 تماسی که به‌صورت ناهمزمان انجام شده است را اندازه‌گیری می‌کنیم و میانگین هر درخواست را برمی‌گردانیم. به منظور مقایسه و نفی زمان پاسخ سرور در معیارها، هر دو درخواست GET و POST به یک نمونه محلی httpbin.org ارسال می شوند. آمار دانلود برای مقایسه ما از PePy گرفته شده است که از اطلاعات دانلود عمومی PyPI استفاده می کند.

 

Name Downloads / mo Github Stars Sync GET Sync POST Async GET Async POST
urllib N/A N/A 5.79ms 6.12ms N/A N/A
urllib3 154M 2.7k 4.13ms 4.39ms N/A N/A
Requests 115M 45.5k 6.94ms 7.41ms N/A N/A
Grequests 0.37M 3.8k 7.11ms 7.66ms 4.53ms 4.95ms
AIOHTTP 25M 11.3k 6.07ms 6.61ms 3.58ms 3.92ms
HTTPX 4M 7k 5.43ms 5.72ms 4.01ms 4.34ms

 

می‌توانیم ببینیم که از نظر عملکرد خالص برای درخواست‌های تکی، urllib3 برنده است. Requests به وضوح کتابخانه‌ای است که بیشترین جامعه را دارد و مجموعه‌ای از ویژگی‌های پیشرفته را ارائه می‌دهد که ما در این آزمایش‌های ساده محک‌گذاری نکرده‌ایم. 

 

از نظر کلاینت‌های همگام، AIOHTTP در صدر قرار می‌گیرد و زمان هر درخواست برای هر دو GET و POST کمترین است. همچنین بیشترین دانلود و ستاره را دارد، اما به خاطر داشته باشید که این ممکن است به این دلیل باشد که رفتار چارچوب وب را نیز ارائه می دهد. به دلیل عدم فعالیت در پروژه GRequests و توصیه خود آن، احتمالاً نباید آن را در نظر بگیرید مگر اینکه نیازهای بسیار خاصی داشته باشید.

 

 

 #  نتیجه گیری

در طول این مقاله دیدیم که Requests از طراحی بسیاری از کتابخانه های نشان داده شده الهام گرفته است. در جامعه پایتون بسیار محبوب است، به طوری که انتخاب پیش فرض برای اکثر توسعه دهندگان است. با ویژگی‌های اضافی که ارائه می‌کند مانند session و رفتار retry، اگر نیازهای ساده‌ای دارید یا می‌خواهید کد ساده را حفظ کنید، احتمالاً باید به آن نگاه کنید.

 

برای توسعه‌دهندگانی که نیازمندی‌های پیچیده‌تری دارند و می‌خواهند درخواست‌های بیشتری ارائه کنند - AIOHTTP در حال حاضر بهترین انتخاب است. از بین همه آزمایش‌های ما، بهترین عملکرد را به صورت ناهمزمان انجام داد، بیشترین تعداد دانلود/ستاره را دارد و در حال حاضر نسخه پایداری را ارائه می‌دهد.

مطالب مشابه



مونگارد