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

Pipeline של Normalization: איך הופכים דאטה גולמי לנכס אמיתי

8 במאי 20268 דק׳ קריאה
דיאגרמה מופשטת המציגה זרימת נתונים דרך שלבים שונים של ניקוי וטרנספורמציה בתוך צינור עיבוד נתונים

למה דאטה גולמי מ-scraping הוא חוב, לא נכס

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

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

ההבדל בין פרויקט scraping חובבני למערכת production-grade הוא מה שקורה אחרי שהדאטה יורד מהרשת. זהו ה-data normalization pipeline. זה התהליך שהופך רעש דיגיטלי למידע איכותי שאפשר לסמוך עליו.

ארבעת השלבים של Pipeline שמכבד את עצמו

כל פיסת מידע שעוברת אצלנו עוברת מסלול קבוע בן ארבעה שלבים. לא מדלגים, לא מעגלים פינות. Raw → Cleaned → Enriched → Published.

שלב 1: Raw - הביצה הגולמית

כאן הדאטה נוחת כמו שהוא. בלי פילטרים, בלי שינויים. אם ה-scraper הוציא HTML מבולגן, זה מה שיישמר. אם המחיר הוא `"₪ 1,299.90 *חדש*"`, זו בדיוק המחרוזת שתהיה כאן. המטרה של השלב הזה היא אחת: אחסון מהיר ועמיד של המקור, למקרה שנצטרך לחזור אליו לדיבאגינג או re-processing. זה ה-checkpoint הבטוח שלנו לפני שמתחילים לגעת בדאטה.

שלב 2: Cleaned - ניקוי וסטנדרטיזציה

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

  • הסרת רעש: הורדת תגיות HTML, רווחים לבנים מיותרים, תווים מיוחדים.
  • תיקון סוגי נתונים: הפיכת מחרוזת מחיר למספר (float או integer), המרת תאריך טקסטואלי ("אתמול", "15 במאי, 2025") לפורמט ISO 8601 סטנדרטי (`2025-05-15T00:00:00Z`).
  • טיפול בערכים חסרים: החלטה מפורשת מה עושים עם nulls. האם זה `null` אמיתי, 0, או מחרוזת ריקה? חייבים להיות עקביים.

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


import re
from datetime import datetime

def clean_product_data(raw_item: dict) -> dict:
    cleaned = {}
    
    # Clean price
    price_str = raw_item.get('price', '0')
    price_digits = re.findall(r'[\d,.]+', price_str)
    if price_digits:
        price_val = float(price_digits[0].replace(',', ''))
        cleaned['price'] = price_val
    else:
        cleaned['price'] = None

    # Standardize date
    date_str = raw_item.get('last_updated', '')
    try:
        # Assuming format like 'Updated on: May 15, 2025'
        dt_obj = datetime.strptime(re.sub(r'Updated on: ', '', date_str), '%B %d, %Y')
        cleaned['last_updated_iso'] = dt_obj.isoformat()
    except ValueError:
        cleaned['last_updated_iso'] = None
        
    # Simple title cleaning
    title = raw_item.get('title', '')
    cleaned['title'] = title.strip()

    return cleaned

# Example usage
raw = {
    'title': '  Awesome Product   ',
    'price': 'Only ₪499.90!',
    'last_updated': 'Updated on: June 22, 2025'
}

clean = clean_product_data(raw)
# {'price': 499.9, 'last_updated_iso': '2025-06-22T00:00:00', 'title': 'Awesome Product'}

שלב 3: Enriched - הוספת ערך וקונטקסט

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

  • המרת מטבעות: אם אספנו מחירים ב-USD, EUR ו-ILS, כאן נמיר הכל למטבע בסיס אחד לפי שערים עדכניים.
  • Geocoding: הפיכת כתובת טקסטואלית לקואורדינטות (latitude/longitude).
  • חישובים מורכבים: חישוב אחוז הנחה על סמך מחיר מקורי ומחיר מבצע.
  • הצמדה ל-SKU פנימי: זיהוי המוצר והצמדתו למזהה ייחודי במערכת הפנימית שלנו.

