مقدمه
در این مقاله هدف داریم تا نحوهٔ ساخت یک لایهٔ HTTP قابلاعتماد برای اسکریپینگ با Python Requests را از صفر تا میانپیشرفته پوشش دهیم. پس از مطالعهٔ این راهنما شما یاد میگیرید چگونه درخواستهای GET و POST را اصولی ارسال کنید، User‑Agent و پروکسی را مدیریت و چرخانده، خطاها را با منطق retry کنترل کنید، کار را با concurrency مقیاسپذیر کنید و صفحات رندر شده با جاوااسکریپت را نیز استخراج نمایید. مثالها بهصورت پایتون هستند و برای توسعهدهندهای با سطح متوسط مناسباند.
ارسال سادهٔ GET و درک شیٔ Response
ایدهٔ کلی: با requests.get محتوای صفحه را میگیریم و از خواص شیٔ Response برای تصمیمگیری استفاده میکنیم.
مثال پایه:
import requests
response = requests.get('http://quotes.toscrape.com/')
print(response.status_code)
print(response.headers.get('Content-Type'))
print(response.text[:200]) # فقط ۲۰۰ کاراکتر اولورودی: یک URL (رشته). خروجی: یک شیٔ Response که شامل status_code، text، content و headers است. کاربرد عملی: قبل از پردازش HTML چک کنید که status_code برابر 200 باشد و Content-Type مورد انتظار را دارد.
نکات خطبهخط:
- خط اول: وارد کردن کتابخانهٔ requests.
- requests.get: ارسال درخواست HTTP و دریافت Response.
- response.status_code: برای تشخیص موفقیت یا خطا.
- response.text: محتوای پاسخ بهصورت یونیکد؛ برای محتوای باینری از response.content استفاده کنید.
ارسال POST — JSON و فرم
ایدهٔ کلی: برای ارسال payload از پارامترهای مناسب استفاده کنید تا Requests هدر مناسب بسازد.
ارسال JSON:
import requests
url = 'http://quotes.toscrape.com/api/example'
payload = {'key': 'value'}
resp = requests.post(url, json=payload)
print(resp.status_code, resp.json())توضیح: پارامتر json= باعث میشود که Requests بدنه را با Content-Type: application/json ارسال کند. ورودی: url و یک دیکشنری Python؛ خروجی: Response که معمولاً با resp.json() به دیکتهٔ پایتون تبدیل میشود.
ارسال فرم (application/x-www-form-urlencoded یا multipart):
import requests
url = 'http://quotes.toscrape.com/login'
form = {'username': 'joe', 'password': 'secret'}
resp = requests.post(url, data=form)
print(resp.status_code)
# برای فایل از files={'file': open('x','rb')} استفاده کنیدنکتهٔ امنیتی: هرگز کلیدهای حساس یا رمز عبور را بهصورت سختکد در کد قرار ندهید؛ از متغیرهای محیطی یا secret managers استفاده کنید.
مدیریت User‑Agent و چرخش آن
ایدهٔ کلی: هدر User-Agent یکی از پایهایترین نشانههایی است که سایتها برای شناسایی رباتها استفاده میکنند؛ بههمین دلیل باید آن را مدیریت و در سطح مقیاس بچرخانید.
تنظیم یک User‑Agent سفارشی:
import requests
headers = {"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X)"}
r = requests.get('http://quotes.toscrape.com/', headers=headers)
print(r.status_code)توضیح: ورودی headers یک دیکشنری است. Requests آن را به هدرهای HTTP تبدیل میکند. همیشه هدرهای پایه مثل Accept و Accept-Language را نیز در نظر بگیرید تا درخواست طبیعیتر بهنظر برسد.
چرخش سادهٔ User‑Agent با لیست محلی:
import requests
import random
user_agent_list = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2)...',
'Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)'
]
headers = {'User-Agent': random.choice(user_agent_list)}
r = requests.get('http://quotes.toscrape.com/', headers=headers)
print(r.status_code)معایب روش محلی: باید لیست را بهروز نگه دارید و ممکن است patternهای تکراری ایجاد کند. راه بهتر استفاده از سرویسهای بهروز User‑Agent است (مثلاً پایگاه دادهها یا APIهایی که لیستهای بهروز ارائه میدهند).
پراکسیها: چرا و چگونه
ایدهٔ کلی: پراکسیها به شما اجازه میدهند ترافیک را از آیپیهای مختلف ارسال کنید تا احتمال مسدود شدن کاهش یابد. سه روش مرسوم وجود دارد: چرخش لیست آیپی، استفاده از gateway و استفاده از proxy API.
نمونهٔ پایه برای استفاده از یک پراکسی:
import requests
proxies = {
'http': 'http://proxy.example.com:8080',
'https': 'http://proxy.example.com:8081',
}
resp = requests.get('http://quotes.toscrape.com/', proxies=proxies)
print(resp.status_code)این روش در همهٔ متدهای HTTP کار میکند ولی برای مقیاس باید پراکسیها چرخانده شوند.
Integration 1: چرخش از لیست پراکسی
ایدهٔ کلی: یک لیست از پراکسیها نگه دارید و برای هر درخواست یکی را انتخاب کنید، همچنین عملکرد هر آیپی را مانیتور کنید و پراکسیهای خراب را حذف کنید.
import requests
import random
proxy_list = [
'http://user:pass@85.237.57.198:20000',
'http://user:pass@85.237.57.198:21000',
]
proxy = random.choice(proxy_list)
proxies = {'http': proxy, 'https': proxy}
r = requests.get('http://quotes.toscrape.com/', proxies=proxies)
print(r.status_code)نکات عملی: باید مکانیزمی برای سنجش latency و نرخ خطای هر پراکسی داشته باشید و retries را با پراکسیهای دیگر انجام دهید. لاگگیری و حذف خودکار پراکسیهای با نرخ خطای بالا ضروری است.
Integration 2: استفاده از Proxy Gateway
ایدهٔ کلی: بهجای مدیریت لیست آیپیها، یک پراکسی gateway میگیرید که روی سرور ارائهدهنده مسئولیت انتخاب و چرخش آیپیها را بر عهده دارد. برای شما کافی است یک endpoint واحد را در proxies جایگذاری کنید و در صورت نیاز authentication اضافه کنید.
import requests
proxies = {
'http': 'http://zproxy.example-proxy-gateway:22225',
'https': 'http://zproxy.example-proxy-gateway:22225',
}
resp = requests.get('http://quotes.toscrape.com/', proxies=proxies, auth=('USERNAME','PASSWORD'))
print(resp.status_code)مزیت: نگهداری و بهروزرسانی لیست پراکسی بر عهدهٔ ارائهدهنده است. معایب: هزینهٔ بیشتر و احتمال ایجاد یک گلوگاه واحد (single point of failure).
Integration 3: Proxy API (ارسال URL به API)
ایدهٔ کلی: بهجای ارسال مستقیم درخواست به سایت هدف، URL را به API پراکسی میفرستید و آن API HTML آمادهشده را برمیگرداند. این روش معمولاً امکاناتی مانند رندر JS و مدیریت هدرها را هم ارائه میدهد.
import requests
from urllib.parse import urlencode
payload = {'api_key': 'APIKEY', 'url': 'http://quotes.toscrape.com/'}
r = requests.get('https://proxy.example-api.io/v1/', params=urlencode(payload))
print(r.status_code)
print(r.text[:200])نکتهٔ مهم: قبل از ارسال URL آن را URL‑encode کنید تا query params هدف با query های API مخلوط نشوند.
منطق Retry برای درخواستهای ناموفق
ایدهٔ کلی: برخی خطاها گذرا هستند؛ با داشتن یک لایه retry قابل پیکربندی میتوان قابلاطمینانتر عمل کرد. در retry باید backoff افزایشی، محدودیت تعداد تلاشها و تشخیص واضحِ خطا لحاظ شود.
مثال ساده با چک کد وضعیت و محتوای صفحه (شناسایی صفحهٔ ban):
import requests
import time
NUM_RETRIES = 3
BACKOFF = 2
response = None
for i in range(NUM_RETRIES):
try:
response = requests.get('http://quotes.toscrape.com/')
if response.status_code in (200, 404):
# بررسی محتوایی: آیا صفحهٔ ban نیست؟
if response.status_code == 200 and 'Robot or human? ' not in response.text:
break
except requests.exceptions.ConnectionError:
# خطاهای شبکه را retry کن
time.sleep(BACKOFF ** i)
continue
if response and response.status_code == 200:
# پردازش امن پاسخ
passنکات عملی:
- برای backoff از exponential یا jitter استفاده کنید تا از توالیهای همزمان retry جلوگیری شود.
- هر retry را لاگ کنید تا الگوهای مسدودی مشخص شوند.
- در صورت استفاده از پراکسی، هنگام شکست یک پراکسی را از چرخه حذف یا علامتگذاری کنید.
مقیاسگذاری با ThreadPoolExecutor
ایدهٔ کلی: استفاده از threads مناسب برای I/O-bound مثل HTTP باعث میشود زمان کل کار کاهش یابد. ThreadPoolExecutor از concurrent.futures سادهترین راه است.
import requests
from bs4 import BeautifulSoup
import concurrent.futures
NUM_THREADS = 5
list_of_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
'http://quotes.toscrape.com/page/3/',
]
output_data_list = []
def scrape_page(url):
try:
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
soup = BeautifulSoup(resp.text, 'html.parser')
title_tag = soup.find('h1')
title = title_tag.text if title_tag else None
output_data_list.append({'url': url, 'title': title})
except Exception as e:
print('Error', e)
with concurrent.futures.ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
executor.map(scrape_page, list_of_urls)
print(output_data_list)نکات و بهترین روشها:
- همیشه timeout تعیین کنید تا threadها قفل نکنند (timeout در requests).
- برای اشتراکگذاری ساختار خروجی از قفل (lock) استفاده کنید اگر عملیات نوشتن پیچیدهتر است؛ اپِ مثال ساده از append استفاده میکند که برای لیست در CPython نسبتاً ایمن است اما در حالت کلی بهتر است از صف (queue) یا locking استفاده کنید.
- تعداد threads را طوری انتخاب کنید که باعث فشار بیشازحد روی هدف یا پراکسیها نشود؛ تست و مانیتورینگ لازم است.
اسکرپ کردن صفحات رندر شده با JS
ایدهٔ کلی: Requests تنها HTML اولیه را دریافت میکند و جاوااسکریپت سمت مشتری را اجرا نمیکند؛ برای صفحات SPA باید از headless browser یا سرویس رندر استفاده کنید.
گزینهها:
- استفاده از Selenium یا Playwright/Pyppeteer برای اجرای جاوااسکریپت و سپس استخراج HTML.
- استفاده از proxy API که رندر سمت سرور انجام میدهد (پارامترهایی مانند render_js=true).
مثال استفاده از API رندر (ایدهٔ مشابه proxy API بالا):
import requests
from urllib.parse import urlencode
payload = {'api_key': 'APIKEY', 'url': 'http://quotes.toscrape.com/', 'render_js': 'true'}
r = requests.get('https://proxy.example-api.io/v1/', params=urlencode(payload))
print(r.status_code)
print(r.text[:300])نکات عملی:
- رندر JS هزینه و زمان بیشتری میبرد؛ برای هر صفحه نیازی نیست مگر دادهها تنها پس از اجرای JS در دسترس باشند.
- اگر از Selenium استفاده میکنید، از pool مدیریت مرورگرها و reuse sessionها استفاده کنید تا overhead ایجاد نشود.
بهینهسازی، امنیت و سازگاری
نکات کلیدی که در تمام مراحل باید رعایت شوند:
- احراز هویت و کلیدهای API را در متغیرهای محیطی نگه دارید و آنها را در کد نمایش ندهید.
- از timeout و retry منطقی استفاده کنید تا منابع محلی مصرف نشود.
- در صورت مقیاسگذاری، رفتار هدف را مانیتور کنید (نرخ 429، تغییرات صفحه، captchas).
- باردهی روی سرور مقصد را با نرخدهی (rate limiting) و پراکسی مناسب کنترل کنید.
- قوانین و اخلاق: قبل از اسکریپینگ سیاست robots.txt و شرایط استفاده سایت را بررسی کنید و از دادههای حساس سوءاستفاده نکنید.
جمعبندی
برای ساخت یک اسکریپر قابلاعتماد با Python Requests، روی چهار ستون اصلی کار کنید: ارسال صحیح درخواستها (GET/POST)، مدیریت هدرها و User‑Agent، استفاده هوشمندانه از پراکسیها و پیادهسازی retry/مقیاسپذیری. اگر صفحهها JS رندر میشوند از headless browser یا سرویس رندر استفاده کنید. در نهایت، مانیتورینگ، لاگگذاری و رعایت قوانین هدف، فرق بین یک پروژهٔ کوتاهمدت و یک سیستم پایدار در تولید را رقم میزند.





