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

راهنمای اسکریپینگ: مدیریت User-Agent در پایتون

راهنمای اسکریپینگ: مدیریت User-Agent در پایتون
این مقاله به صورت عملی روش‌های مدیریت و چرخش User-Agent در اسکریپینگ با پایتون (requests، httpx، aiohttp) را توضیح می‌دهد، نحوهٔ یکپارچه‌سازی با API برای دریافت هزاران User-Agent و هدرهای مرورگر را نشان می‌دهد و نکات امنیتی و بهترین‌روش‌ها را برای جلوگیری از بلوک شدن پوشش می‌دهد.
آسان اسکریپ آسان اسکریپ
1405-04-30

مقدمه

در اسکریپینگ، یکی از دلایل معمول بلوک شدن درخواست‌ها، ارسال هدرهای مشخص‌کنندهٔ کتابخانه یا یک User-Agent قدیمی است. در این راهنما به‌صورت عملی و فنی می‌فهمیم که User-Agent چیست، چگونه در کتابخانه‌های رایج پایتون مثل requests، httpx و aiohttp آن را جایگزین کنیم، چگونه آن‌ها را بچرخانیم و برای مقیاس بزرگ از یک سرویس API استفاده کنیم. در پایان این مقاله شما نمونه‌کدهای کاربردی، نکات ایمنی و بهترین روش‌ها برای مدیریت User-Agent و هدرهای مرورگر خواهید داشت.

چه هستند User-Agent های تقلبی و چرا مهم‌اند؟

User-Agent یک رشتهٔ متنی است که مرورگر یا کلاینت در هدر درخواست HTTP می‌فرستد تا سرور تشخیص دهد چه نرم‌افزاری و چه سیستم‌عاملی دارد درخواست را ارسال می‌کند. وقتی از کلاینت‌های پایتون به‌صورت پیش‌فرض استفاده می‌کنید، مقدار User-Agent معمولا نشان می‌دهد که درخواست با همان کتابخانه ساخته شده (مثلاً python-requests/...) و این می‌تواند باعث شناسایی و بلوک شدن شود.

یک User-Agent تقلبی (Fake User-Agent) رشته‌ای است که به‌ظاهر متعلق به مرورگرهای رایج است و هدف آن شبیه‌سازی درخواست‌های انسانی است تا از قواعد سادهٔ تشخیص مولد جلوگیری شود.

تنظیم User-Agent در requests

ایدهٔ کلی: هدر User-Agent را در دیکشنری headers مشخص کنید و آن را به هر درخواست یا به Session اضافه کنید.

نمونهٔ ساده (ملاحظات: از TARGET_URL به‌عنوان شناسهٔ مقصد استفاده کنید و مقدار User-Agent را بر اساس نیاز به‌روز کنید):

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}

r = requests.get('TARGET_URL', headers=headers, timeout=10)
print(r.status_code)
print(r.text[:200])  # نمایش خلاصه‌ای از پاسخ

توضیح: ورودی‌ها اینجا عبارت‌اند از TARGET_URL و دیکشنری headers. خروجی‌ها شامل کد وضعیت و بدنهٔ پاسخ است. خط‌به‌خط: import کتابخانه، تعریف هدرها، ارسال GET با تایم‌اوت برای جلوگیری از بلوکهٔ طولانی، و چاپ خروجی.

استفاده از Session برای بهبود کارایی و پیوستگی هدرها:

s = requests.Session()
s.headers.update(headers)

r = s.get('TARGET_URL', timeout=10)
print(r.status_code)

نکتهٔ امنیتی/عملی: همیشه timeout تعیین کنید، و هرگز کلیدهای محرمانه را در کد هاردکُد نکنید. برای رِتری بهتر از مکانیزم‌های استاندارد مثل urllib3 Retry یا backoff سفارشی استفاده کنید.

تنظیم User-Agent در httpx

httpx هم API مشابهی دارد و هم در حالت sync و هم async کاربردی است. بهترین روش استفاده از یک Client و به‌روزرسانی هدرهای آن است.

import httpx

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."}

with httpx.Client(headers=headers, timeout=10.0) as client:
    r = client.get('TARGET_URL')
    print(r.status_code)
    print(r.text[:200])

