مقدمه
در این راهنمای عملی برای توسعهدهندگان پایتون با سطح متوسط، به نحوهٔ تنظیم و مدیریت User-Agent هنگام اسکریپینگ با HTTPX میپردازیم. بعد از خواندن این مطلب شما یاد میگیرید چگونه یک User-Agent ساده بسازید، آن را بچرخانید، از فهرستهای بزرگ استفاده کنید، هدرهای کامل مرورگر را اعمال کنید و نکات امنیتی و بهترینروشها را برای پایداری اسکریپرها بهکار بگیرید.
چیست و چرا مهم است
User-Agent یک رشتهٔ متنی است که مرورگر یا کلاینت را به سرور معرفی میکند (نوع مرورگر، سیستمعامل و نسخه). بسیاری از وبسایتها درخواستهایی که رشتهٔ User-Agent آنها مشخصاً مربوط به کتابخانههای پایتون باشد را فیلتر یا بلاک میکنند. بنابراین مدیریت صحیح User-Agent گامی ساده اما ضروری برای کاهش بلاکشدن است.
تنظیم یک User-Agent ساده در HTTPX
ایده کلی: یک دیکشنری headers میسازیم و آن را به درخواست میدهیم. در این مثال از کلاینت همیشگی httpx.Client استفاده میکنیم تا اتصالها را باز استفاده (connection reuse) کنیم.
import httpx
headers = {
"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148"
}
with httpx.Client() as client:
response = client.get("HTTPBIN_HEADERS_ENDPOINT", headers=headers)
print(response.json())
توضیح کد:
- ورودی: هیچ ورودی خارجی جز headers و آدرس هدف (اینجا به صورت مقدار نمادین HTTPBIN_HEADERS_ENDPOINT نمایش داده شده).
- خروجی: خروجی تابع response.json() حاوی هدرهای ارسالشده است.
- خطبهخط: ابتدا headers تعریف میشود، سپس با استفاده از httpx.Client درخواست GET ارسال و پاسخ بهصورت JSON چاپ میشود.
چرخاندن User-Agentها
برای کاهش احتمال شناسایی، بهتر است بهازای هر درخواست یک User-Agent تصادفی استفاده کنید. نسخهٔ ساده با random.choice کد را خواناتر میکند.
import httpx
import random
user_agent_list = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/93.0.4577.82 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2) AppleWebKit/605.1.15 Version/14.0.3 Mobile/15E148 Safari/604.1',
'Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)'
]
with httpx.Client() as client:
for target_url in ["TARGET_URL_1", "TARGET_URL_2"]:
headers = {"User-Agent": random.choice(user_agent_list)}
r = client.get(target_url, headers=headers)
print(r.status_code)
نکات عملی:
- بهجای randint از random.choice استفاده کنید تا خوانایی بهتر شود.
- اگر همزمانی (concurrency) دارید، مطمئن شوید که انتخاب User-Agent بین thread/taskها ایزوله است و یک User-Agent به صورت همزمان توسط تعداد زیادی درخواست تکرار نشود (زیرا الگوهای تکراری قابل تشخیصاند).
مدیریت هزاران User-Agent با API
نگهداری دستی یک فهرست طولانی و بهروز از User-Agentها دشوار است. راه بهتر این است که هنگام شروع اسکریپر، فهرست را از یک سرویس بهروز بارگیری کنید و سپس بهصورت محلی از آنها استفاده کنید. در ادامه نمونهٔ سادهٔ بازیابی فهرست از سرویس ScrapeOps و انتخاب تصادفی آورده شده است (آدرسهای شبکه بهصورت نمادین نشان داده شدهاند).
import httpx
from random import choice
import time
SCRAPEOPS_API_KEY = 'YOUR_API_KEY'
SCRAPEOPS_USER_AGENTS_ENDPOINT = 'SCRAPEOPS_USER_AGENTS_ENDPOINT'
def get_user_agent_list(client: httpx.Client):
try:
resp = client.get(SCRAPEOPS_USER_AGENTS_ENDPOINT, params={"api_key": SCRAPEOPS_API_KEY}, timeout=10)
data = resp.json()
return data.get('result', [])
except Exception:
# fallback محلی یا لیست پیشفرض
return [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/103.0.0.0 Safari/537.36'
]
with httpx.Client() as client:
user_agent_list = get_user_agent_list(client)
for url in ['TARGET_URL_1', 'TARGET_URL_2']:
headers = {"User-Agent": choice(user_agent_list)}
r = client.get(url, headers=headers)
print(r.status_code)
نکات مهم:
- همیشه یک fallback برای مواقعی که API در دسترس نیست در نظر بگیرید.
- کلید API را هرگز داخل لاگها یا مخازن عمومی قرار ندهید. بهتر است آن را از طریق متغیر محیطی بارگذاری کنید.
فچ کردن هدرهای کامل مرورگر (Fake Browser Headers)
تنها User-Agent کافی نیست؛ بسیاری از سرورها به مجموعهای از هدرها نگاه میکنند (مثل sec-ch-ua, Accept, Sec-Fetch-*) تا رفتار مرورگر را شبیهسازی کنند. ارسال مجموعهٔ کامل هدرهای معتبر شناسایی را سختتر میکند.
# نمونهٔ ساخت دستی هدر کامل
browser_headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/99.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Site": "none",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"sec-ch-ua": '".Not/A)Brand";v="99", "Google Chrome";v="99", "Chromium";v="99"'
}
with httpx.Client() as client:
r = client.get('TARGET_URL_1', headers=browser_headers)
print(r.status_code)
اما ساخت و نگهداری این هدرها مشکل است؛ سرویسهایی مثل ScrapeOps میتوانند مجموعهٔ بهروز هدرها را برای شما فراهم کنند تا هر بار هنگام استارت اسکریپر آنها را دریافت کرده و انتخاب کنید.
همزمانی و Async با HTTPX
اگر اسکریپر شما از همزمانی استفاده میکند، تعویض User-Agent در سطح هر تسک و استفاده از httpx.AsyncClient به همراه اتصالهای باز (keep-alive) اهمیت دارد. نمونهٔ سادهٔ async با انتخاب تصادفی هدر:
import asyncio
import httpx
import random
async def fetch(client, url, header):
r = await client.get(url, headers=header)
return r.status_code
async def main(urls, user_agent_list):
async with httpx.AsyncClient() as client:
tasks = []
for url in urls:
headers = {"User-Agent": random.choice(user_agent_list)}
tasks.append(fetch(client, url, headers))
results = await asyncio.gather(*tasks)
print(results)
# اجرا: asyncio.run(main(['TARGET_URL_1','TARGET_URL_2'], user_agent_list))
نکات همزمانی:
- در حالت async، استفادهٔ همزمان از یک فهرست مشترک مشکلساز نیست، اما اگر فهرستها از API گرفته میشوند، مطمئن شوید که عملیات خواندن/نوشتن همزمان منجر به race condition نشود.
- برای حجم بالا، محدودیت همزمانی (semaphore یا اتصال محدود) تعیین کنید تا تعداد اتصالات همزمان کنترل شود.
بهترینروشها، خطاها و امنیت
برای تولید اسکریپر قابلاطمینان توجه به نکات زیر ضروری است:
- Rate limiting: سرعت درخواستها را محدود کنید تا از تشدید رفتار رباتی جلوگیری شود (sleep تصادفی بین درخواستها، یا توزیع بار).
- Retry و Backoff: برای خطاهای موقت از retry با backoff نمایی استفاده کنید. مثال ساده:
import time
def robust_get(client, url, headers, max_retries=3):
backoff = 1
for i in range(max_retries):
try:
r = client.get(url, headers=headers, timeout=10)
r.raise_for_status()
return r
except Exception as e:
time.sleep(backoff)
backoff *= 2
raise RuntimeError('max retries exceeded')
- پنهانسازی کلیدها: کلیدهای API را در متغیر محیطی ذخیره کنید و هرگز آنها را در خروجی لاگ نکنید.
- استفاده از پراکسی: برای توزیع ترافیک و کاهش شناسایی از پراکسیها استفاده کنید؛ ترکیب پراکسی و چرخش User-Agent اثربخشی بالاتری دارد.
- مطابقت با قوانین و اخلاق: همواره قوانین سایت و فایل robots.txt را بررسی کنید و دادههایی که نباید برداشت شوند را جمعآوری نکنید.
مزایا و محدودیتهای استفاده از Fake Headers
- مزایا:
- کاهش احتمال بلاکشدن
- ساخت رفتار شبیهبهانسان با کمترین پیچیدگی
- محدودیتها:
- برخی سایتها تحلیل رفتار (fingerprinting) پیشرفته دارند که صرفاً تغییر هدرها را تشخیص نمیدهند
- نگهداری و بهروزرسانی فهرستها و هدرها نیاز به منابع دارد
نمونهٔ جریان کامل (ترکیب همهٔ موارد)
روال پیشنهادی در شروع اسکریپر:
- بارگیری فهرست User-Agent و/یا هدرهای کامل از یک API معتبر (با fallback داخلی).
- راهاندازی اتصال یا کلاینت (sync یا async) با محدودیت همزمانی.
- برای هر درخواست: انتخاب تصادفی هدر، اعمال پراکسی (در صورت نیاز)، اعمال محدودیت نرخ، ارسال درخواست با retry و backoff.
- ثبت نتایج و متادیتا (بدون لو دادن کلیدها) و مانیتورینگ خطاها برای تنظیم استراتژیها.
جمعبندی
تنظیم و چرخش User-Agentها در HTTPX ساده است اما برای حصول پایداری واقعی باید هدرهای کامل مرورگر، مدیریت فهرستهای بهروز، استراتژیهای retry، rate limiting و در صورت نیاز پراکسی را ترکیب کنید. استفاده از سرویسهایی مانند ScrapeOps میتواند بار نگهداری فهرستهای بزرگ را کم کند؛ اما همیشه از fallback و حفاظت کلیدها غافل نشوید. با رعایت این اصول، اسکریپر شما قابلاطمینانتر و کمتر در معرض بلاکشدن خواهد بود.





