ה־Scrapy spider שלך יכול להצליח ב־100 דפי בדיקה, ואז לקרוס ב־10,000. זו לא באמת תקלה ב־scraping — זו בעיית רשת שמתחפשת לתקלה ב־scraping. הפתרון שכולם שולפים הוא פרוקסי, וההכנסה שלו ל־request.meta לוקחת בערך שלושים שניות.
הבעיה היא שהפתרון של שלושים השניות הוא בדיוק המקום שבו הרבה מדריכים בסיסיים נעצרים. הם מראים meta={"proxy": "http://IP:PORT"}, אולי מחלקת middleware, וזהו. בדרך כלל הם מדלגים על מה קורה כשהפרוקסי נופל באמצע הסריקה, איך פרטי ההתחברות דולפים להיסטוריית Git, או למה ניסיון retry עלול להשתמש שוב באותו פרוקסי כושל. אלה בדיוק שיקולי הפרודקשן שמדריך זה מכסה: קונפיגורציה שמעדיפה כשל בטוח, ניהול סודות, בחירת פרוקסי מכוונת בזמן retry, מגבלות פרוטוקול, ופשרות עלות שאפשר למדוד.
מה זה Proxy Middleware ב‑Scrapy?
Proxy middleware הוא קוד שיושב בצנרת ההורדה של Scrapy ומחליט מאיזו כתובת IP הבקשה תיראה כאילו הגיעה, עוד לפני שהיא יוצאת לרשת. ל‑Scrapy יש middleware מובנה כזה, HttpProxyMiddleware, שקורא את המפתח proxy מתוך request.meta, מוסיף אימות מול הפרוקסי אם צריך, ומעביר את הבקשה ל־download handler שמבצע בפועל את החיבור. Middleware מותאם אישית לפרוקסי לא מחליף את שלב ההעברה הזה — הוא רק בוחר איזה פרוקסי לשבץ לפני שהמנגנון המובנה נכנס לפעולה. ההבחנה הזו חשובה הרבה יותר ממה שנדמה, והיא מקורם של רוב הדיווחים בסגנון "ה־custom middleware שלי לא עובד".
למה פרוקסי חשובים בפרויקטי Scrapy בייצור
פרוקסי משנה את מסלול הרשת ואת כתובת ה־IP שנראית בצד היעד. זה יכול לעזור בבדיקות גיאוגרפיות לגיטימיות, לפזר תעבורת בקשות מאושרת, ולבודד תקלות רשת. זה לא מעניק הרשאה, לא עוקף מגבלות קצב, ולא מבטיח גישה. האם בכלל צריך פרוקסי לסריקה תלוי ביעד, בתנאי השימוש שלו, בקצב הבקשות, ובדרישות האמינות של המשימה.
לא כל הפרוקסיים נולדו שווים, ובחירה בשכבה הלא נכונה היא סוג בפני עצמו של אירוע ייצור:
| סוג פרוקסי | אמינות | מהירות | סיכון לזיהוי | עלות טיפוסית |
|---|---|---|---|---|
| פרוקסי ציבוריים חינמיים | משתנה מאוד | משתנה | לרוב גבוה | ללא תשלום, אבל עם סיכון תפעולי/אבטחתי משמעותי |
| Datacenter proxies | תלוי בספק וביעד | לרוב מהיר | תלוי ביעד | נפוץ לפי GB או לפי IP |
| Residential proxies | תלוי בספק וביעד | משתנה | תלוי ביעד | נפוץ לפי GB |
| ISP proxies | תלוי בספק וביעד | משתנה | תלוי ביעד | ספציפי לספק |
פרוקסי חינמיים ראויים לאזהרה מיוחדת, כי הסיכונים שנמדדו בהם משמעותיים. מחקר Free Proxies Unmasked שנמשך 30 חודשים עקב אחרי יותר מ־640,000 כתובות מ־11 ספקים: 34.5% היו פעילות לפחות פעם אחת, ו־16,923 שינו תוכן. הממצא הזה מצדיק אזהרת אבטחה חזקה לגבי רשימות ציבוריות; הוא לא שיעור כשל שניתן להעתקה לכל רשימה עכשווית, מאגר בתשלום, יעד או עומס עבודה.
גם פרוקסי לא מבטיחים לעקוף זיהוי בוטים מודרני. שירותים כמו Cloudflare מדרגים בקשות לפי עשרות אותות — טביעות TLS, עקביות כותרות, הרצת JavaScript, דפוסי התנהגות — כשכתובת ה־IP שלך היא רק קלט אחד מתוך רבים. פרוקסי Residential נקי, אבל עם כותרות לא עקביות וללא cookie jar, עדיין עלול להיחשב חשוד. חשוב לשמור על ציפיות ריאליות לפני שבונים ארכיטקטורה שלמה סביב "פשוט נחליף IP".
לפני שמתחילים
- רמת קושי: בינונית
- זמן נדרש: כ־30–45 דקות להגדרה מלאה לפרודקשן, כ־5 דקות לבדיקה מהירה
- מה צריך: Python 3.9+, התקנת Scrapy (המדריך נבדק מול Scrapy 2.17.0, שיצאה ביולי 2026), spider עובד מ־
scrapy startproject, ולפחות נקודת קצה אחת של פרוקסי (גרסת ניסיון חינמית מכל ספק Datacenter תספיק לבדיקה)
שלב 1: בדיקת פרוקסי עם פרמטר Request Meta
הדרך המהירה ביותר לוודא שפרוקסי עובד היא לעקוף את כל ארכיטקטורת ה־middleware ופשוט לנסות אותו.
ה־HttpProxyMiddleware המובנה של Scrapy קורא מפתח proxy ישירות מ־request.meta ומנתב דרכו את הבקשה. בלי שינוי הגדרות, בלי מחלקת middleware — רק ארגומנט מפתח אחד.
import scrapy
class ProxyTestSpider(scrapy.Spider):
name = "proxy_test"
start_urls = ["https://httpbin.org/ip"]
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url,
meta={"proxy": "http://203.0.113.10:8080"},
callback=self.parse,
)
def parse(self, response):
self.logger.info(response.text)
הריצו עם scrapy runspider proxy_test.py. אם הפרוקסי עובד, httpbin.org/ip יחזיר את כתובת ה־IP של הפרוקסי במקום שלכם — זו האישור שלכם. אם התגובה נתקעת ובסוף זורקת שגיאת TCP connection timed out, הפרוקסי מת או לא נגיש, וזה, למרבה הצער, קורה יותר ממה שספקי פרוקסי אוהבים להודות.
השיטה הזו מצוינת ל־spiders חד־פעמיים או לבדיקה מהירה. היא נשברת ברגע שיש יותר מ־spider אחד, כי אז אתם מקשיחים את אותו מחרוזת פרוקסי בחמישה קבצים שונים.
שלב 2: בניית Custom Proxy Middleware
מעבר ל־spider יחיד, תרצו לרכז את לוגיקת הפרוקסי במקום אחד. צרו מחלקת ProxyMiddleware בקובץ middlewares.py של הפרויקט:
from scrapy.exceptions import NotConfigured
class ProxyMiddleware:
def __init__(self, proxy_url):
self.proxy_url = proxy_url
@classmethod
def from_crawler(cls, crawler):
proxy_url = crawler.settings.get("PROXY_URL")
if not proxy_url:
raise NotConfigured("PROXY_URL is required; refusing silent direct fallback")
return cls(proxy_url=proxy_url)
def process_request(self, request, spider):
if "proxy" not in request.meta:
request.meta["proxy"] = self.proxy_url
ואז רשמו אותה ב־settings.py:
import os
PROXY_URL = os.environ.get("PROXY_URL")
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ProxyMiddleware": 350,
}
שימו לב לשימוש ב־from_crawler במקום __init__ רגיל. זה הדפוס ש־Scrapy באמת ממליץ עליו לקריאת הגדרות, וזה בדיוק ה־hook שנשתמש בו שוב בשלב 4 כשנדבר על סודות. היתרון כאן פשוט: משנים הגדרה אחת, וכל spider בפרויקט מקבל את הפרוקסי החדש. בלי לחפש בחמישה קבצים ולהחליף IP מת.
שלב 3: להבין את סדר ההפעלה של middleware כדי שהפרוקסי לא "יעבוד" אבל בעצם לא יעשה כלום
זה השלב שכמעט כל מדריך אחר מדלג עליו עם "פשוט תגדירו priority 350, תסמכו עליי". ה־downloader middleware ב־Scrapy רץ לפי סדר ברור ומוגדר, ואם לא מבינים אותו, הגדרת הפרוקסי תייצר באגים שנראים בכלל לא קשורים לפרוקסי.
ה־request hooks (process_request) רצים בסדר עדיפות עולה — מספר נמוך קודם. ה־response hooks (process_response, process_exception) רצים בסדר יורד — מספר גבוה קודם, וחוזרים אחורה לאורך השרשרת.
זרימת Request (עולה):
Spider → [100] RobotsTxt → [300] HttpAuth → [350] YOUR PROXY MIDDLEWARE
→ [500] UserAgent → [550] Retry → [700] Cookies → [750] HttpProxy
→ Downloader
זרימת Response (יורד):
Downloader → [750] HttpProxy → [700] Cookies → [550] Retry
→ [500] UserAgent → [350] YOUR PROXY MIDDLEWARE → [300] HttpAuth
→ [100] RobotsTxt → Spider
הנה טבלת ברירת המחדל האמיתית של priorities עבור ה־middlewares המובנים של Scrapy (מאומת מול 2.17.0):
| Middleware | Priority ברירת מחדל |
|---|---|
| RobotsTxtMiddleware | 100 |
| HttpAuthMiddleware | 300 |
| DownloadTimeoutMiddleware | 350 |
| DefaultHeadersMiddleware | 400 |
| UserAgentMiddleware | 500 |
| RetryMiddleware | 550 |
| RedirectMiddleware | 600 |
| CookiesMiddleware | 700 |
| HttpProxyMiddleware | 750 |
| DownloaderStats | 850 |
| HttpCacheMiddleware | 900 |
כש־RetryMiddleware מתזמן retry, הוא מעתיק את הבקשה שנכשלה — כולל המטא־דאטה שלה — והבקשה החדשה הזו נכנסת שוב לשרשרת ה־downloader middleware. היחס המספרי של ה־selector ל־priority 550 לא מבטיח סיבוב פרוקסי. סיבוב קורה רק כשה־selector מזהה את ה־retry ובאופן מכוון מחליף את meta["proxy"] שהועתק. priority 350 הוא מקום נוח ל־selector כי הוא רץ לפני שלב ההעברה המובנה ב־750, אבל הוא לא מנגנון rotation בפני עצמו.