השלב הזה הופך אוסף של עובדות בודדות לתובנות עסקיות.

שלב 4: Published - מוכן לצריכה

זהו השלב הסופי. הדאטה עבר ניקוי והעשרה, והוא מוכן להיחשף לעולם. "העולם" יכול להיות טבלה ב-Data Warehouse כמו BigQuery או Snowflake, אינדקס ב-Elasticsearch, או API פנימי שמשרת את אפליקציית המובייל. הדאטה פה הוא אמין, עקבי, ובעל סכמה קבועה ומתועדת. אפשר לבנות עליו.

Schema Mapping: הגיבור השקט של ה-Pipeline

בואו נדבר על הבעיה הכי נפוצה ב-scraping מכמה מקורות. אתר א' קורא למחיר `price`, אתר ב' קורא לו `discountedPrice`, ואתר ג' בכלל מטמיע אותו בתוך אובייקט `offerDetails.finalPrice`. אם לא תטפל בזה, תמצא את עצמך עם טבלה מבולגנת שאי אפשר לתחקר.

הפיתרון הוא Schema Mapping. אנחנו מגדירים סכמה קנונית (אחידה) עבור כל סוג ישות (למשל, "מוצר") וממפים את השדות מכל מקור לסכמה הזו. זה יכול להיות קובץ YAML פשוט או מנגנון מורכב יותר.

דוגמת מיפוי פשוטה:


{
  "canonical_schema": {
    "product_name": "string",
    "price_final": "float",
    "currency": "string",
    "stock_status": "string"
  },
  "source_A_mapping": {
    "product_name": "$.title",
    "price_final": "$.price",
    "currency": "ILS", // Hardcoded for this source
    "stock_status": "$.inventory.status"
  },
  "source_B_mapping": {
    "product_name": "$.productName",
    "price_final": "$.offers[0].price",
    "currency": "$.offers[0].priceCurrency",
    "stock_status": "$.availability"
  }
}

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

כלים לארגון הכאוס: Orchestration ו-Transformation

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

  1. Orchestration (תזמור): כלים כמו Apache Airflow או Prefect אחראים על הרצת ה-pipeline כולו. הם מריצים את ה-scraper, מחכים שהוא יסיים, מפעילים את סקריפט הניקוי, אחריו את סקריפט ההעשרה, ומטפלים בשגיאות, retries, ותלויות בין השלבים. הם המוח של המבצע.
  2. Transformation (טרנספורמציה): כשהדאטה כבר נחת בדאטהבייס או ב-warehouse, כלים כמו dbt (data build tool) נכנסים לפעולה. dbt מאפשר להגדיר את שלבי הניקוי וההעשרה באמצעות קוד SQL, ומנהל את כל התלויות ביניהם. הוא מעולה לשלב שבין ה-Raw ל-Published, במיוחד כשעובדים בסביבת דאטה מודרנית.

השילוב הקלאסי הוא Airflow שמריץ את ה-scraper ומעלה את הדאטה הגולמי ל-BigQuery, ואז מפעיל ריצה של dbt שמבצעת את כל הטרנספורמציות בתוך ה-warehouse.

סיפור אימה מהשטח: כשה-Pipeline כושל

לפני כמה שנים, בניתי מערכת שאספה נתוני מוצרים מאתרי סופרמרקט באירופה. אחד השדות היה משקל המוצר. פשוט, נכון? ממש לא. מקור אחד נתן משקל בגרמים ('500g'), אחר בקילוגרמים ('0.5 kg'), שלישי השתמש במחרוזת חופשית ('approx. 1kg'), והגרוע מכל - מקור רביעי פשוט נתן מספר (500) בלי יחידה. ה-pipeline הראשוני שלי היה נאיבי. הוא פשוט חילץ את המספרים. התוצאה? המערכת ערבבה בין 500 גרם ל-500 קילו. דשבורד הניהול הראה שהמשקל הממוצע של יוגורט הוא 250 ק"ג. החלטות עסקיות על לוגיסטיקה ושטחי מדף התקבלו על בסיס הזבל הזה. לקח לנו שבוע להבין את מקור הבעיה ולתקן את הנתונים ההיסטוריים. זה היה שיעור כואב בחשיבותה של נורמליזציה קפדנית, במיוחד ביחידות מידה. כישלון כזה הוא לא רק באג, הוא סימפטום של ארכיטקטורת scraping שברירית שלא לוקחת בחשבון את הכאוס של העולם האמיתי.

למה כל זה שווה את המאמץ?

בניית pipeline כזה דורשת זמן, מחשבה ומשמעת. אז למה לא פשוט לזרוק הכל לאיזה S3 bucket ולקוות לטוב? כי הגישה הזו לא סקייבילית. היא מייצרת "ביצת נתונים" (data swamp) שאיש לא סומך עליה.

Pipeline נורמליזציה טוב מבטיח:

  • אמינות: כולם בחברה יודעים שהנתונים בטבלת ה-`products_published` נקיים וניתן לסמוך עליהם. זה בונה אמון.
  • יעילות: אנליסטים לא מבזבזים 80% מהזמן שלהם על ניקוי דאטה לפני שהם בכלל מתחילים לעבוד.
  • סקיילביליות: קל יותר להוסיף מקור נתונים חדש. פשוט כותבים לו mapping לסכמה הקיימת, וכל שאר ה-pipeline ממשיך לעבוד.

בסופו של יום, המטרה של web scraping היא לא לאסוף דפים, אלא לייצר תובנות. בלי data normalization pipeline, כל מה שיש לך זה אוסף של דפים. עם pipeline, יש לך נכס אסטרטגי.

שאלות נפוצות

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

dbt הוא כלי אידיאלי כאשר הנתונים הגולמיים שלך כבר נמצאים בתוך data warehouse שתומך ב-SQL, כמו BigQuery, Snowflake או Redshift. הוא מצטיין בטרנספורמציות מבוססות סטים (set-based) שניתן לבטא היטב ב-SQL. טרנספורמציה מבוססת קוד, למשל עם Pandas בפייתון, עדיפה למשימות מורכבות יותר שדורשות לוגיקה איטרטיבית, קריאות API חיצוניות (למשל להעשרה), או עיבוד של נתונים לא מובנים כמו קבצי טקסט או תמונות, לפני שהם בכלל מגיעים ל-warehouse.

שינויים במבנה האתר (schema drift) הם בלתי נמנעים ודורשים ניטור אקטיבי. הפתרון הטוב ביותר הוא לשלב בדיקות אימות נתונים (data validation) בכל שלב ב-pipeline, למשל באמצעות כלים כמו Great Expectations. הבדיקות יכולות לוודא ששדה המחיר הוא תמיד מספר חיובי, שכתובת ה-URL תקינה, או שכמות הרשומות החדשות נמצאת בטווח צפוי. כאשר בדיקה נכשלת, המערכת צריכה לשלוח התראה מיידית לצוות כדי שיוכלו לעדכן את ה-scraper או את ה-schema mapping בהתאם.

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

Deduplication (מניעת כפילויות) צריכה להתרחש בדרך כלל בשלב ה-Cleaned, רגע לפני שלב ה-Enriched. הסיבה לכך היא שבשלב הגולמי, רשומות עשויות להיראות שונות בגלל רווחים לבנים או פרטים זניחים אחרים, אך לאחר ניקוי הן יתגלו כזהות. ביצוע deduplication אחרי הניקוי מבטיח שאתה מזהה כפילויות אמיתיות על בסיס מזהה ייחודי נקי (כמו SKU או ID מוצר). זה גם חוסך משאבים, כי כך אתה לא מבצע קריאות API יקרות בשלב ההעשרה על רשומות כפולות שתזרוק בכל מקרה.

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

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

הירשמו עכשיו

עוד לקריאה