خانه/مقالات/اسکریپینگ با Python hRequests: نکات عملی
سلنیوم
پروکسی و چرخش IP
ضد بلاک (Anti-bot)
برگشت به مقاله‌ها

اسکریپینگ با Python hRequests: نکات عملی

اسکریپینگ با Python hRequests: نکات عملی
این مقاله یک راهنمای عملی برای اسکریپینگ با Python hRequests است که نحوه ارسال GET/POST، مدیریت User-Agent، استفاده و روتیشن پراکسی، پیاده‌سازی retry با backoff، افزایش کارایی با ThreadPoolExecutor و راهکارهای رندر جاوااسکریپت را با مثال‌های کد و نکات فنی توضیح می‌دهد.
آسان اسکریپ آسان اسکریپ
1405-05-07

مقدمه

این مقاله یک راهنمای عملی برای ساخت کلاینت‌های HTTP سریع و مقاوم با Python hRequests است. هدف این است که بعد از خواندن این مطلب بتوانید درخواست‌های GET و POST را به‌درستی ارسال کنید، User-Agent و پراکسی را مدیریت کنید، خطاها را با استراتژی retry هندل کنید و اسکریپرهای خود را با هم‌زمانی مقیاس‌پذیر کنید. در پایان بخش‌های عملی و مثال‌های کد با توضیح ورودی‌ها، خروجی‌ها و نقش هر تابع خواهید دید.

ارسال درخواست GET — پایه و اصول

ایده کلی: با hrequests.get HTML یا JSON را از سرور دریافت می‌کنیم و با استفاده از ویژگی‌های شیء Response بررسی و پردازش می‌کنیم.

import hrequests

response = hrequests.get('http://quotes.toscrape.com/')
print(response.text[:200])  # نمایش اولین 200 کاراکتر

توضیحات:

  • ورودی: یک URL (رشته) و اختیاری پارامترهایی مثل headers، params یا proxies.
  • خروجی: یک شیء Response که ویژگی‌هایی مثل status_code، text، content و headers دارد.
  • نکته خط‌به‌خط: hrequests.get یک درخواست HTTP ارسال می‌کند، سرور پاسخ می‌دهد و متن پاسخ در response.text قرار می‌گیرد.

ویژگی‌های مهم Response (خلاصه):

  1. status_code: کد HTTP
  2. text: محتوای یونیکد
  3. content: محتوای بایت
  4. headers: هدرهای پاسخ
  5. url, encoding, cookies, history, ok, elapsed

ارسال درخواست POST (JSON و فرم)

ایده کلی: برای ارسال داده‌ها از متد post استفاده می‌کنیم و برای JSON از پارامتر json و برای فرم از data.

import hrequests

url = 'https://example.com/api'
payload = {'key': 'value'}
# ارسال JSON
r = hrequests.post(url, json=payload)
print(r.status_code, r.json())

توضیحات:

  • پارامتر json باعث می‌شود هِدر Content-Type به "application/json" ست شود و داده به صورت JSON سریالایز شود.
  • پارامتر data برای فرم‌های سنتی کاربرد دارد (application/x-www-form-urlencoded یا multipart برای فایل).
  • همیشه پاسخ سرور را با status_code و در صورت نیاز با r.json() یا بررسی r.text کنترل کنید.

مدیریت User-Agent و روتیشن

چرا مهم است: User-Agent یکی از سیگنال‌های ساده‌ای است که سرورها برای تشخیص بات‌ها استفاده می‌کنند. مقادیر پیش‌فرض کلاینت‌ها معمولاً قابل شناسایی هستند، بنابراین باید آن را مدیریت کنیم.

تنظیم یک User-Agent ساده:

import hrequests

headers = {"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X)"}
r = hrequests.get('http://quotes.toscrape.com/', headers=headers)
print(r.status_code)

توضیح: ورودی این تابع یک دیکشنری headers است و آن را به عنوان هدر درخواست ارسال می‌کند. خروجی همان Response است.

چرخش User-Agent (simple rotation):

import hrequests
import random

user_agent_list = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
    'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 ...) ...',
]
headers = {"User-Agent": random.choice(user_agent_list)}
r = hrequests.get('http://quotes.toscrape.com/', headers=headers)
print(r.status_code)