טעויות נפוצות בסדר middleware
- להגדיר את ה־middleware שלכם באותו priority כמו
HttpProxyMiddleware(750): זה יוצר race condition שבה ה־dict-ordering של Scrapy (ולא הלוגיקה שלכם) קובעת איזה middleware מעבד את הבקשה קודם. סימפטום: התנהגות פרוקסי לא עקבית ובלתי מוסברת. - להשתמש ב־
setdefault()או ב־if "proxy" not in request.metaב־selector של rotation: ה־retry שהועתק שומר את הפרוקסי הישן. סימפטום: כל retry חוזר בדיוק לאותו מסלול כושל. הפתרון: לזהות retry (למשל,retry_times > 0) ולהחליף במפורש את ערך הפרוקסי שבבעלות ה־selector. - לנטרל לגמרי את
HttpProxyMiddleware: יש מדריכים שמציעים להגדיר"scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": Noneכי "ה־custom middleware כבר מטפל בזה". הוא לא — ה־custom middleware שלכם הוא selector, לא שכבת העברה. השבתת המובנה אומרת שכותרות אימות של הפרוקסי לא יצורפו, והבקשות ייצאו בלי אימות (או לא ייצאו בכלל).
שלב 4: להפסיק להקשיח פרטי התחברות לפרוקסי בקוד
כמעט כל מדריך Scrapy פרוקסי שמדורג גבוה שאני מכיר כותב http://username:password@proxy.example.com:8080 ישירות בתוך קובץ Python. זהו סוד שנשאר לנצח ב־Git history שלכם, מופיע בכל clone, בכל fork, ובכל dump של לוגים אם מישהו מתרשל עם print().
בפועל יש שלוש רמות לעשות את זה:
| שיטה | אבטחה | גמישות | מתאים במיוחד ל |
|---|---|---|---|
| כתיבה קשיחה ב־spider/settings.py | חלשה — הסודות חיים בריפו | נמוכה | בדיקה מקומית מהירה בלבד |
משתנה סביבה http_proxy (מותאם ל־Scrapy) | טוב יותר — מחוץ לקוד | נמוכה (פרוקסי אחד) | CI/CD, Docker |
קובץ .env + python-dotenv + from_crawler | הטוב ביותר — מחוץ לקוד, לכל סביבה | גבוהה (כמה פרוקסיים, rotation) | סקרייפרים לפרודקשן |
השיטה השלישית שווה בנייה נכונה. התקינו את python-dotenv, צרו קובץ .env (והוסיפו אותו מיד ל־.gitignore — באמת, עשו את זה עכשיו):
PROXY_USER=myuser
PROXY_PASSWORD=my$ecret!Pass
PROXY_HOST=proxy.example.com
PROXY_PORT=8080
טענו אותו בראש settings.py:
from dotenv import load_dotenv
import os
load_dotenv()
PROXY_USER = os.getenv("PROXY_USER")
PROXY_PASSWORD = os.getenv("PROXY_PASSWORD")
PROXY_HOST = os.getenv("PROXY_HOST")
PROXY_PORT = os.getenv("PROXY_PORT")
ואז קראו אותו בצורה בטוחה בתוך ה־middleware באמצעות from_crawler — זה בדיוק הדפוס שפותר את הבלבול שרואים בפורומים, כשאנשים שואלים "איך מגדירים את זה לפני scrapy crawl אם פרטי ההתחברות משתנים בין הרצות?":
import os
from urllib.parse import quote
class SecureProxyMiddleware:
def __init__(self, user, password, host, port):
self.user = quote(user, safe="")
self.password = quote(password, safe="")
self.host = host
self.port = port
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
return cls(
user=settings.get("PROXY_USER"),
password=settings.get("PROXY_PASSWORD"),
host=settings.get("PROXY_HOST"),
port=settings.get("PROXY_PORT"),
)
def process_request(self, request, spider):
proxy_url = f"http://{self.user}:{self.password}@{self.host}:{self.port}"
request.meta["proxy"] = proxy_url
שימו לב לקריאה ל־urllib.parse.quote() סביב פרטי ההתחברות. אם הסיסמה שלכם כוללת @, : או / (ומחוללי סיסמאות אוהבים להוסיף תווים כאלה), הפענוח של ה־URL יישבר אלא אם מקודדים אותם קודם. זה תיקון של שורה אחת שחוסך שעה מביכה של ניפוי שגיאות "invalid proxy URL" שאין להן שום קשר לפרוקסי עצמו.
שמרו את פרטי ההתחברות מחוץ ללוגים, ובדקו percent-encoding עם ערכים מייצגים — אבל פיקטיביים. ל־HTTPS tunneling, ל־SOCKS5 ולפרטי התחברות לא־לטיניים יש גבולות תלויי handler; אל תניחו שדפוס אימות אחד עובד בכל מקום בלי integration test מקובע.

שלב 5: הוספת Proxy Rotation
פרוקסי יחיד — אפילו טוב — שמבצע 5,000 בקשות לאותו אתר יזוהה בסופו של דבר. צריך pool.
אפשרות A — לבנות לבד. זה באמת פשוט:
import random
class RotatingProxyMiddleware:
def __init__(self, proxy_pool):
self.proxy_pool = proxy_pool
@classmethod
def from_crawler(cls, crawler):
return cls(proxy_pool=crawler.settings.getlist("PROXY_POOL"))
def process_request(self, request, spider):
request.meta["proxy"] = random.choice(self.proxy_pool)
זה עובד לשימוש בסיסי, אבל אין לו שום מודעות לשאלה אילו פרוקסיים באמת חיים. אתם זורקים קוביות בכל בקשה.
אפשרות B — להשתמש ב־scrapy-rotating-proxies. החבילה הזו מוסיפה זיהוי ban ו־backoff אוטומטי מהקופסה:
pip install scrapy-rotating-proxies
אני כן רוצה לציין דבר חשוב: הגרסה האחרונה ב‑PyPI היא 0.6.2, משנת 2019, והפרויקט מסומן כ־Alpha. זה לא בהכרח שבור ב־Scrapy מודרני, אבל "לא מתוחזק באופן פעיל מאז 2019" זה לא אותו דבר כמו "נבדק לקרב מול תעבורת ייצור ב־2026". נעלו גרסה, בדקו מול אתרי היעד האמיתיים שלכם, ואל תניחו שהוא יודע לטפל בנקודות קצה מאומתות של פרוקסי — ברובו הוא לא.
שלב 6: בניית Proxy Middleware עמיד לתקלות (זיהוי פרוקסי מת)
זה החלק שכמעט כל מדריך מתחרה מדלג עליו לחלוטין, והוא ההבדל בין דמו לבין משהו ששורד crawl של 6 שעות ללא השגחה.
| תרחיש | מה שרוב המדריכים מראים | מה שה־middleware הזה מוסיף |
|---|---|---|
| הפרוקסי מחזיר 407 | לא מטופל | מתייחס לזה ככשל באימות מול הפרוקסי |
| היעד מחזיר 403/429 | לעיתים מאוגד יחד | מפריד בין משוב של מדיניות/קצב לבין בריאות הפרוקסי |
| הפרוקסי חורג בזמן | לא מטופל | סף timeout ניתן להגדרה, דעיכת ציון בריאות |
| כל הפרוקסיים מתים | לא מטופל | fallback אלגנטי או עצירת crawl עם אזהרה בלוג |
| פרוקסי "מהבהב" (לסירוגין) | לא מטופל | תקופת צינון לפני החזרה ל‑pool |
import time
import random
from scrapy.exceptions import IgnoreRequest
class FaultTolerantProxyMiddleware:
MAX_FAILURES = 3
COOLDOWN_SECONDS = 300
def __init__(self, proxy_pool):
self.pool = {p: {"failures": 0, "banned_until": 0} for p in proxy_pool}
@classmethod
def from_crawler(cls, crawler):
return cls(proxy_pool=crawler.settings.getlist("PROXY_POOL"))
def _healthy_proxies(self):
now = time.time()
return [p for p, s in self.pool.items() if s["banned_until"] < now]
def process_request(self, request, spider):
healthy = self._healthy_proxies()
if not healthy:
spider.logger.warning("כל הפרוקסיים לא בריאים — עוצרים את ה‑crawl")
raise IgnoreRequest("No healthy proxies available")
request.meta["proxy"] = random.choice(healthy)
def process_response(self, request, response, spider):
proxy = request.meta.get("proxy")
if proxy and response.status == 407:
self._mark_failure(proxy)
return response
def process_exception(self, request, exception, spider):
proxy = request.meta.get("proxy")
if proxy:
self._mark_failure(proxy)
def _mark_failure(self, proxy):
state = self.pool[proxy]
state["failures"] += 1
if state["failures"] >= self.MAX_FAILURES:
state["banned_until"] = time.time() + self.COOLDOWN_SECONDS
state["failures"] = 0
כמה הערות מניסיון בנייה אמיתי: אל תתייחסו לכל 403 כהוכחה שהפרוקסי עצמו מת — RFC 9110 מגדיר 403 כ־"השרת הבין אך מסרב", וזה יכול באותה מידה להעיד שהכותרות או ה־session שלכם נראים חשודים, בלי קשר לפרוקסי. 407, לעומת זאת, אומר שהפרוקסי עצמו דוחה את האימות שלכם — וזה אות חזק וספציפי יותר. ערבוב האותות האלה לאותה קופסה הוא בדיוק איך אנשים שורפים פרוקסיים תקינים בלי סיבה.
שמרו בייצור את ברירות המחדל של Scrapy ל־retry באופן מפורש כדי שסוקרים יראו את המדיניות:
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]
RETRY_TIMES = 2
אלו ברירות המחדל של Scrapy 2.17. אל תוסיפו 403 או 407 בצורה גורפת: 403 הוא סירוב של היעד עם הרבה סיבות אפשריות, בעוד 407 הוא בעיית אימות מול הפרוקסי ש־retry גנרי של בקשה לא יתקן. אם חוזה יעד מסוים מצדיק retry על סטטוס אחר, תעדו את הסיבה ובדקו אותה בנפרד.

שלב 7: דפוס מדוד של Proxy-on-Retry
יש יעדים מאושרים שיכולים להחזיר תוכן תקין ישירות, ולהזדקק לפרוקסי רק אחרי תגובה זמנית מתועדת. זה יכול להפחית bytes שמועברים דרך פרוקסי, אבל אין כאן אחוז חיסכון אוניברסלי שאפשר להגן עליו או שיעור הצלחה ישיר שניתן להעתיק למקומות אחרים. מדדו את העומס שלכם לפני אימוץ הדפוס.
השתמשו ב־helper הציבורי של Scrapy, get_retry_request(), ושמרו את ההסלמה רק לסטטוסים ספציפיים ליעד שסיווגתם במפורש. הדוגמה הזו מתייחסת ל־429 ו־503 כאותות עומס שניתן לנסות שוב; היא מוציאה במכוון 403 ו־407.
from scrapy.downloadermiddlewares.retry import get_retry_request
class CostAwareEscalationMiddleware:
def process_response(self, request, response, spider):
if response.status not in {429, 503}:
return response
retry = get_retry_request(
request,
spider=spider,
reason=f"proxy_escalation_{response.status}",
max_retry_times=2,
)
if retry is None:
return response
current_tier = request.meta.get("proxy_tier", "direct")
if current_tier == "direct":
retry.meta["proxy"] = spider.settings["DATACENTER_PROXY"]
retry.meta["proxy_tier"] = "datacenter"
elif current_tier == "datacenter":
retry.meta["proxy"] = spider.settings["RESIDENTIAL_PROXY"]
retry.meta["proxy_tier"] = "residential"
else:
return response
return retry
תעדו את שיעור ה־valid content בנתיב ישיר, את שיעור ה־valid content דרך פרוקסי, את מספר ה־bytes לכל רשומה מוצלחת, את מספר ה־retries להצלחה, ואת תוספת הלטנטיות. עיצוב direct-first מקובל רק כשהגישה הישירה מאושרת, ורק כשהמערכת נכשלת בצורה בטוחה כאשר נדרש פרוקסי. החיסכון הוא הפחתה נמדדת בתעבורת פרוקסי — לא אחוז משוער.
מתי ניהול פרוקסי עצמאי כבר לא שווה את זה
אני רוצה להיות ישיר במקום להעמיד פנים שכל שאר המאמר היה מיותר: כל מה שלמעלה הוא הנדסה אמיתית ושימושית, ולרבה פרויקטים — סריקות בנפח גבוה, pipelines מותאמים אישית, כל מקום שבו צריך שליטה מלאה על תזמון בקשות — זו הבחירה הנכונה.
אבל אם המטרה האמיתית שלכם היא "להוציא נתונים מובנים מהעמוד" ולא "להפעיל תשתית פרוקסי", ייתכן ש־API של extraction הוא גבול נכון יותר. התיעוד הנוכחי של POST /extract של Thunderbit מקבל כתובת URL של עמוד ו־JSON Schema אופציונלי; כאשר הסכמה מושמטת, השירות יכול לייצר אחת מתוכן העמוד. ה־endpoint גם מציע מצבי render של none, basic, ו־full, יחד עם בקרות timeout והמתנה אחרי טעינה. חילוץ באמצעות prompt בלבד אינו חלק מממשק הבקשה הנתמך כיום, לכן אינטגרציות פרודקשן צריכות להיבנות סביב חוזה הסכמה המתועד. זה מעביר את ממשק החילוץ מאחורי בקשה אחת; זה לא מצדיק הבטחה אוניברסלית לגבי כל יעד עם anti-bot או CAPTCHA.
| גורם | DIY Scrapy + Proxies | חילוץ מבוסס API (למשל Thunderbit) |
|---|---|---|
| מאמץ הקמה | גבוה — middleware, rotation, retry logic | נמוך — קריאת API אחת עם schema |
| התנהגות רשת/רינדור | אתם מגדירים handlers, פרוקסי, headers ו־delays | נשלט דרך אפשרויות API מתועדות |
| תחזוקה | אתם אחראים ל־selectors, בריאות pool, ושינויים ביעד | אתם אחראים לאיכות schema, אימות, והתנהגות האינטגרציה |
| מודל עלות | דמי פרוקסי + מחשוב + זמן הנדסה | התיעוד הנוכחי מציג 20 יחידות לכל עמוד (נבדק 2026-08-10) |
| שליטה | מלאה — pipelines מותאמים, שרשרת middleware | מוגבלת ליכולות ה־API |
| מתאים במיוחד ל | Crawls מורכבים, נפח גבוה, לוגיקה מותאמת | חילוץ ממוקד, prototyping, enrichment |
אם אתם בעיקר מחלצים עמודים מובנים לרשימות לידים, נתוני מוצרים או מחקר, הריצו פיילוט מייצג עם Thunderbit Chrome Extension או עם ה־API, והשוו רשומות תקינות, latency, units, וזמן תחזוקה. אם הפרויקט שלכם דורש גרפי crawl מותאמים ושליטה ב־pipeline, Scrapy עדיין יהיה הפתרון החזק יותר.
עוד לקריאה
טיפים ומלכודות נפוצות
- טיפ: בדקו פרוקסיים מול
httpbin.org/ipלפני שאתם מכוונים אותם ליעד אמיתי. זו הדרך המהירה ביותר לוודא שהניתוב עובד לפני שמוסיפים עוד שכבות של מורכבות. - מלכודת: הגדרת פרוקסי ב־
request.headersבמקום ב־request.meta. זהו באג מאוד נפוץ שנראה כמו טעות קטנה —HttpProxyMiddlewareקורא רק אתmeta["proxy"], וניסיון מבוסס header ייכשל בשקט בלי שגיאה ברורה. - מלכודת: הנחה שפרוקסי HTTP ו־HTTPS מוגדרים אותו דבר. URL של פרוקסי HTTP שמנתב ליעד HTTPS בדרך כלל עובד דרך CONNECT tunneling עם handler תואם, אבל שימוש בסכמת
https://עבור נקודת הקצה של הפרוקסי עצמה הוא תצורה אחרת, פחות נתמכת — אל תערבבו ביניהן. - טיפ: אם אתם צריכים תמיכה ב־SOCKS5, בדקו קודם את היכולות של ה־download handler בגרסת Scrapy שלכם. ה־Httpx handler הניסיוני של Scrapy 2.17 הוסיף תמיכה ב־SOCKS5 דרך
httpx[socks], אבל הוא עדיין מסומן כניסיוני — אל תבנו עליו כתלות פרודקשן בלי בדיקה משלכם.
שיטות חלופיות
מעבר ל־scrapy-rotating-proxies, יש צוותים שמנתבים את כל לוגיקת הפרוקסי דרך gateway של ספק — כתובת פרוקסי אחת, כשהספק מטפל מאחורי הקלעים ב־rotation, ב־session stickiness וב־geo-targeting. זה מחליף קצת שליטה בהרבה פחות קוד middleware, ושווה לבדוק תמחור מול pool מנוהל עצמאית לפני שבונים אחד מאפס.
סיכום
הגדרת פרוקסי ב־Scrapy לוקחת שורה אחת. לגרום לה לשרוד crawl אמיתי בייצור דורש מדיניות כשל מפורשת, שמירה על סודות, בדיקה של גבולות ה־handler, וקוד selector שמחליף במכוון מטא־דאטה של פרוקסי שהועתק בזמן retry. אם לוקחים מהמאמר הזה רק שתי הרגלים, שיהיו אלה: להיכשל בצורה בטוחה כשהפרוקסי נדרש, ומעולם לא להתחייב עם סיסמת פרוקסי בתוך קובץ Python.
שאלות נפוצות
איך מגדירים פרוקסי מותאם אישית ב‑Scrapy עם אימות?
השתמשו בפורמט protocol://username:password@host:port, אבל קודם קודדו את שם המשתמש והסיסמה עם urllib.parse.quote() אם יש בהם תווים מיוחדים. בפרודקשן, קראו את פרטי ההתחברות דרך from_crawler classmethod שמושך אותם ממשתני סביבה, במקום להקשיח אותם בקוד.
איזה מספר priority כדאי להשתמש עבור custom proxy middleware ב‑Scrapy?
350 הוא priority נפוץ ל־selector כי הוא רץ לפני HttpProxyMiddleware ב־750. זה לא מבטיח rotation. retry יקבל פרוקסי חדש רק אם ה־selector מזהה את בקשת ה־retry שהועתקה ומחליף את ערך meta["proxy"] הקודם.
איך מטפלים בפרוקסי מתים ב‑Scrapy באופן אוטומטי?
בנו middleware שעוקב אחרי מספר כשלונות לכל פרוקסי ב־process_response וב־process_exception, מוציא פרוקסיים מה־pool הפעיל אחרי סף כשל, ומחזיר אותם רק אחרי תקופת צינון במקום לחסום אותם לנצח.
האם אפשר להשתמש ב‑Scrapy עם פרוקסי SOCKS5?
ב־Scrapy 2.17, ה־HttpxDownloadHandler הניסיוני מתעד תמיכה ב־SOCKS5 כש־httpx[socks] מותקן. ה־HTTP/1.1 handler המובנה לא תומך בפרוקסי SOCKS. נעלו גרסה/handler והפעילו integration test לפני שתתייחסו למסלול הזה כאל מוכן לפרודקשן.
כמה באמת אפשר לחסוך בעלויות פרוקסי עם דפוס proxy-on-retry?
אין אחוז נייד שאפשר להצהיר עליו. מדדו את החלק של הבקשות המאושרות שמחזירות תוכן תקין ישירות, את ה־bytes שנשלחים דרך כל שכבת פרוקסי, את מספר ה־retries לכל רשומה מוצלחת, ואת תוספת הלטנטיות. ההפחתה שנמדדה בתעבורת פרוקסי היא החיסכון שלכם; אם גישה ישירה אינה מאושרת או אינה תקפה, אל תשתמשו בדפוס הזה.


