آموزش کتابخانه‌ Requests در پایتون

  ‏سطح ساده
‏  22 دقیقه
۲۷ آذر ۱۳۹۹
آموزش کتابخانه‌ Requests در پایتون

هدف از ساخت کتابخانه‌ requests در پایتون، ایجاد استانداردهایی برای ارسال و دریافت درخواست‌های مبتنی بر HTTP است. در این کتابخانه می‌توان به کمک APIهای نوشته شده برای برنامه، بر روی به‌کارگیری داده‌ها و خدمات ارائه شده توسط یک سیستم مبتنی بر وب تمرکز کرد و دیگر درگیر سایر جنبه‌ها و پیچیدگی‌های برنامه‌نویسی مرتبط با سرویس وب نشد.

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

  • درخواست یا همان ریکوئست خود را به کمک متدهای استاندارد و گوناگون HTTP ایجاد کنید.
  • به کمک رشته‌ی کوئری (query string) و بدنه‌ی پیام (message body)، قسمت سرآیند (header) و داده‌ی (data) ریکوئست را مطابق نیاز و کاربرد موردنظر خود تغییر دهید.
  • داده‌ی مورد نیاز خود را با ارسال درخواست یا همان ریکوئست request و پاسخ یا همان response، به دست بیاورید.
  • درخواست‌های معتبر (authenticated request) ارائه دهید.
  • ریکوئست‌های خود را به گونه‌ای پیکربندی کنید که از مشکلات ناشی از بک آپ‌گیری یا کاهش سرعت در امان بماند (در قسمت کارایی درخواست از API به این موضوع پرداخته‌ایم).

بهتر است برای درک بهتر مطالب، دانش اولیه و اطلاعات مختصری در خصوص بسته‌های HTTP داشته باشید.

فهرست محتوای این مقاله

شروع کار با requests

کار را با نصب کتابخانه‌ Requests پایتون شروع می‌کنیم. دستور زیر را در محیط ترمینال اجرا کنید:

$ pip install requests

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

$ pipenv install requests

پس از نصب این کتابخانه، به کمک دستور زیر می‌توانید از این کتابخانه استفاده کنید:

import requests

در ادامه، نحوه‌ی استفاده از متد GET در کتابخانه‌ requests را می‌آموزیم.

متد GET Request

متدهای HTTP مانند GET و POST، تعیین کننده‌ی عملی هستند که شما از فراخوانی درخواست HTTP انتظار دارید. علاوه بر متد GET و POST، متدهای دیگری نیز وجود دارند که در ادامه‌ی این مقاله با آن‌ها آشنا خواهیم شد. متد GET از متدهای پرکاربرد و رایج در درخواست‌های مبتنی بر وب است. این متد بیانگر این است که شما سعی در به دست آوردن یا بازیابی اطلاعات از منبع مشخصی از داده‌ها دارید. برای ایجاد GET Request از متد ()requests.get استفاده می‌کنیم. برای تست این دستور یک درخواست مبتنی بر GET برای صفحه‌ی گیت هاب ایجاد می‌کنیم:

>>> requests.get('https://api.github.com')
<Response [200]>

شما یک درخواست یا request ایجاد کرده و در مقابل یک پاسخ و یا همان response دریافت کردید. Response، یک شی برای دریافت و بررسی نتایج درخواست است. بیایید همان درخواست قبلی را تکرار کنیم و این دفعه نتیجه را در یک متغیر ذخیره کنیم تا بتوانیم نتیجه را ببینیم و ویژگی‌های آن را بررسی کنیم.

>>> response = requests.get('https://api.github.com')

در کد بالا حاصل درخواست ارسالی از طریق متد get در یک متغیر به نام response ذخیره شده است. حال می‌توانیم ویژگی‌های این متد را به شکل زیر بررسی کنیم.

کد وضعیت یا Status Codes

بیت اول به دست آمده از response، حاوی اطلاعاتی در مورد status code یا کد وضعیت است. کد وضعیت شما را از وضعیت ریکوئست باخبر می‌کند. برای مثال وضعیت 200 OK به این معناست که ریکوئست شما با موفقیت پاسخ داده شده است. کد 404 NOT FOUND بیانگر این است که منابع مورد جستجوی شما پیدا نشده‌ است. status codeهای بسیاری وجود دارند که می‌توانند نشان دهند درخواست شما چگونه پاسخی را دریافت کرده و در چه وضعیتی قرار دارد. از طریق دستور status_code. می‌توانید وضعیتی که سرور برای درخواست شما باز می‌گرداند را ببینید. به این صورت:

>>> response.status_code
200

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

if response.status_code == 200:
    print('Success!')
elif response.status_code == 404:
    print('Not Found.')

با توجه به منطق کد بالا، اگر سرور به درخواست شما پاسخ مثبت داد، در خروجی عبارت !Success و در صورت بروز خطای 404، عبارت .Not Found را چاپ می‌کند. ‌می‌توان بررسی درخواست‌ها را یک گام جلوتر برد. به‌طوریکه اگر در عبارات شرطی، کد وضعیت پاسخ درخواستی، عددی مابین 200 و 400 بود، پاسخ موفقیت آمیز ارزیابی شود و در غیر این‌صورت، پیام بروز خطا در پاسخ از سمت سرور ارسال شود. به کد زیر توجه کنید:

if response:
    print('Success!')
else:
    print('An error has occurred.')

در کد بالا وضعیت پاسخ موفقیت‌آمیز را تنها حالتی در نظر نگرفته‌ایم که status code برابر 200 باشد. این کار به این دلیل است که اعداد مابین 200 و 400 نیز جزو درخواست‌های موفقیت‌آمیز هستند. برای مثال 204 یعنی درخواست موفقیت‌آمیز بوده ولی محتوایی در سرور برای بازگشت در بدنه‌ی پیام وجود ندارد (NO CONTENT) و 304 یعنی محتوا تغییر داده نشده است (NO MODIFIED). بنابراین، فقط اگر می‌خواهید بدانید که آیا درخواست به طور کلی موفقیت‌آمیز بوده است یا خیر، می‌توانید از این کد ساده استفاده کنید و سپس در صورت لزوم، پاسخ را بر اساس کد وضعیت به طور مناسب مدیریت کنید.

حال فرض کنید که نمی‌خواهیم از عبارت شرطی if استفاده کنیم و به جای آن از کنترل استثنا یا همان raise exception در برنامه استفاده کنیم. این کار را می‌توان به کمک ()raise_for_status. انجام داد. به کد زیر توجه کنید:

import requests
from requests.exceptions import HTTPError

for url in ['https://api.github.com', 'https://api.github.com/invalid']:
    try:
        response = requests.get(url)

        # If the response was successful, no Exception will be raised
        response.raise_for_status()
    except HTTPError as http_err:
        print(f'HTTP error occurred: {http_err}')  # Python 3.6
    except Exception as err:
        print(f'Other error occurred: {err}')  # Python 3.6
    else:
        print('Success!')

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

 

برای مطالعه‌ی بیشتر: تاپل‌ها، مجموعه‌ها و دیکشنری‌ها در پایتون - آموزش مقدماتی پایتون - قسمت 10

محتوا یا Content

پاسخ دریافتی برای یک درخواست از نوع GET شامل اطلاعاتی ارزشمند است. با استفاده از متدها و صفات تعریف شده برای Response می‌توانید payloadها را در گستره‌ی وسیعی از فرمت‌ها ببینید. برای دیدن محتوای پاسخ (response content) از content. استفاده می‌کنیم.

>>> response = requests.get('https://api.github.com')
>>> response.content
b'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'

از آنجایی که content. قسمت payload پاسخ را در قالب بایت‌های خام نشان می‌دهد، می‌توانید با استفاده از رمزگذاری کاراکترها با روش UTF-8 آن را در قالب رشته (string) نمایش دهید. اگر نوع رمزگذاری داده‌ها مشخص نباشد، تنها با متد text. می‌توان خروجی را به شکل متنی دید.

>>> response.text
'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'

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

>>> response.encoding = 'utf-8' # Optional: requests infers this internally
>>> response.text
'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'

اگر در محتوای پاسخ دقت کنید متوجه خواهید شد که در اصل خروجی content در فرمت JSON است. برای تبدیل به فرمت دیکشنری می‌توان خروجی حاصل از str. را با کمک ()json.loads به شکل دیکشنری دربیاوریم. البته ساده‌ترین روش برای این کار استفاده از تابع ()json. است.

