بازگشت به وبلاگ

چرا پروکسی در مرورگر کار می‌کند اما در کد خیر: تحلیل کامل مشکل

پروکسی در مرورگر عالی کار می‌کند اما اسکریپت خطا می‌دهد؟ دلایل رایج و راه‌حل‌های آماده برای پایتون، نود جی‌اس و سی‌یو‌آر‌ال را بررسی می‌کنیم.

📅۲۰ آذر ۱۴۰۴
```html

چرا پروکسی در مرورگر کار می‌کند اما در کد خیر: بررسی کامل مشکل

سناریوی کلاسیک: پروکسی را در مرورگر تنظیم می‌کنید، سایت را باز می‌کنید — همه چیز کار می‌کند. اسکریپت را با همان پروکسی اجرا می‌کنید — خطای اتصال، مهلت زمانی (تایم‌اوت) یا مسدود شدن (بَن) دریافت می‌کنید. در اینجا دلایل این اتفاق و نحوه رفع آن را بررسی می‌کنیم.

تفاوت درخواست مرورگر با درخواست کد

هنگامی که یک سایت را از طریق پروکسی در مرورگر باز می‌کنید، اتفاقات بسیار بیشتری نسبت به یک درخواست HTTP ساده رخ می‌دهد. مرورگر به طور خودکار:

  • مجموعه کاملی از هدرها (User-Agent, Accept, Accept-Language, Accept-Encoding) را ارسال می‌کند.
  • TLS-handshake را با مجموعه رمزنگاری‌های صحیح انجام می‌دهد.
  • ریدایرکت‌ها و کوکی‌ها را مدیریت می‌کند.
  • جاوا اسکریپت را اجرا کرده و منابع وابسته را بارگیری می‌کند.
  • پاسخ‌های DNS و گواهی‌ها را کش می‌کند.

یک درخواست حداقل از کد برای سرور کاملاً متفاوت به نظر می‌رسد — شبیه یک ربات است، نه یک کاربر واقعی. حتی اگر پروکسی به درستی کار کند، ممکن است سایت هدف، اسکریپت شما را مسدود کند.

مشکلات احراز هویت پروکسی

شایع‌ترین دلیل، انتقال نادرست نام کاربری و رمز عبور است. مرورگر یک پنجره پاپ‌آپ برای وارد کردن اطلاعات نمایش می‌دهد، اما در کد باید این کار به صورت صریح انجام شود.

فرمت نادرست URL

یک اشتباه رایج، حذف طرح (Scheme) یا کدگذاری نادرست کاراکترهای ویژه است:

# اشتباه
proxy = "user:pass@proxy.example.com:8080"

# صحیح
proxy = "http://user:pass@proxy.example.com:8080"

# اگر رمز عبور دارای کاراکترهای ویژه است (@, :, /)
from urllib.parse import quote
password = quote("p@ss:word/123", safe="")
proxy = f"http://user:{password}@proxy.example.com:8080"

احراز هویت بر اساس IP در مقابل نام کاربری/رمز عبور

برخی ارائه‌دهندگان پروکسی از لیست مجاز (Whitelist) بر اساس آدرس IP استفاده می‌کنند. مرورگر روی کامپیوتر شما کار می‌کند زیرا IP شما در لیست مجاز قرار دارد. اما اسکریپت روی سرور کار نمی‌کند، زیرا سرور IP متفاوتی دارد.

در پنل ارائه‌دهنده پروکسی بررسی کنید که از چه روش احراز هویتی استفاده می‌شود و چه IPهایی در لیست مجاز قرار دارند.

عدم تطابق پروتکل‌های HTTP/HTTPS/SOCKS

مرورگر اغلب نوع پروکسی را به صورت خودکار تشخیص می‌دهد. در کد، باید آن را به صراحت مشخص کنید، و اشتباه در پروتکل منجر به رد شدن خاموش درخواست می‌شود.

نوع پروکسی طرح در URL ویژگی‌ها
پروکسی HTTP http:// برای HTTP و HTTPS از طریق CONNECT کار می‌کند
پروکسی HTTPS https:// اتصال رمزنگاری شده به پروکسی
SOCKS4 socks4:// بدون احراز هویت، فقط IPv4
SOCKS5 socks5:// با احراز هویت، UDP، IPv6
SOCKS5h socks5h:// تفکیک نام DNS از طریق پروکسی

نکته حیاتی: اگر پروکسی شما SOCKS5 است و شما http:// را مشخص می‌کنید، اتصال برقرار نخواهد شد. کتابخانه سعی می‌کند با پروتکل HTTP با سرور SOCKS صحبت کند.

هدرهای از دست رفته و اثر انگشت (Fingerprint)

حتی اگر پروکسی به درستی کار کند، سایت هدف ممکن است به دلیل هدرهای مشکوک، درخواست را مسدود کند. مقایسه کنید:

درخواست از مرورگر

GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

درخواست پیش‌فرض از requests

GET /api/data HTTP/1.1
Host: example.com
User-Agent: python-requests/2.28.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

تفاوت آشکار است. سایتی که دارای سیستم ضد ربات است، بلافاصله تشخیص می‌دهد که درخواست از مرورگر نمی‌آید.

حداقل مجموعه هدرها برای پنهان‌کاری

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Cache-Control": "max-age=0"
}

گواهی‌های SSL و تأیید اعتبار

مرورگر دارای یک مخزن داخلی برای گواهی‌های ریشه (Root Certificates) است و می‌تواند پیکربندی‌های مختلف SSL را مدیریت کند. در کد ممکن است با مشکلات زیر مواجه شوید:

خطای SSL: CERTIFICATE_VERIFY_FAILED

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

# راه‌حل موقت برای اشکال‌زدایی (برای محیط پروداکشن مناسب نیست!)
import requests
response = requests.get(url, proxies=proxies, verify=False)

# راه‌حل صحیح — مشخص کردن مسیر گواهی
response = requests.get(url, proxies=proxies, verify="/path/to/proxy-ca.crt")

مهم: غیرفعال کردن تأیید اعتبار SSL (verify=False) اتصال را در برابر حملات MITM آسیب‌پذیر می‌کند. فقط برای اشکال‌زدایی در محیط امن استفاده کنید.

اثر انگشت TLS (TLS Fingerprint)

سیستم‌های پیشرفته ضد ربات، اثر انگشت TLS را تحلیل می‌کنند — یعنی ترتیب و مجموعه رمزنگاری‌ها هنگام برقراری اتصال. پایتون requests از یک مجموعه استاندارد استفاده می‌کند که با مجموعه مرورگر متفاوت است.

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

# نصب: pip install curl-cffi
from curl_cffi import requests

response = requests.get(
    url,
    proxies={"https": proxy},
    impersonate="chrome120"  # اثر انگشت TLS مرورگر کروم 120 را تقلید می‌کند
)

نشت DNS و تفکیک نام (Resolving)

یک مشکل پنهان دیگر، تفکیک نام DNS است. هنگام استفاده از پروکسی HTTP، درخواست DNS ممکن است مستقیماً از دستگاه شما ارسال شود و از پروکسی عبور نکند.

تأثیر این موضوع

  • سایت، تفکیک‌کننده DNS واقعی شما را می‌بیند، نه پروکسی را.
  • موقعیت جغرافیایی به اشتباه تعیین می‌شود.
  • برخی سایت‌ها عدم تطابق منطقه IP و DNS را مسدود می‌کنند.

راه‌حل برای SOCKS5

از طرح socks5h:// به جای socks5:// استفاده کنید — حرف "h" به این معنی است که تفکیک نام DNS در سمت پروکسی انجام می‌شود:

# DNS به صورت محلی تفکیک می‌شود (نشت!)
proxy = "socks5://user:pass@proxy.example.com:1080"

# DNS از طریق پروکسی تفکیک می‌شود (صحیح)
proxy = "socks5h://user:pass@proxy.example.com:1080"

نمونه کدهای عملی برای پایتون، Node.js و cURL

پایتون با requests

import requests
from urllib.parse import quote

# اطلاعات پروکسی
proxy_host = "proxy.example.com"
proxy_port = "8080"
proxy_user = "username"
proxy_pass = quote("p@ssword!", safe="")  # کدگذاری کاراکترهای ویژه

# ساخت URL پروکسی
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"

proxies = {
    "http": proxy_url,
    "https": proxy_url
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
}

try:
    response = requests.get(
        "https://httpbin.org/ip",
        proxies=proxies,
        headers=headers,
        timeout=30
    )
    print(f"وضعیت: {response.status_code}")
    print(f"IP: {response.json()}")
except requests.exceptions.ProxyError as e:
    print(f"خطای پروکسی: {e}")
except requests.exceptions.ConnectTimeout:
    print("مهلت زمانی اتصال به پروکسی به پایان رسید")

پایتون با aiohttp (ناهمزمان)

import aiohttp
import asyncio

async def fetch_with_proxy():
    proxy_url = "http://user:pass@proxy.example.com:8080"
    
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "https://httpbin.org/ip",
            proxy=proxy_url,
            headers={"User-Agent": "Mozilla/5.0..."}
        ) as response:
            return await response.json()

result = asyncio.run(fetch_with_proxy())
print(result)

Node.js با axios

const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');

const proxyUrl = 'http://user:pass@proxy.example.com:8080';
const agent = new HttpsProxyAgent(proxyUrl);

axios.get('https://httpbin.org/ip', {
    httpsAgent: agent,
    headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
    }
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error.message));

Node.js با node-fetch و SOCKS

const fetch = require('node-fetch');
const { SocksProxyAgent } = require('socks-proxy-agent');

const agent = new SocksProxyAgent('socks5://user:pass@proxy.example.com:1080');

fetch('https://httpbin.org/ip', { agent })
    .then(res => res.json())
    .then(data => console.log(data));

cURL

# پروکسی HTTP
curl -x "http://user:pass@proxy.example.com:8080" \
     -H "User-Agent: Mozilla/5.0..." \
     https://httpbin.org/ip

# پروکسی SOCKS5 با DNS از طریق پروکسی
curl --socks5-hostname "proxy.example.com:1080" \
     --proxy-user "user:pass" \
     https://httpbin.org/ip

# اشکال‌زدایی — نمایش کل فرآیند اتصال
curl -v -x "http://user:pass@proxy.example.com:8080" \
     https://httpbin.org/ip

چک لیست عیب‌یابی

اگر پروکسی در کد کار نمی‌کند، به ترتیب زیر بررسی کنید:

  1. فرمت URL پروکسی — آیا طرح (scheme) وجود دارد (http://، socks5://)؟
  2. کاراکترهای ویژه در رمز عبور — آیا با کدگذاری URL (URL-encoding) رمزگذاری شده‌اند؟
  3. نوع پروکسی — آیا پروتکل مشخص شده با نوع واقعی مطابقت دارد؟
  4. احراز هویت — آیا بر اساس IP است؟ آیا IP سرور در لیست مجاز قرار دارد؟
  5. هدرها — آیا هدرهای مرورگر مانند User-Agent اضافه شده‌اند؟
  6. SSL — آیا خطای گواهی وجود دارد؟
  7. DNS — آیا برای تفکیک نام از طریق پروکسی از socks5h:// استفاده شده است؟
  8. تایم‌اوت‌ها — آیا زمان کافی برای اتصال در نظر گرفته شده است (به ویژه برای پروکسی‌های مسکونی

نتیجه‌گیری

تفاوت بین مرورگر و کد در جزئیات است: هدرها، پروتکل‌ها، SSL و DNS. مرورگر این پیچیدگی‌ها را پنهان می‌کند، اما در کد باید هر جنبه‌ای را به صورت صریح تنظیم کنید. با بررسی فرمت URL و احراز هویت شروع کنید، سپس هدرهای مرورگر را اضافه کنید — این کار 90٪ مشکلات را حل می‌کند.

برای وظایف خزش (Scraping) و اتوماسیون که ثبات و درصد پایین مسدود شدن در آن‌ها اهمیت دارد، پروکسی‌های مسکونی گزینه‌های خوبی هستند — اطلاعات بیشتر در مورد آن‌ها را می‌توانید در proxycove.com بیابید.

```