توضیح: ورودی‌ها همانند requests هستند. مزیت httpx.Client این است که مدیریت اتصال (connection pool) و گزینه‌های sync/async قوی‌تر دارد. برای حالات async می‌توانید از httpx.AsyncClient استفاده کنید.

تنظیم User-Agent در aiohttp (async)

در اسکریپینگ غیرهم‌زمان (async)، از aiohttp برای حفظ همزمانی بالا استفاده می‌شود. هدرها را می‌توان در زمان ساخت Session تعیین کرد یا به هر درخواست جداگانه اضافه نمود.

import asyncio
import aiohttp

async def fetch(url, headers):
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(url, timeout=10) as resp:
            text = await resp.text()
            return resp.status, text[:200]

headers = {"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) ..."}

status, snippet = asyncio.run(fetch('TARGET_URL', headers))
print(status)
print(snippet)

توضیح: تابع async fetch ورودی‌های url و headers را می‌گیرد و وضعیت و بخشی از متن را برمی‌گرداند. در محیط‌هایی با تعداد زیاد درخواست هم‌زمان، aiohttp کارایی و کنترل بهتری ارائه می‌دهد.

چرخش (Rotate) User-Agentها

ایدهٔ ساده: مجموعه‌ای از User-Agentهای معتبر داشته باشید و برای هر درخواست یکی را به‌صورت تصادفی انتخاب کنید. این روش در برابر قوانین سادهٔ شناسایی کمک‌کننده است اما به‌تنهایی کافی نیست.

import random
import requests

user_agents = [
    "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)"
]

def get_random_ua():
    return random.choice(user_agents)

headers = {"User-Agent": get_random_ua()}
r = requests.get('TARGET_URL', headers=headers, timeout=10)
print(r.status_code)

نکات عملی: این روش نیاز به نگهداری و به‌روز‌رسانی لیست دارد، و اگر لیست قدیمی باشد یا همهٔ درخواست‌ها از یک مجموعهٔ ثابت IP بیایند، سرورها باز هم می‌توانند رفتار را تشخیص دهند. بهتر است همراه با استفاده از پراکسی، تاخیرهای تصادفی و محدودیت درخواست (rate limiting) به کار رود.

مدیریت هزاران User-Agent با API

برای اسکریپرهای بزرگ‌تر نگهداری دستی هزاران User-Agent عملی نیست. یک راهکار معمول استفاده از یک سرویس API است که در زمان شروع، یک لیست تازه برمی‌گرداند. نمونهٔ کد زیر نشان می‌دهد چگونه یک لیست را از یک endpoint دریافت کرده و سپس در اسکریپر استفاده کنیم (از متغیر SCRAPEOPS_API_KEY و SCRAPEOPS_ENDPOINT_USER_AGENTS برای نگهداری اطلاعات Endpoint و کلید استفاده کنید).

import requests
import random

SCRAPEOPS_API_KEY = 'YOUR_API_KEY'
SCRAPEOPS_ENDPOINT_USER_AGENTS = 'SCRAPEOPS_ENDPOINT_USER_AGENTS'  # مقدار واقعی را در متغیر محیطی یا کانفیگ نگهدارید

def get_user_agent_list():
    resp = requests.get(SCRAPEOPS_ENDPOINT_USER_AGENTS, params={"api_key": SCRAPEOPS_API_KEY}, timeout=10)
    resp.raise_for_status()
    data = resp.json()
    return data.get('result', [])

user_agent_list = get_user_agent_list()
if not user_agent_list:
    raise SystemExit('هیچ User-Agent معتبری دریافت نشد')

headers = {"User-Agent": random.choice(user_agent_list)}
r = requests.get('TARGET_URL', headers=headers, timeout=10)
print(r.status_code)

نکات مهم: اپی‌آی ممکن است نرخ محدود داشته باشد؛ پس لیست را کش (cache) کنید و هر بار که اسکریپر بالا می‌آید آن را تازه‌سازی نمایید. کلید API را هرگز در مخزن عمومی قرار ندهید و از متغیرهای محیطی یا vault برای نگهداری استفاده کنید.

چرا از هدرهای کامل مرورگر استفاده کنیم؟

وب‌سایت‌های پیشرفته فقط User-Agent را بررسی نمی‌کنند؛ آن‌ها به مجموعه‌ای از هدرهای مرورگر مثل Accept، Accept-Language، Sec-Fetch-* و sec-ch-ua نگاه کرده و رفتار کلی را تحلیل می‌کنند. ارسال یک مجموعهٔ کامل و سازگار از هدرها شانس تشخیص ربات را کاهش می‌دهد.

