خانه/مقالات/اسکریپینگ امن با Requests: رفع خطاهای SSL
برنامه نویسی
استخراج داده
برگشت به مقاله‌ها

اسکریپینگ امن با Requests: رفع خطاهای SSL

اسکریپینگ امن با Requests: رفع خطاهای SSL
این مقاله راهکارهای عملی برای شناسایی و رفع خطاهای SSL هنگام اسکریپینگ با Python Requests را معرفی می‌کند؛ از catch کردن استثناها و استفاده از certifi تا مدیریت گواهی‌های self-signed و نکات امنیتی و بهترین‌روش‌ها برای تولید.
آسان اسکریپ آسان اسکریپ
1405-05-17

مقدمه

در اسکریپینگ وب با Python، Requests پرکاربردترین کتابخانه برای ارسال HTTP است. اما هنگام ارتباط با سایت‌های HTTPS ممکن است به خطاهای SSL برخورد کنید که هم عملکرد اسکریپت را مختل می‌کنند و هم ریسک‌های امنیتی ایجاد می‌کنند. در این مقاله به‌صورت عملی و گام‌به‌گام یاد می‌گیرید که چگونه خطاهای متداول SSL را شناسایی، عیب‌یابی و با رعایت بهترین روش‌ها رفع کنید.

پس از خواندن این راهنما شما می‌توانید:

  • اصطلاحات پایهٔ SSL/TLS و مراحل handshake را توضیح دهید،
  • خطاهای متداول را در Requests تشخیص و لاگ کنید،
  • روش‌های امن رفع مشکل (به‌جای خام‌کردن اعتبارسنجی) را به‌کار ببرید،
  • برای حالت‌های تست، محیط بسته یا گواهی‌های خود-امضا راهکارهای مناسب پیاده‌سازی کنید.

نگاهی سریع (TLDR)

سریع‌ترین راه برای عبور از خطای SSL در Requests، غیر فعال‌کردن اعتبارسنجی با verify=False است، اما این کار امنیت را تضعیف می‌کند و فقط در محیط‌های کنترل‌شده یا برای تست کوتاه‌مدت مجاز است. روش‌های امن‌تر عبارت‌اند از: به‌روز کردن کتابخانه‌ها، استفاده از یک CA bundle معتبر (مثلاً با certifi)، یا ارائهٔ مسیر به گواهی مورد اعتماد.

# راه‌حل سریع (حساس به امنیت) - فقط برای تست
import warnings
import requests
import urllib3

# حذف هشدارهای InsecureRequestWarning (اختیاری، فقط برای خروجی خواناتر)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

response = requests.get('https://example.com', verify=False)
print(response.status_code)

چه ورودی دارد: URL سایت. خروجی: یک Response که وضعیت HTTP را نشان می‌دهد. خط‌به‌خط: ابتدا هشدارهای InsecureRequestWarning غیرفعال شده، سپس درخواست با اعتبارسنجی غیرفعال ارسال می‌شود. هشدار: این روش حملات MITM را ممکن می‌سازد؛ در تولید استفاده نکنید.

چطور SSL/TLS کار می‌کند (خلاصه)

برای فهم خطاها کافی است مراحل خلاصهٔ handshake را بدانید:

  1. Client Hello: کلاینت نسخهٔ TLS و cipher suiteهای مورد قبول را ارسال می‌کند.
  2. Server Hello: سرور یک cipher suite انتخاب و گواهی (certificate) خود را ارسال می‌کند.
  3. Authentication: کلاینت اعتبار گواهی، صادرکننده (CA)، و تطابق hostname را بررسی می‌کند.
  4. Key Exchange: کلیدهای session تبادل و تبادل امن کلید نهایی انجام می‌شود.
  5. Finished: پیام‌های نهایی برای تایید کامل شدن handshake رد و بدل می‌شود.

هرجا این مراحل شکست بخورد، خطاهای متفاوتی (مانند certificate verify failed یا handshake failure) ظاهر می‌شوند.

شناسایی خطاهای SSL هنگام اسکریپینگ

برای مدیریت درست خطاها، ابتدا باید آنها را ثبت و تفکیک کنید: اعتبارسنجی گواهی، تطابق hostname یا مشکلات handshake. Requests هنگام خطا استثنای requests.exceptions.SSLError را پرتاب می‌کند که باید آن را catch کنید تا تصمیم منطقی بگیرید (مثلاً retry یا لاگ جزئیات).

import requests
from requests.exceptions import SSLError

try:
    resp = requests.get('https://example.com')
except SSLError as e:
    # ورودی: URL، خروجی: لاگ خطا
    print(f"SSL Error: {e}")
else:
    if not resp.ok:
        print(f"Request failed: {resp.status_code} {resp.reason}")

این‌کد: تلاش برای GET، گرفتن SSLError و چاپ پیام. در لاگ تولیدی پیام کتابخانه شامل علت دقیق (مثلاً certificate verify failed) است که می‌تواند نشان‌دهندهٔ CA نامعتبر، منقضی بودن گواهی یا mismatch دامنه باشد.

روش 1: به‌روز نگه داشتن کتابخانه‌ها و وابستگی‌ها

اغلب خطاها با آپدیت کردن Requests و اجزای SSL حل می‌شوند چون باگ‌ها و CAهای جدید در آپدیت‌ها اضافه می‌شود.