>>> response.json()
{'current_user_url': 'https://api.github.com/user', 'current_user_authorizations_html_url': 'https://github.com/settings/connections/applications{/client_id}', 'authorizations_url': 'https://api.github.com/authorizations', 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', 'commit_search_url': 'https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}', 'emails_url': 'https://api.github.com/user/emails', 'emojis_url': 'https://api.github.com/emojis', 'events_url': 'https://api.github.com/events', 'feeds_url': 'https://api.github.com/feeds', 'followers_url': 'https://api.github.com/user/followers', 'following_url': 'https://api.github.com/user/following{/target}', 'gists_url': 'https://api.github.com/gists{/gist_id}', 'hub_url': 'https://api.github.com/hub', 'issue_search_url': 'https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}', 'issues_url': 'https://api.github.com/issues', 'keys_url': 'https://api.github.com/user/keys', 'notifications_url': 'https://api.github.com/notifications', 'organization_repositories_url': 'https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}', 'organization_url': 'https://api.github.com/orgs/{org}', 'public_gists_url': 'https://api.github.com/gists/public', 'rate_limit_url': 'https://api.github.com/rate_limit', 'repository_url': 'https://api.github.com/repos/{owner}/{repo}', 'repository_search_url': 'https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}', 'current_user_repositories_url': 'https://api.github.com/user/repos{?type,page,per_page,sort}', 'starred_url': 'https://api.github.com/user/starred{/owner}{/repo}', 'starred_gists_url': 'https://api.github.com/gists/starred', 'team_url': 'https://api.github.com/teams', 'user_url': 'https://api.github.com/users/{user}', 'user_organizations_url': 'https://api.github.com/user/orgs', 'user_repositories_url': 'https://api.github.com/users/{user}/repos{?type,page,per_page,sort}', 'user_search_url': 'https://api.github.com/search/users?q={query}{&page,per_page,sort,order}'}

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

سرآیند یا Header

قسمت سرآیند اطلاعات زیادی در مورد نوع داده‌ی موجود در قسمت payload پاسخ و محدودیت زمانی برای مدت cashe کردن پاسخ را به شما ارائه می‌دهد. برای این منظور از تابع header. استفاده می‌کنیم:

>>> response.headers
{'Server': 'GitHub.com', 'Date': 'Mon, 10 Dec 2018 17:49:54 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Status': '200 OK', 'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '59', 'X-RateLimit-Reset': '1544467794', 'Cache-Control': 'public, max-age=60, s-maxage=60', 'Vary': 'Accept', 'ETag': 'W/"7dc470913f1fe9bb6c7355b50a0737bc"', 'X-GitHub-Media-Type': 'github.v3; format=json', 'Access-Control-Expose-Headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload', 'X-Frame-Options': 'deny', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block', 'Referrer-Policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'Content-Security-Policy': "default-src 'none'", 'Content-Encoding': 'gzip', 'X-GitHub-Request-Id': 'E439:4581:CF2351:1CA3E06:5C0EA741'}

این متد یک شی مشابه ساختار داده‌ی دیکشنری بر می‌گرداند که می‌توانید از طریق کلید به مقادیر آن دسترسی پیدا کنید. در کد زیر نوع محتوا و مقدار payload را با کلید Content-Type مشاهده می‌کنید:

>>> response.headers['Content-Type']
'application/json; charset=utf-8'

از موارد خاص درخصوص قسمت سرآیند این شی دیکشنری مانند، این است که نام کلیدها در آن case-insensitive است؛ به این معنا که حساس به حروف بزرگ و کوچک نیست. یعنی دستور کد بالا را می‌توان به شکل زیر نیز نوشت و شما مقدار کلید را چه به صورت Content-Type و چه به صورت content-type بنویسید، در هر دو صورت نتیجه یکسان خواهد بود:

>>> response.headers['content-type']
'application/json; charset=utf-8'

اکنون اصول مربوط به Response ریکوئست GET را آموخته‌اید و مفیدترین ویژگی‌ها و متدهای آن را در عمل مشاهده کرده‌اید. بیایید یک قدم به عقب برگردیم و ببینیم که هنگام سفارشی کردن درخواست‌های GET، پاسخ‌های شما چگونه تغییر می‌کند.

پارامترهای Query String

یک روش متداول برای سفارشی کردن درخواست مبتنی بر GET، ارسال مقادیر در خلال پارامترهای query string در URL است. برای انجام این کار با استفاده از متد ()get، داده را به آرگومان params می‌دهیم. برای مثال، از Search API سایت GitHub در کتابخانه‌ requests به شکل زیر استفاده می‌کنیم:

import requests

# Search GitHub's repositories for requests
response = requests.get(
    'https://api.github.com/search/repositories',
    params={'q': 'requests+language:python'},
)

# Inspect some attributes of the `requests` repository
json_response = response.json()
repository = json_response['items'][0]
print(f'Repository name: {repository["name"]}')  # Python 3.6+
print(f'Repository description: {repository["description"]}')  # Python 3.6+

در کد بالا، شما با ارسال دیکشنری {'q': 'requests+language:python'} به پارامتر params با استفاده از متد ()get.، قادر به سفارشی سازی نتیجه‌ی حاصل از به کارگیری این API و تغییر آن متناسب با کاربرد مورد نیاز خود هستید. همچنین می‌توانید علاوه بر ارسال مقدار به شکل دیکشنری، به شکل لیست نیز آرگومان params را مقداردهی کنید. به کد زیر توجه کنید:

>>> requests.get(
...     'https://api.github.com/search/repositories',
...     params=[('q', 'requests+language:python')],
... )
<Response [200]>

همچنین مقادیر از نوع بایت را نیز می‌توان به پارامتر params ارسال کرد:

>>> requests.get(
...     'https://api.github.com/search/repositories',
...     params=b'q=requests+language:python',
... )
<Response [200]>

سرآیند مربوط به پاسخ: Response Headers

برای سفارشی کردن قسمت سرآیند مطابق با نیاز خود، می‌توانید مقادیر را به شکل ساختار داده‌ی دیکشنری با استفاده از پارامتر header به تابع ()get. ارسال کنید. برای مثال در درخواست جستجوی قبلی با استفاده از text-match در قسمت Accept می‌توانید بخش‌های مورد نظر خود را در نتیجه‌ی حاصل، برجسته نمایید. به کد زیر توجه کنید:

import requests

response = requests.get(
    'https://api.github.com/search/repositories',
    params={'q': 'requests+language:python'},
    headers={'Accept': 'application/vnd.github.v3.text-match+json'},
)

# View the new `text-matches` array which provides information
# about your search term within the results
json_response = response.json()
repository = json_response['items'][0]
print(f'Text matches: {repository["text_matches"]}')

قسمت Accept در سرآیند به سرور می‌گوید که اپلیکیشن شما چه نوع داده‌ای را می‌تواند بپذیرد. در کد قبلی در قسمت Accept، بیان شده که داده را به شکل json می‌پذیرد.

پیش از ادامه‌ی مطلب بهتر است کمی با سایر متدهای HTTP آشنا شویم.

سایر متدهای HTTP

پروتکل HTTP علاوه بر متد GET، متدهای معروف و کاربردی دیگری مانند POST ،PUT ،DELETE ،HEAD ،PATCH و OPTIONS دارد. همانند متدها و ویژگی‌هایی که برای ()get در کتابخانه‌ requests وجود دارد، برای این متدها نیز موارد مشابه تعریف شده است. به دستورات کد زیر توجه کنید:

>>> requests.post('https://httpbin.org/post', data={'key':'value'})
>>> requests.put('https://httpbin.org/put', data={'key':'value'})
>>> requests.delete('https://httpbin.org/delete')
>>> requests.head('https://httpbin.org/get')
>>> requests.patch('https://httpbin.org/patch', data={'key':'value'})
>>> requests.options('https://httpbin.org/get')

هر تابع فراخوانی درخواستی را به سرویس httpbin متناظر با متد HTTP ارسال می‌کند. برای هر متد، می‌توانید پاسخ دریافتی را مطابق آنچه تاکنون آموخته‌ایم، بررسی کنید:

>>> response = requests.head('https://httpbin.org/get')
>>> response.headers['Content-Type']
'application/json'

>>> response = requests.delete('https://httpbin.org/delete')
>>> json_response = response.json()
>>> json_response['args']
{}

سرآیند، بدنه‌ی پاسخ و کد وضعیت برای تمامی متدها در قسمت پاسخ دریافتی وجود دارد. در ادامه با متدهای PUT ،POST و PATCH بیشتر آشنا می‌شویم و تفاوت آن‌ها را بهتر درک می‌کنیم.

بدنه ی پیام: Message Body

مطابق پروتکل HTTP، درخواست‌های مبتنی بر متد POST ،PUT و همچنین درخواست‌های مبتننی بر متد PATCH، که رواج کمتری در مقایسه با دو متد قبلی دارد، داده‌های خود را به جای اینکه در داخل مقادیر پارامترهای رشته‌ای ارسال کنند، در بدنه‌ی پیام ارسال می‌کنند. برای این منظور در کتابخانه‌ی payload ،requests مورد نظر را به عنوان پارامتر به تابع data ارسال می‌کند. ورودی تابع data می‌تواند به صورت دیکشنری، لیست، تاپل، بایت و یا حتی یک آبجکت به شکل فایل باشد. هدف این است که داده‌ای که شما در بدنه‌ی پیام ارسال می‌کنید، متناسب با نیازمندی‌های لازم برای ایجاد سرویس مورد نظر از سرور ارائه دهنده‌ی آن سرویس باشد.

برای مثال اگر نوع ریکوئست شما application/x-www-form-urlencoded باشد، فرم داده‌ی ارسالی می‌تواند به شکل دیکشنری باشد. کد زیر را در نظر بگیرید:

>>> requests.post('https://httpbin.org/post', data={'key':'value'})
<Response [200]>

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

>>> requests.post('https://httpbin.org/post', data=[('key', 'value')])
<Response [200]>

اگر داده‌ای که می‌خواهید ارسال کنید به شکل فایل JSON باشد، می‌توانید از پارامتر json استفاده کنید. زمانی‌که فایل JSON را با پارامتر json ارسال می‌کنید، requests داده‌ی شما را مرتب‌سازی می‌کند و Content-Type صحیح را به سرآیند شما می‌افزاید.

سایت httpbin که توسط نویسنده‌ی کتابخانه‌ requests ایجاد شده است، یک منبع بسیار خوب برای بررسی متدهای HTTP است. در این سایت، سرویس ارائه شده درخواست‌های تست را دریافت کرده و پاسخ متناسب با آن را برمی‌گرداند. در کد زیر به منظور بررسی ریکوئست مبتنی بر POST از این سایت استفاده کرده‌ایم.

>>> response = requests.post('https://httpbin.org/post', json={'key':'value'})
>>> json_response = response.json()
>>> json_response['data']
'{"key": "value"}'
>>> json_response['headers']['Content-Type']
'application/json'

با توجه به خروجی‌ها می‌توانید درک درستی از این داشته باشید که سرور ورودی شما را که شامل داده و سرآیند ارسالی‌تان می‌باشد، دریافت کرده و پاسخ داده است. همچنین در کتابخانه‌ requests می‌توانید این اطلاعات را به شکل PreparedRequest به دست آورید.

بررسی دقیق درخواست

وقتی که یک درخواست یا Request ارسال می‌کنید، کتابخانه‌ی requesets پیش از ارسال به سرور مقصد آن را بازرسی و آماده‌سازی می‌کند. آماده‌سازی ریکوئست شامل مواردی چون اعتبارسنجی سرآیند و مرتب‌سازی محتوای فایل JSON است. عملیات PreparedRequest یا آماده‌سازی درخواست به شکل کد زیر است:

>>> response = requests.post('https://httpbin.org/post', json={'key':'value'})
>>> response.request.headers['Content-Type']
'application/json'
>>> response.request.url
'https://httpbin.org/post'
>>> response.request.body
b'{"key": "value"}'

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

احراز هویت

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

یک مثال از APIهایی که نیاز به احراز هویت دارد، API احراز هویت کاربر گیت هاب (GitHub’s Authenticated User API) است. این API، اطلاعاتی در خصوص مشخصات کاربر تأیید شده ارائه می‌دهد. برای ایجاد یک درخواست به این API، می‌توانید نام کاربری و رمز عبور خود در سایت GitHub را به تابع ()get ارسال کنید:

>>> from getpass import getpass
>>> requests.get('https://api.github.com/user', auth=('username', getpass()))
<Response [200]>

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

>>> requests.get('https://api.github.com/user')
<Response [401]>

احراز هویت را می‌توان به شیوه‌ی زیر با ارسال پارامترها در تابع HTTPBasicAuth نیز انجام داد:

>>> from requests.auth import HTTPBasicAuth
>>> from getpass import getpass
>>> requests.get(
...     'https://api.github.com/user',
...     auth=HTTPBasicAuth('username', getpass())
... )
<Response [200]>

روش‌های غیر صریح دیگری مانند HTTPDigestAuth و HTTPProxyAuth برای احراز هویت در کتابخانه‌ requests تعریف شده است. حتی می‌توانید مکانیزم اعتبارسنجی خود را نیز ایجاد کنید. برای انجام این کار ابتدا باید زیرکلاس AuthBase را ایجاد کنید. سپس دستور __()call__ را اجرا کنید:

import requests
from requests.auth import AuthBase

class TokenAuth(AuthBase):
    """Implements a custom authentication scheme."""

    def __init__(self, token):
        self.token = token

    def __call__(self, r):
        """Attach an API token to a custom auth header."""
        r.headers['X-TokenAuth'] = f'{self.token}'  # Python 3.6+
        return r


requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))