نمونهٔ سادهٔ یک مجموعهٔ هدر (این نمونه‌ها برای فهم ساختار است):

{
  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
  "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,...",
  "accept-language": "en-US,en;q=0.9",
  "accept-encoding": "gzip, deflate, br",
  "sec-ch-ua": '"Chromium";v="XX", "Google Chrome";v="XX"',
  "sec-ch-ua-mobile": "?0",
  "sec-ch-ua-platform": '"macOS"'
}

اگر از چنین هدرهای کاملی استفاده می‌کنید، مطمئن شوید که هدرها با هم همخوانی دارند (مثلاً user-agent و مقادیر sec-ch-ua-platform تناقض نداشته باشند).

ادغام هدرهای کامل با API

اگر از سرویس‌هایی استفاده می‌کنید که هِدرهای مرورگر به‌روز برمی‌گردانند، می‌توانید در زمان شروع، فهرستی از هدرها را دریافت و برای هر درخواست یک آیتم انتخاب کنید:

import requests
import random

SCRAPEOPS_ENDPOINT_BROWSER_HEADERS = 'SCRAPEOPS_ENDPOINT_BROWSER_HEADERS'

def get_browser_headers_list():
    resp = requests.get(SCRAPEOPS_ENDPOINT_BROWSER_HEADERS, params={"api_key": SCRAPEOPS_API_KEY}, timeout=10)
    resp.raise_for_status()
    return resp.json().get('result', [])

header_list = get_browser_headers_list()
chosen_headers = random.choice(header_list)  # این یک dict است
r = requests.get('TARGET_URL', headers=chosen_headers, timeout=10)
print(r.status_code)

نکتهٔ فنی: اگر headerها شامل مقادیر غیرمتعارف یا تهی باشند قبل از استفاده آن‌ها را اعتبارسنجی کنید. همچنین بعضی سرورها روی ترتیب هدرها حساس نیستند اما برخی رفتار بررسی دقیقی دارند؛ بنابراین بهتر است هدرهای برگشتی را به‌صورت دقیق بررسی و در صورت نیاز نرمال‌سازی کنید.

بهترین روش‌ها، محدودیت‌ها و هشدارها

  • هرگز کلید API یا داده‌های حساس را هاردکُد نکنید؛ از متغیرهای محیطی استفاده کنید.
  • از Session/Client برای بازیافت کانکشن و کاهش سربار استفاده کنید.
  • همیشه timeout تنظیم کنید و برای درخواست‌ها مکانیزم retry با backoff داشته باشید.
  • چرخش User-Agent به‌تنهایی کافی نیست: از پراکسی‌، تاخیر تصادفی، و محدودسازی نرخ (rate limiting) نیز استفاده کنید.
  • مراقب قوانین سایت و مسائل حقوقی و اخلاقی باشید؛ اسکریپ کردن داده‌ها ممکن است قوانین یا شرایط استفاده را نقض کند.
  • هدرهای کامل مرورگر را فقط زمانی استفاده کنید که نیاز به شبیه‌سازی دقیق کاربر وجود دارد، چون برخی سایت‌ها تحلیل‌های رفتاری پیچیده‌تری انجام می‌دهند.

جمع‌بندی

مدیریت صحیح User-Agent و هدرهای مرورگر بخش مهمی از ساختن یک اسکریپر قابل‌اطمینان است. در سطح پایه می‌توانید با تنظیم یک هدر ساده شروع کنید، سپس با چرخش User-Agent و در نهایت با استفاده از یک سرویس API برای دریافت لیست‌های به‌روز، مقیاس و پایداری را افزایش دهید. همیشه زمان‌بندی، پراکسی و سیاست‌های retry را نیز در نظر بگیرید تا شانس بلوک شدن کاهش یابد و اسکریپر شما پایدارتر عمل کند.

اگر دنبال یک استراتژی مقیاس‌پذیر هستید: (1) لیست هدرهای به‌روز را کش کنید، (2) برای هر درخواست یک هدر تصادفی انتخاب کنید، (3) از پراکسی و محدودیت نرخ استفاده کنید و (4) کلیدهای محرمانه را ایمن نگهداری کنید.

مطالب مرتبط

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