למה סקריפט פשוט ב-requests ייכשל על AllJobs

הטעות הראשונה והנפוצה ביותר היא לחשוב שאפשר פשוט לשלוח בקשת GET ולקבל את כל המידע. תפתחו את ה-DevTools ותראו ש-AllJobs לא מגיש HTML סטטי עם כל המשרות. התוכן המרכזי, רשימת המודעות, נטען דינמית באמצעות JavaScript. בקשת requests תחזיר לכם שלד HTML ריק מתוכן, עם כמה תגי <script> שיטענו את האפליקציה. זהו. המשחק נגמר עוד לפני שהתחיל.

כדי להתמודד עם זה, אין מנוס משימוש ב-headless browser. תפסיקו עם Selenium לפרויקטים חדשים. Playwright מנצח אותו ב-2025 בכל מטריקה רלוונטית, במיוחד בביצועים וב-API הנקי שלו. עם Playwright, אתם מריצים מנוע דפדפן אמיתי (כמו Chromium) שיודע להריץ את ה-JS, לבצע את קריאות ה-API הפנימיות, ולעבד את הדף למצב הסופי שלו – בדיוק כמו שמשתמש רואה אותו. רק אז אפשר להתחיל לחלץ את הנתונים שמעניינים אותנו, כמו שמות מודעות וקטגוריות.

אבל גם כאן יש מלכודת. הפעלת דפדפן מלא לכל בקשה היא כבדה ואיטית. במקום לעשות full page load לכל עמוד משרה, הגישה הנכונה היא להשתמש בדפדפן כדי לאסוף את רשימת המשרות מעמודי החיפוש, ואז לנתח את תעבורת הרשת (XHR/Fetch requests) כדי לזהות את ה-API הפנימי שממנו נטענות המשרות. לרוב, אפשר לדמות את הקריאות האלה ישירות, ולחסוך 90% ממשאבי העיבוד. זה ההבדל בין סקריפט שרץ שעות לבין כזה שמסיים את העבודה בדקות.

תכנון הסקרייפר: איך לגשת לקטלוג של 30,000+ משרות

איסוף קטלוג מלא מ-AllJobs הוא לא משימה חד-פעמית, אלא תהליך מתמשך. אנחנו מדברים על קטלוג של עשרות אלפי משרות פעילות בכל רגע נתון, שמתעדכן ללא הרף. סקריפט שרץ באופן לינארי פשוט לא יעמוד בקצב ולא יהיה עמיד לתקלות. אתם חייבים ארכיטקטורת תורים.

המודל שעובד הכי טוב הוא הפרדה בין 'מגלים' (discoverers) ל'מעבדים' (processors). המגלים סורקים את עמודי הקטגוריות והחיפוש (כ-2,000-3,000 עמודים) ומכניסים את כתובות ה-URL של המשרות הבודדות לתור (למשל, Redis או RabbitMQ). בצד השני, worker-ים שולפים משימות מהתור ומבצעים את ה-scraping לעמוד הספציפי. היתרון כאן הוא עצום: המערכת הופכת להיות מקבילית וא-סינכרונית. אם worker אחד נופל, המשימה חוזרת לתור ומטופלת על ידי אחר. אם צריך להגביר קצב, פשוט מוסיפים עוד worker-ים. גישה זו חיונית במיוחד עבור use cases כמו בניית API / קובץ נתונים AllJobs יומי, שדורש עמידות ואמינות.

יתרון נוסף הוא ניהול קצבים. במקום שכל התהליך ירוץ מכתובת IP אחת, כל worker יכול להשתמש בפרוקסי אחר, מה שמפזר את העומס ומקשה על הזיהוי. אם אתם נתקלים בחסימות, תוכלו ללמוד איך לנהל אותן מה-מדריך לטיפול בשגיאות 429 ותגובות דומות. זה הבסיס למערכת שיכולה לרוץ 24/7 בלי בייביסיטר.

המשחק האמיתי: התמודדות עם Rate Limiting ו-Fingerprinting

אחרי שפתרתם את בעיית ה-JS rendering, מתחילה הבעיה האמיתית: חסימות. AllJobs, כמו כל אתר בסדר גודל כזה, לא אוהב שמפציצים אותו בבקשות. המנגנון הראשון שתפגשו הוא rate limiting פשוט מבוסס IP. אם תשלחו יותר מדי בקשות בפרק זמן קצר מאותה כתובת, תקבלו שגיאות HTTP 429 (Too Many Requests) או פשוט CAPTCHA.