در کد بالا مکانیزم احراز هویت ساخته شده توسط شما (کلاس TokenAuth) یک توکن را به عنوان ورودی دریافت می‌کند. سپس این توکن را در قسمت X-TokenAuth سرآیند قرار می‌دهد. در نظر داشته باشید که مکانیزم احراز هویت نادرست ممکن است منجر به آسیب‌پذیری‌های امنیتی شود. از این رو اگر سرویسی نیاز به ساخت مکانیزم احراز هویت سفارشی و خاص نداشته باشد، بهتر است از روش‌های احراز هویت پایه که در کدهای قبلی دیدیم، استفاده کنید. مسأله‌ی دیگری که در احراز هویت درخواست سرویس می‌تواند بیان شود، گواهینامه‌ی SSL است.

تأیید گواهی SSL

هر زمان که داده‌های ارسالی یا دریافتی شما از حساسیت بالایی برخوردار باشند، امنیت داده‌ها به یک مسأله‌ی مهم تبدیل می‌شود. راه برقراری ارتباط با سایت‌های امن از طریق HTTP، ایجاد ارتباط رمزگذاری شده با استفاده از SSL است. به این معنی که تأیید گواهی SSL سرور هدف بسیار مهم است. خبر خوب این است که کتابخانه‌ requests به طور پیش فرض این کار را برای شما انجام می‌ دهد. با این حال، مواردی وجود دارد که ممکن است بخواهید این رفتار را تغییر دهید. اگر می‌خواهید تأیید گواهی SSL را غیرفعال کنید، مقدار پارامتر verify را همانند کد زیر false قرار دهید.