نکات عملی و best practices:

  • به جای نگهداری استاتیک یک لیست قدیمی، از سرویس‌های به‌روز کننده User-Agent یا یک دیتابیس معتبر استفاده کنید.
  • همیشه User-Agent را همراه با تغییرات دیگر (پراکسی، تأخیر یکنواخت) ترکیب کنید تا الگوهای قابل تشخیص را کاهش دهید.

پراکسی‌ها — چرا و چطور

هدف: پراکسی‌ها به شما اجازه می‌دهند درخواست‌ها را از IPهای مختلف بفرستید تا احتمال بلاک کاهش یابد. سه مدل متداول وجود دارد که در ادامه برای هرکدام مثال و مزایا/معایب می‌آوریم.

پراکسی نوع 1: لیست IP و چرخش محلی

ایده: سرویس دهنده یک لیست از IPها به شما می‌دهد و شما برای هر درخواست یک IP تصادفی انتخاب می‌کنید.

import hrequests
import random

proxy_list = [
    'http://Username:Password@85.237.57.198:20000',
    'http://Username:Password@85.237.57.198:21000',
]
proxy = random.choice(proxy_list)
proxies = {"http": proxy, "https": proxy}
r = hrequests.get('http://quotes.toscrape.com/', proxies=proxies)
print(r.status_code)

مزایا/معایب:

  • مزایا: کنترل کامل روی IPها و منطق روتیشن.
  • معایب: نیاز به مانیتورینگ و حذف IPهای بد/کم‌کیفیت؛ کار زیاد برای نگهداری.

پراکسی نوع 2: پروکسی گیت‌وی (Gateway)

ایده: شما یک endpoint ثابت دارید و ارائه‌دهنده خودش روتیشن، تعویض IP و سلامت‌سنجی را انجام می‌دهد. در درخواست شما فقط یک proxy تعریف می‌شود و معمولاً نیاز به احراز هویت است.

import hrequests

proxies = {
    'http': 'http://zproxy.lum-superproxy.io:22225',
    'https': 'http://zproxy.lum-superproxy.io:22225',
}
# auth tuple اگر لازم باشد
r = hrequests.get('http://quotes.toscrape.com/', proxies=proxies, auth=('USERNAME','PASSWORD'))
print(r.status_code)

مزایا/معایب:

  • مزایا: ساده، مناسب برای استفاده از پراکسی‌های residential یا mobile بدون مدیریت دستی.
  • معایب: هزینه بالاتر و وابستگی به کیفیت سرویس‌دهنده.

پراکسی نوع 3: API پراکسی (Smart Proxy API)

ایده: به یک API می‌فرستید (پارامتر URL و API key) و خود سرویس HTML پردازش‌شده یا proxied response را برمی‌گرداند. مناسب وقتی می‌خواهید سربار مدیریت پراکسی را حذف کنید.

import hrequests

params = {'api_key': 'APIKEY', 'url': 'http://quotes.toscrape.com/'}
r = hrequests.get('https://proxy.example.com/v1/', params=params)
print(r.text[:200])

نکات امنیتی: کلیدهای API را در محیط (ENV) یا secret manager نگه دارید و آنها را در کد ثابت قرار ندهید.

Retry و مدیریت خطاها

ایده کلی: درخواست‌ها ممکن است به خاطر شبکه یا بلاک شدن شکست بخورند. برای افزایش پایداری از مکانیسم retry با backoff استفاده کنید.

import time
import hrequests
import random

NUM_RETRIES = 4
BACKOFF_BASE = 1  # ثانیه
url = 'http://quotes.toscrape.com/'
response = None
for attempt in range(1, NUM_RETRIES + 1):
    try:
        response = hrequests.get(url, timeout=10)
        if response.status_code == 200 and 'Robot or human?' not in response.text:
            break
        # اگر کد 404 هم قابل قبول است، می‌توانید آن را قبول کنید
    except Exception as e:
        # لاگ کردن خطا و ادامه
        pass
    sleep_time = BACKOFF_BASE * (2 ** (attempt - 1)) + random.uniform(0, 0.5)
    time.sleep(sleep_time)

