لا يزال Scrapy هو حصان العمل لعمليات الزحف الإنتاجية في 2026 — ولا يزال الإطار الذي يربك الناس أكثر من غيره في إعداد البروكسي، لأن هناك أربعة أماكن مختلفة لتوصيل البروكسي، وثلاثة منها خاطئة لمعظم المشاريع. يمنحك هذا الدليل المكان الصحيح: middleware مخصص صغير مع توجيه لكل طلب، وجلسات لاصقة، وكشف الحظر، وسلوك إعادة محاولة معقول.
إذا كنت تستخدم requests/httpx/aiohttp العادية بدلًا من ذلك، راجع كيفية تناوب البروكسيات في Python. هذا المقال خاص بـ Scrapy.
لبوابة سكنية متناوبة، الإعداد الأدنى القابل للتطبيق هو سطر واحد لكل طلب — لا حاجة إلى middleware:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
يقرأ HttpProxyMiddleware المدمج في Scrapy قيمة request.meta["proxy"] ويتولى المصادقة من عنوان URL. تتناوب البوابة عنوان IP الخارج نيابة عنك. إذا كان هذا كل ما تحتاجه، توقّف هنا. بقية هذا الدليل مخصصة لعندما تحتاج إلى التحكم: الجلسات اللاصقة، والتوجيه حسب البلد، والتناوب الواعي بالحظر، وضبط التزامن.
ضع هذا في middlewares.py. يُخصّص جلسات لاصقة لكل نطاق، ويتناوب عند الحظر، ويُعلّم كل طلب حتى تتمكن من تصحيح الجلسة التي جلبت ماذا:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # move to settings.py / env in real projects
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky session per domain; rotate session on ban."""
def __init__(self):
self.sessions = {} # domain -> session id
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Call when a session is burned."""
self.sessions[domain] = _new_session()
و middleware مُكمّل للتنزيل يكشف عمليات الحظر ويعيد المحاولة على جلسة جديدة:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # burn the session
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
وصِّل كليهما في settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
الأولوية مهمة: يجب أن يعمل middleware البروكسي قبل HttpProxyMiddleware الخاص بـ Scrapy (750)، لذا فأي شيء أقل من 750 يعمل؛ القيمة 350 تُبقيه مبكرًا ويمكن التنبؤ به.
| نوع الزحف | الوضع | التنفيذ |
|---|---|---|
| حصاد الصفحات بلا حالة | متناوب | اسم مستخدم مجرّد، تتناوب البوابة لكل طلب |
| تسجيل الدخول + الزحف خلف المصادقة | لاصق لكل حساب | -session-{account_id}، لا تتناوب أبدًا في منتصف تسجيل الدخول |
| قوائم كثيفة التصفّح | لاصق لكل نطاق، يتناوب عند الحظر | الـ middleware أعلاه |
| تسعير خاص بالمنطقة الجغرافية | متناوب + تثبيت بلد | معاملات بنمط USERNAME-country-de |
معالجة أعمق لهذه المفاضلة: جلسات البروكسي اللاصقة مقابل المتناوبة.
إعدادات Scrapy الافتراضية مضبوطة للزحف المهذّب بعنوان IP واحد. خلف مجمّع متناوب يمكنك الدفع بقوة أكبر — لكن حدود كل نطاق لا تزال مهمة لأن الهدف يرى السلوك الإجمالي:
# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # what the target experiences
DOWNLOAD_DELAY = 0.25 # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
ارفع CONCURRENT_REQUESTS_PER_DOMAIN فقط بعد مراقبة معدّل 403 لديك عند المستوى الحالي لبضعة آلاف من الطلبات. الانتقال من 8 → 32 لأن "البروكسيات تتناوب على أي حال" هو الطريقة التي يستنزف بها الناس الجيجابايت في إعادات المحاولة.
IMAGES_STORE للجلب عبر بروكسي مركز بيانات أو اتصال مباشر إذا لم يكترث الـ CDN — فالوسائط هي معظم الجيجابايت لديك ونادرًا ما تكون محمية.HTTPCACHE_ENABLED = True أثناء تكرارك على المحلّلات يعيد تشغيل الاستجابات من القرص بدلًا من إعادة الجلب عبر المجمّع. عادة ما يخفّض هذا الإعداد وحده فاتورة النطاق الترددي للمشروع إلى النصف.COMPRESSION_ENABLED = True (افتراضي) — فـ HTML المضغوط بـ gzip أصغر 5–10 أضعاف على الشبكة.407 Proxy Authentication Requiredلم تصل بيانات الاعتماد إلى البروكسي. ضعها في عنوان URL (http://user:pass@host:port) داخل meta["proxy"] — فـ Scrapy يحلّلها ويضبط Proxy-Authorization نيابة عنك. ضبط الترويسة يدويًا و استخدام بيانات اعتماد عنوان URL يسبّب غرابة المصادقة المزدوجة؛ اختر واحدًا.
TunnelError: Could not open CONNECT tunnelغالبًا ما يكون خطأً مطبعيًا في المضيف/المنفذ، أو هدف HTTPS عبر نقطة نهاية لا تسمح بـ CONNECT على ذلك المنفذ. تحقّق باستخدام curl -x خارج Scrapy أولًا.
تجاوزت جلستك اللاصقة الترحيب بها، أو أن معدّلك لكل نطاق ساخن جدًا. يعالج الـ middleware الواعي بالحظر أعلاه الحالة الأولى؛ خفّض CONCURRENT_REQUESTS_PER_DOMAIN للحالة الثانية. إذا كان هدفًا يفحص JA4، فقد تكون حزمة TLS الخاصة بـ Scrapy نفسها هي الدليل — راجع شرح JA3/JA4 لمعرفة لماذا لا يصلح أي بروكسي ذلك.
meta["proxy"] مع عنوان URL للبوابة هو الإعداد بأكمله.جلسات سكنية متناوبة ولاصقة على بوابة واحدة. رصيد مجاني بحجم 500 ميجابايت للزحف به.
ابدأ النسخة التجريبية المجانيةيحصل المستخدمون الجدد على 500MB عند التسجيل، بالإضافة إلى مكافأة على أول عملية شحن. العرض لفترة محدودة.