>>> requests.get('https://api.github.com', verify=False)
InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning)
<Response [200]>

همانطور که در نتیجه‌ی اجرای کد بالا مشاهده می‌کنید، هشداری به شما درخصوص ناامن بودن ارسال اطلاعات در صورت غیر فعال کردن SSL ارسال خواهد شد.

کارآیی

بحث کارآیی یا performance به خصوص هنگام استفاده از کتابخانه‌ requests در برنامه‌های تجاری و تولیدی، مهم است. ویژگی‌هایی مانند کنترل مهلت زمانی، نشست یا sessionها و محدودیت‌های تلاش مجدد، در اجرای روان برنامه‌ها‌ی نرم‌افزاری تأثیرگذار است.

مهلت زمانی یا Timeout

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

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

>>> requests.get('https://api.github.com', timeout=1)
<Response [200]>
>>> requests.get('https://api.github.com', timeout=3.05)
<Response [200]>

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

>>> requests.get('https://api.github.com', timeout=(2, 5))
<Response [200]>

در کد بالا بالا به مدت 2 ثانیه برای ایجاد اتصال بین درخواست و سرور ارائه دهنده‌ی سرویس، منتظر می‌مانیم و اگر در طی این دوثانیه اتصالی ایجاد شد، حداکثر پنج ثانیه منتظر دریافت پاسخ می‌مانیم. اگر به هر دلیلی پاسخی به درخواست ما داده نشود، کتابخانه‌ requests پیغام استثنای Timeout را اجرا می‌کند.

import requests
from requests.exceptions import Timeout

try:
    response = requests.get('https://api.github.com', timeout=1)
except Timeout:
    print('The request timed out')
else:
    print('The request did not time out')

شی مربوط به نشست یا The Session Object