if response and response.status_code == 200:
    # پردازش موفق
    pass
else:
    # رسیدگی به شکست نهایی
    pass

توضیحات:

  • این الگو از exponential backoff به اضافه یک جرقه تصادفی استفاده می‌کند تا فشار روی سرور کم شود.
  • در بررسی پاسخ علاوه بر کد می‌توانید محتوای HTML را برای نشانه‌های صفحه بن شود (مانند متن «Robot or human?») بررسی کنید.

هم‌زمانی و مقیاس‌پذیری با ThreadPoolExecutor

ایده کلی: با استفاده از ThreadPoolExecutor می‌توان درخواست‌ها را به‌صورت هم‌زمان ارسال کرد تا زمان کل اجرا کاهش یابد. این روش برای IO-bound (مثل HTTP) بسیار مؤثر است.

import hrequests
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/',
]
output_data_list = []

def scrape_page(url):
    try:
        r = hrequests.get(url, timeout=10)
        if r.status_code == 200:
            soup = BeautifulSoup(r.text, 'html.parser')
            title = soup.find('h1').text if soup.find('h1') else ''
            return {'url': url, 'title': title}
    except Exception as e:
        return {'url': url, 'error': str(e)}

with concurrent.futures.ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
    results = executor.map(scrape_page, list_of_urls)
    for item in results:
        output_data_list.append(item)

print(output_data_list)

نکات عملی:

  • برای IO-bound کارها، threading بهتر از پردازش‌های سنگین CPU است.
  • با افزایش تعداد نخ‌ها، بار روی سرور مقصد، محدودیت‌های شبکه و مصرف حافظه را زیر نظر داشته باشید.
  • ترکیب هم‌زمانی با پراکسی و روتیشن UA برای اجتناب از بلاک مهم است.

صفحات JS-rendered و راهکارها

مشکل: hRequests فقط HTML اولیه را برمی‌گرداند و JavaScript را اجرا نمی‌کند؛ بنابراین اگر سایت با SPA ساخته شده باشد داده‌ها پس از لود اولیه در سمت کلاینت تولید می‌شوند.

گزینه‌ها:

  • استفاده از headless browser مانند Selenium یا Pyppeteer برای رندر جاوااسکریپت و دریافت HTML نهایی.
  • استفاده از سرویس پراکسی/API که رندر JS را برای شما انجام می‌دهد (پارامتری مانند render_js=true به API می‌فرستید).
# نمونه ساده: درخواست به یک Proxy API که قابلیت رندر JS دارد
import hrequests
params = {'api_key': 'APIKEY', 'url': 'http://quotes.toscrape.com/', 'render_js': 'true'}
r = hrequests.get('https://proxy.example.com/v1/', params=params)
print(r.text[:300])

نکات: رندر کردن JS هزینه‌بر است و نرخ درخواست مجاز را کاهش می‌دهد؛ بنابراین فقط برای صفحات ضروری از آن استفاده کنید.

جمع‌بندی و توصیه‌های نهایی

نکات کلیدی که باید به‌کار ببرید:

  • همیشه هدرها به‌ویژه User-Agent را مدیریت کنید و در مقیاس از روتیشن استفاده کنید.
  • برای جلوگیری از بلاک از پراکسی استفاده کنید؛ اگر می‌خواهید نگهداری کم باشد از gateway یا proxy API استفاده کنید.
  • برای افزایش پایداری از retry با backoff استفاده کنید و محتوای پاسخ را برای تشخیص صفحات بن چک کنید.
  • برای سرعت از هم‌زمانی (مثل ThreadPoolExecutor) استفاده کنید، اما منابع سیستم و رفتار سایت را رعایت کنید.
  • برای صفحات JS-rendered از headless browser یا سرویس‌هایی که رندر را انجام می‌دهند بهره ببرید.

با ترکیب این الگوها می‌توانید اسکریپرهایی بسازید که هم سریع، هم قابل‌اطمینان و هم کمتر در معرض بلاک شدن باشند. همیشه قوانین و سیاست‌های هدف (robots.txt و Terms of Service) را بررسی کنید و اخلاق و قوانین را رعایت نمایید.

مطالب مرتبط

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