pip install --upgrade requests certifi pyOpenSSL cryptography ndg-httpsclient

توضیح: این دستورات بسته‌ها را آپدیت می‌کنند. certifi بسته‌ای است که یک CA bundle معتبر ارائه می‌دهد و اغلب مشکل «certificate verify failed» را حل می‌کند.

روش 2: استفاده از CA bundle معتبر (مثال با certifi)

به‌جای خام‌کردن اعتبارسنجی، بهتر است از یک CA bundle قابل اعتماد استفاده کنید یا مسیر به فایل PEM گواهی CA را به Requests بدهید. این روش مخصوصاً وقتی سرور از CA جدیدی استفاده می‌کند مفید است.

import requests
import certifi

# استفاده از CA bundle ارائه‌شده توسط certifi
resp = requests.get('https://example.com', verify=certifi.where())
print(resp.status_code)

ورودی: URL. عملکرد: certifi.where() مسیر فایل cacert.pem را برمی‌گرداند و Requests از آن برای اعتبارسنجی استفاده می‌کند. مزیت: بدون غیرفعال‌کردن validation به سرور معتبر وصل می‌شوید.

اگر گواهی‌ای در قالب DER دارید، با OpenSSL آن را به PEM تبدیل کنید:

openssl x509 -inform der -in certificate.der -out certificate.pem

روش 3: استفاده از گواهی محلی یا self-signed در محیط کنترل‌شده

برای سرورهای داخلی یا محیط‌های تست که از گواهی self-signed استفاده می‌کنند، می‌توانید فایل گواهی را در پروژه نگه داشته و به Requests مسیر بدهید.

# مسیر به گواهی self-signed یا CA داخلی
cert_path = '/path/to/internal_ca.pem'
resp = requests.get('https://internal.example', verify=cert_path)

توضیح: Requests با استفاده از فایل مشخص‌شده گواهی سرور را اعتبارسنجی می‌کند. این راه امن‌تر از verify=False است و مخصوص محیط‌های کنترل‌شده توصیه می‌شود.

روش 4: بازرسی و عیب‌یابی گواهی و نام میزبان

خطای hostname verification زمانی رخ می‌دهد که نام دامنهٔ URL با مقدار CN یا SAN در گواهی تطابق نداشته باشد. برای بررسی دستی گواهی می‌توانید با openssl اطلاعات گواهی را دریافت کنید و ببینید چه SAN/CNهایی دارد:

openssl s_client -connect example.com:443 -showcerts

چک کنید که نامی که در اسکریپت استفاده می‌کنید دقیقاً با نام‌های داخل گواهی مطابقت داشته باشد. اگر mismatch است، یا از hostname صحیح استفاده کنید، یا گواهی را طوری صادر کنید که SAN شامل نام دلخواه باشد.

روش 5: زمان‌بندی retry، لاگ و fallback در اسکریپینگ

در اسکریپینگ مقادیر زیادی درخواست ارسال می‌کنید؛ پس برای پایداری سیستم بهتر است:

  • در صورت SSLError، چند بار با backoff منطقی تلاش مجدد کنید.
  • لاگ کامل شامل traceback و جزئیات SSL را ذخیره کنید تا علت مشکل واضح شود.
  • در صورت امکان از mirror یا endpoint جایگزین استفاده کنید.
from time import sleep
import requests
from requests.exceptions import SSLError

for attempt in range(3):
    try:
        r = requests.get('https://example.com', timeout=10)
        break
    except SSLError as e:
        print('SSL failed, attempt', attempt+1, e)
        sleep(2 ** attempt)
else:
    raise RuntimeError('All retries failed')

بهترین روش‌ها (Best Practices)

  • همیشه اعتبارسنجی را فعال نگه دارید مگر با دلایل قوی و مستند؛ verify=False را تنها در محیط‌های تست یا داخلی استفاده کنید.
  • کتابخانه‌ها و CA bundleها را به‌روز نگه دارید (certifi, OpenSSL).
  • گواهی‌های خود-امضا را فقط در شبکه‌های داخلی یا محیط CI کنترل‌شده قبول کنید و آن‌ها را در trust store مرکزی قرار دهید.
  • لاگینگ و مانیتورینگ handshake failures و certificate expiry را راه‌اندازی کنید تا از انقضای غیرمنتظره جلوگیری شود.
  • در اسکریپینگ از proxyهای قابل اعتماد استفاده کنید و مطمئن شوید که پروکسی SSL termination را درست مدیریت می‌کند (در صورت استفاده از پروکسی باید certificate chain پروکسی را هم مورد توجه قرار دهید).

جمع‌بندی

برای اسکریپینگ ایمن با Python Requests، ابتدا خطاها را شناسایی و لاگ کنید، سپس روش‌های امن‌تر مثل استفاده از certifi یا ارائهٔ مسیر به گواهی معتبر را به کار برید. غیر فعال‌کردن اعتبارسنجی سریع و ساده است اما امنیت را قربانی می‌کند؛ بنابراین فقط در شرایط کنترل‌شده و موقت از آن استفاده کنید. با آپدیت مرتب بسته‌ها، مانیتورینگ و مدیریت گواهی‌ها می‌توانید پایداری و امنیت اسکریپ‌های خود را حفظ کنید.

مطالب مرتبط

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