דלג לתוכן הראשי
scraping.
חזרה לכל המאמרים

ארכיטקטורת Scraping היברידית — API + HTML

8 במאי 20268 דק׳ קריאה
איור מופשט של שני נתיבי נתונים, אחד מסודר ודיגיטלי (API) והשני מורכב ואורגני (HTML), המתמזגים למבנה אחד.

למה רוב ה-scrapers נשברים בסוף?

בוא נהיה כנים. אם אתה בונה scraper, הוא יישבר. השאלה היא לא אם, אלא מתי וכמה גרוע. רוב הכשלים נובעים מהימור על סוס אחד. או שאתה בונה הכל סביב API לא רשמי שמצאת, או שאתה הולך all-in על גרידת HTML.

גישת "API בלבד" היא מהירה ונקייה, עד שהיא לא. יום אחד המפתחים בצד השני משנים endpoint בלי הודעה מוקדמת, מוסיפים rate limiting אגרסיבי, או פשוט מורידים שדה קריטי מה-response. פתאום, 50% מהנתונים שלך הם null. ראיתי את זה קורה עשרות פעמים. אתה תלוי לחלוטין ברצון הטוב של מישהו אחר.

מצד שני, גישת "HTML בלבד" היא מלחמה מתמדת. אתה נלחם בשינויי CSS selectors, ב-JavaScript שמרנדר את התוכן, ובמערכות הגנה כמו Cloudflare ו-DataDome. זה דורש כלים כבדים כמו Playwright, ניהול פרוקסיז מורכב, וזה איטי ויקר יותר. זה קרב התשה. לפעמים אתה צריך את התותחים הכבדים, אבל להשתמש בהם לכל בקשה זה בזבוז משאבים.

הארכיטקטורה ההיברידית: הטוב משני העולמות

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

זרימת העבודה נראית כך:

  1. בקשת נתונים: המערכת צריכה מידע על מוצר X.
  2. בדיקת Cache: האם יש לנו כבר מידע עדכני על מוצר X? אם כן, תחזיר אותו וסיימנו.
  3. מקור ראשי (API): אם אין מידע ב-cache, פנה ל-API הרשמי/הלא-רשמי. הוא מהיר, הנתונים מובנים, ו-90% מהזמן הוא יעבוד.
  4. בדיקת תקינות: האם ה-API החזיר תשובה מלאה ותקינה (סטטוס 200, כל השדות קיימים)? מעולה. שמור ב-cache, עדכן את הדאטהבייס, וסיים.
  5. Fallback (HTML Scraping): ה-API נכשל? החזיר שגיאת 429? החזיר תשובה ריקה? כאן נכנס לפעולה ה-fallback. המערכת מפעילה scraper מבוסס HTML (למשל, עם Playwright) כדי לגרד את אותו המידע מדף המוצר הציבורי.
  6. נורמליזציה: המידע שחולץ מה-HTML עובר נורמליזציה למבנה הזהה לזה של ה-API.
  7. עדכון סופי: המידע המנורמל נשמר ב-cache ובדאטהבייס.

היופי פה הוא שהמערכת שלך הופכת לעמידה בפני תקלות (resilient). נפילה של ה-API גורמת להאטה זמנית, לא להשבתה מלאה של איסוף הנתונים. זו דוגמה קלאסית של graceful degradation.

בניית מודל נתונים אחיד (Consistent Data Model)

האתגר הגדול ביותר בארכיטקטורה היברידית הוא לגרום לשני מקורות מידע שונים לדבר באותה שפה. ה-API יחזיר לך JSON מסודר, וה-HTML ייתן לך מרק של תגיות. אתה צריך "מתרגם" באמצע.

הדרך הנכונה לעשות את זה היא להגדיר מודל נתונים קנוני אחד. בפייתון, Pydantic או dataclasses הם כלים מושלמים למשימה. אתה מגדיר את האובייקט האידיאלי שלך, ובונה שתי פונקציות נפרדות שכל אחת יודעת לאכלס אותו ממקור אחר.

from pydantic import BaseModel, Field
from typing import Optional

class Product(BaseModel):
    sku: str
    name: str
    price: float
    in_stock: bool
    source: str = Field(description="'api' or 'html'")

def parse_from_api(api_response: dict) -> Optional[Product]:
    # Logic to handle the API's JSON structure
    try:
        return Product(
            sku=api_response.get('product_id'),
            name=api_response.get('title'),
            price=float(api_response.get('price_cents', 0)) / 100,
            in_stock=api_response.get('stock_status') == 'AVAILABLE',
            source='api'
        )
    except (TypeError, KeyError):
        return None

def parse_from_html(html_content: str) -> Optional[Product]:
    # Logic to parse HTML with BeautifulSoup or similar
    # ... this will be more complex ...
    try:
        # Fictional parsing logic
        sku = find_sku_in_html(html_content)
        name = find_name_in_html(html_content)
        price = find_price_in_html(html_content)
        in_stock = find_stock_status_in_html(html_content)

        return Product(
            sku=sku,
            name=name,
            price=price,
            in_stock=in_stock,
            source='html'
        )
    except Exception:
        return None

ברגע שיש לך את המודל האחיד הזה, כל שאר המערכת שלך (הלוגיקה העסקית, שמירה לדאטהבייס) לא צריכה לדעת מאיפה הגיע המידע. היא פשוט מקבלת אובייקט Product נקי ועובדת איתו.

זיהוי דלתא: תפסיק לגרד את מה שכבר יש לך

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

איך עושים את זה? לפני שאתה מעדכן רשומה, השווה את האובייקט החדש שקיבלת (מה-API או מה-HTML) עם הגרסה הישנה שיש לך בדאטהבייס. אם אין הבדל – אל תעשה כלום.

דרך פשוטה היא להשוות hash של הנתונים הרלוונטיים. אפשר ליצור hash של ה-JSON מה-API או של חלק רלוונטי מה-HTML, ולשמור אותו לצד הרשומה. בגירוד הבא, אתה מוריד את הדף, מחשב hash, ומשווה אותו ל-hash השמור. אם הם זהים, אתה יודע ששום דבר מהותי לא השתנה ואתה יכול לדלג על כל תהליך ה-parsing והעדכון. זה קריטי כשמטפלים באתרים עם מגבלות קצב صارמות, כי זה מצמצם דרמטית את מספר הבקשות.

ה-Cache Layer: לא רק לביצועים

כולם חושבים על cache בהקשר של מהירות, אבל בארכיטקטורה היברידית התפקיד שלו חשוב יותר: יציבות. ה-cache (בדרך כלל Redis) יושב בין הצרכנים של המידע (למשל, ה-API הפנימי שלך) לבין ה-scrapers הכאוטיים.

כשה-scraper שלך נתקל בבעיה – ה-API לא זמין, האתר חסם אותך – ה-cache ממשיך להגיש את הגרסה האחרונה והתקינה של המידע שהוא מחזיק. אולי המידע הזה בן שעה, אבל מידע בן שעה עדיף על שגיאת 500. זה נותן לך זמן לתקן את ה-scraper בלי שהמשתמשים ירגישו במלחמה שמתחוללת מאחורי הקלעים.

מתי הגישה ההיברידית היא Overkill?

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

גם במקרים של דרישה למידע בזמן אמת (real-time), כמו מחירי מניות או הימורי ספורט, ה-fallback ל-HTML יכול להכניס latency שיפספס את חלון ההזדמנויות. במצבים כאלה, אתה חייב שהמקור הראשי שלך יהיה אמין ב-99.99% מהזמן, והגישה ההיברידית פחות רלוונטית.

סיפור כישלון אישי: פעם בניתי מערכת כזו לאתר מסחר אלקטרוני. ה-API החזיר מחירים עם 4 ספרות אחרי הנקודה (למשל, 19.9900) וה-HTML הציג מחיר עם 2 ספרות (19.99). מודל הנתונים שלי לא לקח את זה בחשבון. התוצאה? מנגנון זיהוי הדלתא שלי חשב שהמחיר משתנה כל הזמן, מה שהפעיל עדכוני סרק לדאטהבייס אלפי פעמים בדקה. לקח לי יום שלם של דיבאגינג להבין שהבעיה היא בנורמליזציה ולא ב-scraper עצמו. זה שיעור חשוב: השטן נמצא בפרטים הקטנים של שכבת התרגום.

איך מתחילים לבנות את זה?

הסטאק הטכנולוגי לא מסובך מדי. בפייתון, השילוב המנצח הוא:

  • בקשות API: ספריית requests או httpx לאסינכרוניות.
  • גירוד HTML: BeautifulSoup לדפים פשוטים, או Playwright כשצריך להריץ JavaScript ולהתמודד עם הגנות מתקדמות.
  • תזמור (Orchestration): Celery עם RabbitMQ/Redis לניהול תורים, או פשוט cron jobs אם המערכת קטנה.
  • Cache: Redis הוא הבחירה הברורה.
  • בסיס נתונים: PostgreSQL או כל בסיס נתונים רלציוני אחר.

התחילו בקטן. בנו את האינטגרציה עם ה-API. אחר כך, הוסיפו את ה-scraper של ה-HTML. חברו ביניהם עם לוגיקת ה-fallback. לבסוף, הוסיפו את ה-cache layer. בניית ארכיטקטורת web scraping טובה היא תהליך איטרטיבי, לא משהו שבונים במכה אחת. אבל ההשקעה הזו מראש תחסוך לכם אינספור שיחות טלפון בהולות באמצע הלילה.

שאלות נפוצות

היתרון המרכזי הוא חסינות (resilience). מערכת המבוססת API בלבד קורסת לחלוטין כשה-API משתנה, נחסם או מוריד שדות מידע. בארכיטקטורה היברידית, כשל ב-API הוא לא סוף העולם; המערכת פשוט עוברת אוטומטית לתוכנית הגיבוי שלה - גרידת HTML - וממשיכה לספק נתונים. זה הופך את המערכת שלך מאמינה ב-99.9% מהזמן, גם כשהמקורות אינם יציבים.

הטיפול מתבצע בשכבת הנורמליזציה באמצעות מודל נתונים קנוני. אתה מגדיר מבנה נתונים אחיד (למשל, Pydantic model בפייתון) וקובע חוקים ברורים לאיך למלא אותו מכל מקור. לדוגמה, אם ה-API מספק מחיר בסנטים (`9990`) וה-HTML בדולרים (`$99.90`), שכבת הנורמליזציה אחראית להמיר את שני הפורמטים למספר float אחיד (99.9) לפני השמירה. זה מבטיח שהנתונים בבסיס הנתונים שלך יהיו עקביים, ללא תלות במקור.

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

סטאק כלים נפוץ בפייתון יכלול את ספריית `requests` או `httpx` עבור קריאות ה-API המהירות. עבור ה-fallback ל-HTML, נשתמש ב-`BeautifulSoup` עם `requests` לדפים סטטיים, או ב-`Playwright` לאתרים מורכבים הדורשים הרצת JavaScript. כל הלוגיקה הזו תנוהל על ידי מערכת תורים כמו Celery, והנתונים יישמרו במטמון (cache) ב-Redis כדי להבטיח זמינות גבוהה גם כשה-scrapers נתקלים בבעיות.

התרחיש הגרוע ביותר הוא "כשל שקט" (silent failure) בו שני המקורות מחזירים נתונים, אך הם שגויים בצורה עדינה וסותרת. לדוגמה, ה-API מדווח שמוצר אזל מהמלאי, בעוד שה-HTML מציג אותו כזמין (אולי בגלל באג ב-cache של האתר). המערכת צריכה לוגיקה שתחליט איזה מקור הוא "מקור האמת" במקרה של סתירה, או להציף התראה לטיפול ידני. ללא מנגנון כזה, אתה עלול לשמור נתונים שגויים מבלי לדעת על כך במשך ימים.

אהבתם את הכתבה? הצטרפו לניוזלטר ה-AI.

סיכום שבועי של כל מה שחדש ב-AI, פרומפטים מעשיים וביקורות כלים — ישר למייל שלכם.

הירשמו עכשיו

עוד לקריאה