למה הגישה הסטנדרטית נכשלת מול אתרים מוניציפליים
רובנו רגילים לקרב מול WAFים מודרניים. אנחנו חושבים במונחים של fingerprinting, אתגרי JavaScript, ו-CAPTCHAs. אבל כשניגשים לפרויקט scraping עיריית ירושלים, סט הבעיות משתנה. הבעיה המרכזית היא לא חסימה אקטיבית, אלא ארכיטקטורה מבוססת-state. חשבו על מערכת היתרי הבנייה או ארכיון המכרזים. לעיתים קרובות, כדי להגיע לדף התוצאות, צריך לעבור דרך טופס חיפוש שמבצע סדרה של קריאות AJAX ברקע, מגדיר קוקיז ספציפיים, ואולי אפילו משתמש ב-ViewState. אם תנסה לגשת ישירות ל-URL של התוצאות עם requests, תקבל במקרה הטוב דף ריק, ובמקרה הרע שגיאת 500 כי הסשן שלך לא אותחל כראוי.
זהו כישלון שקט. ה-status code הוא 200, אבל אין דאטה. ראיתי צוותים מבזבזים ימים בניסיון להנדס לאחור את רצף הבקשות הזה, רק כדי לגלות שהטוקנים מתעדכנים בצד הלקוח עם JavaScript. לכן, הגישה הראשונה שלי לפרויקט כזה היא לוותר מראש על HTTP clients פשוטים. אנחנו צריכים browser אמיתי. תפסיקו עם Selenium לפרויקטים חדשים. Playwright מנצח אותו ב-2025 בכל מטריקה רלוונטית, במיוחד בביצועים וב-API האסינכרוני שלו. הוא מאפשר לנו לדמות את מסע המשתמש המלא, כולל אינטראקציות JS, בלי לנחש את הלוגיקה הפנימית של האתר. כך אנחנו מבטיחים שה-scraper שלנו תמיד מגיע לדפי המידע עם state תקין.
איסוף קטלוג וניטור שינויים: יותר מסתם לולאה על דפים
אחד ה-use cases הנפוצים ביותר הוא איסוף קטלוג עיריית ירושלים – בין אם זה קטלוג השירותים לתושב, רשימת המכרזים הפתוחים, או כל מאגר מידע ציבורי אחר. הנטייה הראשונית היא למצוא את דף הרשימה הראשי ולהתחיל לעבור על הפג'ינציה. הבעיה היא שבאתרים ממשלתיים רבים, הפג'ינציה לא תמיד אמינה. לפעמים היא מבוססת JS, לפעמים היא נשברת אחרי עמוד 20, ולפעמים היא פשוט לא חושפת את כל אלפי הרשומות הזמינות.
האסטרטגיה הנכונה היא לחפש נקודות כניסה מרובות. במקום לסמוך על פג'ינציה אחת, נסו לגשת לדאטה דרך פונקציית החיפוש עם פרמטרים שונים (למשל, חיפוש לפי כל אות באלפבית, או לפי טווחי תאריכים). זה מייצר עומס גבוה יותר על התשתית שלנו, אבל התוצאה היא כיסוי נתונים מלא בהרבה. עבור ניטור מחירים עיריית ירושלים (או ליתר דיוק, ניטור דמי שירותים או סכומים במכרזים), המשימה הופכת להיות ניהול hash-ים. במקום לשמור את כל ה-HTML, אנחנו מחלצים את השדות הרלוונטיים (למשל, שמות מכרזים וסכומים), מייצרים hash ייחודי לכל רשומה, ומשווים אותו מול ה-hash מהריצה הקודמת. זה מצמצם את נפח האחסון הנדרש פי 100 ומאפשר זיהוי שינויים כמעט מיידי. המטרה היא להפוך את המידע הלא-מובנה באתר לקובץ נתונים או API מסודר שאפשר לעבוד איתו.
מעקב זמינות ומודיעין תחרותי בסקטור הציבורי
במבט ראשון, 'מודיעין מתחרים' נשמע לא רלוונטי לעירייה. אבל תחשבו על חברות בנייה, משרדי אדריכלים או יזמים. עבורם, מעקב מלאי/זמינות עיריית ירושלים הוא קריטי. הם לא עוקבים אחרי מלאי של מוצרים, אלא אחרי סטטוס של היתרי בנייה, תוכניות מתאר, או מכרזים שעומדים להיסגר. המידע הזה הוא זהב. היכולת לדעת מתי היתר של פרויקט גדול משנה סטטוס יכולה להשפיע על החלטות עסקיות משמעותיות.
כאן ה-scraper הופך ממחלץ נתונים פאסיבי לכלי ניטור אקטיבי. אנחנו לא מורידים את כל הדאטה-בייס כל יום. זה לא יעיל ומעלה את הסיכוי לחסימה. במקום זאת, אנחנו מריצים scraper ממוקד על רשימת היתרים או מכרזים ספציפית, פעם בשעה או פעם ביום, ומחפשים שינוי רק בשדה הספציפי של סטטוס בקשה. המערכת צריכה להיות בנויה עם לוגיקת התראות. ברגע שזוהה שינוי, המערכת שולחת Webhook או אימייל. הצלחנו להקים מערכת כזו שרצה בקצב של 20-30 בקשות לדקה פר IP, עם אחוזי הצלחה של 99.8% על פני חודשים, על ידי שימוש ב-proxy pool קטן וניהול קוקיז חכם. זהו מודיעין מתחרים עיריית ירושלים הלכה למעשה: הפיכת מידע ציבורי גולמי לתובנה עסקית בזמן אמת.
מתי הגישה הזו היא Overkill (ואפילו מזיקה)
למרות כל מה שאמרתי על Playwright ודפדפנים מלאים, יש מצבים שבהם זו פשוט הגישה הלא נכונה. אם המטרה שלך היא פשוט להוריד קובץ PDF או CSV שמתעדכן פעם ביום ומופיע תחת URL קבוע, שימוש ב-Headless Browser הוא בזבוז משאבים עצום. זה כמו להשתמש בטנק כדי לפצח אגוז. במקרים כאלה, curl פשוט או סקריפט Python עם requests יעשו את העבודה פי 100 יותר מהר ובפחות מ-1% מהעומס על ה-CPU והזיכרון.
האתגר הוא לזהות את המקרים האלה. לפני שאתה כותב שורת קוד אחת של Playwright, פתח את ה-DevTools ברשת ובדוק מה קורה כשאתה לוחץ על הקישור. האם זו בקשת GET פשוטה שמחזירה את הקובץ? אם כן, עבודה של חמש דקות. האם היא דורשת קוקי מסוים? אולי אפשר לקבל אותו בבקשה אחת נפרדת ואז להשתמש בו. המפתח הוא להתאים את הכלי למורכבות הבעיה. הפעלת צי של דפדפנים כדי להוריד 50 קבצים סטטיים היא לא רק לא יעילה, היא גם מגדילה את הסיכון לתקלות. דפדפנים קורסים, תהליכי זומבי נתקעים. אם אפשר להימנע מזה, תמיד עדיף. השתמש בכוח רק כשאתה באמת צריך אותו – מול אינטראקציות JS מורכבות, טפסים דינמיים, או טעינת תוכן אסינכרונית.
בניית Data Pipeline יציב: מ-Scraping ל-API שימושי
האיסוף הוא רק ההתחלה. הנתונים הגולמיים מאתר כמו עיריית ירושלים הם בדרך כלל תוהו ובוהו: תאריכים בפורמטים שונים, שדות ריקים, תגיות HTML בתוך טקסט. השלב הבא, והחשוב לא פחות, הוא הניקוי והנרמול. כאן כלים כמו BeautifulSoup או Parsel נכנסים לתמונה. אחרי ש-Playwright מביא לנו את ה-HTML המלא, אנחנו מעבירים אותו ל-parser ייעודי. אל תנסו לנתח HTML עם ביטויים רגולריים. פשוט אל. זה שביר ומתכון לאסון בתחזוקה.
המטרה הסופית היא בדרך כלל API / קובץ נתונים עיריית ירושלים שצוותים אחרים בארגון יכולים לצרוך. זה אומר שהפלט שלנו חייב להיות עקבי. אנחנו בונים שכבת validation (למשל עם Pydantic) שמוודאת שכל רשומה עומדת בסכמה שהגדרנו: תאריכים הופכים לאובייקטי datetime, מספרים הם באמת מספרים, וערכים חסרים מסומנים כ-null. את הפלט הנקי הזה אנחנו יכולים לטעון ל-database, לכתוב לקובץ CSV, או לחשוף דרך API פנימי. תהליך מלא, מקצה לקצה, יכול לרוץ על תשתית serverless, כאשר scraper רץ כל כמה שעות, מעבד כ-5,000-10,000 דפים בריצה, ומעדכן את מאגר הנתונים המרכזי. עם טיפול נכון בשגיאות רשת ו-retries, אפשר להגיע ליציבות גבוהה מאוד, גם מול תשתית שאינה צפויה.