הפתרון הבסיסי הוא rotation של פרוקסי. אבל אל תפלו למלכודת של פרוקסי זולים מ-datacenter. ה-IP ranges שלהם מוכרים ורובם כבר מסומנים. כאן נכנסים לתמונה פרוקסיים מסוג residential או mobile, שמסווים את התנועה שלכם כתנועה של משתמשים אמיתיים. אבל גם זה לא מספיק. מערכות מתקדמות יותר משתמשות ב-fingerprinting של הדפדפן. הן בודקות מאפיינים כמו רזולוציית מסך, פונטים מותקנים, גרסת הדפדפן, וה-User-Agent. אם אתם שולחים אלפי בקשות וכולן עם אותו fingerprint בדיוק, זה דגל אדום ענק.

כדי לעקוף את זה, צריך להשתמש בפתרונות מתקדמים יותר. Playwright, למשל, מגיע עם יכולות מובנות להסוואה, אבל לרוב תצטרכו להשתמש בספריות נוספות כמו playwright-extra עם תוסף ה-stealth. התוסף הזה מבצע רנדומיזציה חכמה לפרמטרים של ה-fingerprint, מה שהופך כל חיבור לייחודי ומקשה מאוד על הזיהוי. למידע נוסף על הטכניקות האלה, אפשר לקרוא את ה-מדריך המעמיק ל-Playwright stealth.

מודיעין תחרותי ומעקב זמינות: מה באמת אפשר להפיק מהדאטה

אז יש לנו דאטה. מה עושים איתו? מעבר לבניית מאגר משרות, הערך האמיתי נמצא בניתוח המגמות. מודיעין מתחרים AllJobs הוא אחד ה-use cases החזקים ביותר. חברות יכולות לעקוב אחרי דפוסי הגיוס של המתחרים שלהן: אילו סוגי משרות הן פותחות, באילו טכנולוגיות הן משתמשות (על סמך תיאורי המשרה), ומה קצב הגיוס שלהן. זה מידע אסטרטגי ששווה המון.

דוגמה קונקרטית: נניח שאתם רוצים לעקוב אחרי משרות 'Data Scientist' בתל אביב. על ידי scraping יומי של AllJobs, אפשר לזהות מתי משרות חדשות מופיעות, כמה זמן הן נשארות פתוחות בממוצע (אינדיקציה לביקוש או לקושי בגיוס), ומהן דרישות השכר הממוצעות (אם הן מצוינות). זהו למעשה מעקב מלאי/זמינות AllJobs – רק שה'מלאי' הוא משרות פתוחות. אפשר לזהות מגמות כמו עלייה בדרישה למפתחי Python עם ניסיון ב-LLMs, או ירידה בכמות משרות ה-DevOps. המידע הזה יכול להנחות החלטות גיוס ואסטרטגיה עסקית.

האתגר הטכני כאן הוא ניהול המצב (state). צריך לדעת להבדיל בין משרה חדשה, משרה שהוסרה, ומשרה שעודכנה. זה דורש תכנון של מפתח ייחודי (primary key) לכל משרה (למשל, שילוב של ID המשרה וה-URL) ושמירת היסטוריה של שינויים. פרויקט כזה מייצר בקלות מאות מגה-בייטים של דאטה בחודש, שצריך לאחסן ולתחזק בצורה יעילה.

איפה הגישה הזו לא תעבוד (או שהיא Overkill)

בואו נהיה רגע כנים. לא כל פרויקט דורש בניית מערכת מבוזרת עם פרוקסי residential ו-headless browsers. אם כל מה שאתם צריכים זה לבדוק פעם בשבוע אם חברה ספציפית פרסמה משרה חדשה, כנראה שהשקעת המאמץ בבניית מערכת כזו היא מוגזמת. סקריפט פשוט שירוץ מקומית עם Playwright וכמה time.sleep ארוכים כנראה יעשה את העבודה.

המורכבות שאנחנו מדברים עליה רלוונטית כשאתם צריכים דאטה בהיקף גדול, בזמן אמת, ובאמינות גבוהה. למשל, אם אתם בונים שירות שמתחרה ב-AllJobs, או מספקים ניתוחי שוק יומיים ללקוחות. במקרים האלה, כל שעת השבתה היא קריטית. אבל עבור ניטור מחירים AllJobs (כלומר, ניתוח טווחי שכר) בקנה מידה קטן, או איסוף נתונים חד-פעמי למחקר, אפשר להסתפק בגישה פשוטה יותר.

נקודת כשל נוספת היא שינויים במבנה האתר. בניתם סקרייפר מושלם שמסתמך על סלקטורים של CSS? נהדר. אבל מחר AllJobs יכולים לעשות deploy לגרסה חדשה, לשנות את שמות הקלאסים, והכל יישבר. לכן, חשוב לבנות מערכת ניטור שמתריעה על ירידה חדה באחוזי ההצלחה (למשל, מתחת ל-95%) או עלייה בכמות השדות הריקים. בלי מנגנון כזה, אתם עלולים לגלות רק אחרי שבוע שהסקריפט שלכם מחזיר זבל. במקרים רבים, הפתרון הוא לא רק טכני אלא גם תהליכי – תחזוקה שוטפת היא חלק מהעבודה.