تا اینجا با توابع درخواست API در سطح بالا همچون ()get و ()post سروکار داشتید. این توابع یک انتزاع سطح بالا فراهم می‌کنند به گونه‌ای که شما با بسیاری از عملیاتی که در سطح پایین‌تر، هنگام ارسال یک درخواست به API رخ می‌دهد، درگیر نشوید. در نتیجه نیازی نیست که شما در مورد چگونگی مدیریت ارتباط با API نگران باشید. در لایه‌های پایین و در زیر انتزاع ایجاد شده، کلاسی تحت عنوان Session وجود دارد. اگر نیاز به تنظیم یا بهبود وضعیت درخواست خود دارید، ممکن است لازم باشد که با این کلاس به طور مستقیم کار کنید. نشست یا سشن برای ماندگاری پارامترها در میان درخواست‌ها به کار می‌رود. برای مثال اگر بخواهید احراز هویت ایجاد شده را در طول چندین درخواست معتبر نگه داشته و به کار ببرید، باید از سشن استفاده کنید. به کد زیر توجه کنید:

import requests
from getpass import getpass

# By using a context manager, you can ensure the resources used by
# the session will be released after use
with requests.Session() as session:
    session.auth = ('username', getpass())

    # Instead of requests.get(), you'll use session.get()
    response = session.get('https://api.github.com/user')

# You can inspect the response just like you did before
print(response.headers)
print(response.json())

در هر مرتبه که درخواستی با session ایجاد می‌کنید، اعتبارسنجی اولیه در طول درخواست‌ها، معتبر باقی می‌ماند. بهینه سازی عملکرد اولیه‌ی جلسات به شکل اتصالات مداوم است. زمانی که برنامه‌ی شما اتصالی با سرور از طریق Session ایجاد می‌کند، این اتصال باقی می‌ماند و هر زمان که برنامه‌ی شما بخواهد مجدداً درخواستی را به سرور ارسال کند، دوباره از همان اتصال قبلی استفاده کرده و اتصال جدیدی ایجاد نمی‌کند.

بیشینه‌ی تلاش مجدد: Max Retries

زمانی که به هر دلیلی درخواست شما نتیجه‌‌ی موفقیت‌آمیزی نداشته باشد، آن را تکرار می‌کنید. اما کتابخانه‌ requests این کار را به صورت پیش‌فرض انجام نمی‌دهد. برای انجام این کار نیاز به یک Transport Adapter دارید.

Transport Adapter امکان تنظیم مجموعه‌ای از پیکربندی‌ها را برای سرویسی که می‌خواهید از ان استفاده کنید، فراهم می‌کند. برای مثال فرض کنید می‌خواهید از API گیت‌ هاب پیش از آنکه خطا در اتصال (Connection Error) رخ دهد، سه مرتبه درخواست خود را تکرار کنید. یک Transport Adapter ایجاد می‌کنیم و پارامتر max_retries آن را به Seesion فعلی، اضافه می‌کنیم:

import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import ConnectionError

github_adapter = HTTPAdapter(max_retries=3)

session = requests.Session()

# Use `github_adapter` for all requests to endpoints that start with this URL
session.mount('https://api.github.com', github_adapter)

try:
    session.get('https://api.github.com')
except ConnectionError as ce:
    print(ce)

زمانی که HTTPAdapter و github_adapter را به Session افزودید، Session در هر درخواستی که به API ارسال می‌کند، به تنظیمات خود متصل می‌شود و همان درخواست را تا زمان Max Retries تکرار می‌کند.

 

برای مطالعه‌ی بیشتر: آموزش کتابخانه Tkinter در پایتون به همراه مثال کاربردی

 

جمع بندی:

در این مقاله به معرفی کتابخانه‌ی کاربردی requests پایتون پرداختیم. حال شما قادر هستید تا:

  • درخواست‌های خود را با متدهای مختلف پروتکل HTTP مانند GET ،POST و... ارسال کنید.
  • درخواست‌های خود را با تغییر سرآیندها، احراز هویت، query string و بدنه‌ی پیام، مطابق نیاز خود تغییر دهید.
  • داده‌های ارسالی و دریافتی خود از سرور را بررسی کنید.
  • با تصدیق گواهی SSL کار کنید.
  • درخواست‌های خود را با مدیریت نشست بهینه‌سازی کنید.

با استفاده از این کتابخانه می‌توانید در دنیای وب از خدمات سایر برنامه‌ها از طریق APIها استفاده کنید و گستره‌ی وسیعی از کاربردها را برای اپلیکیشن خود پیاده‌سازی نمایید.

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

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

چه امتیازی به این مقاله می دید؟
نویسنده المیرا ناصح
ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :

 

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !