# Extract + Load — requests ו-to_sql📖 שיעור

## מה זה?

עד עכשיו טענו נתונים מקבצים סטטיים (`read_csv`). אבל ETL אמיתי (Extract-Transform-Load) שולף נתונים ממקורות **חיים** — API חיצוני, מסד נתונים — מעבד אותם (כל מה שלמדנו עד כה), וכותב את התוצאה **בחזרה** למקום קבוע. `requests` הוא ה-Extract מ-API; `to_sql` הוא ה-Load למסד נתונים — שני הקצוות של כל צנרת ETL.

## מילות מפתח שחשוב לזכור

• Extract — שליפת נתונים גולמיים ממקור חיצוני (API, קובץ, DB)

• Transform — עיבוד וניקוי הנתונים (כל מה שנלמד בשיעורים הקודמים ביחידה)

• Load — כתיבת התוצאה הסופית ליעד קבוע (DB, קובץ)

• `requests` — ספריית Python לביצוע קריאות HTTP (מקביל ל-`HttpClient` ב-C#)

• `to_sql()` — כותב DataFrame ישירות לטבלת SQL, בלי לבנות שאילתת INSERT ידנית

```python
import requests
import pandas as pd

# Extract — fetching from an external API, just like HTTP GET from the ASP.NET Core unit
response = requests.get("https://api.example.com/orders")
data = response.json()              # decode JSON into a list of dicts
df = pd.DataFrame(data)             # direct conversion to a DataFrame

# Extract from an existing database
import sqlalchemy
engine = sqlalchemy.create_engine("postgresql://user:pass@localhost/mydb")
df_from_db = pd.read_sql("SELECT * FROM orders WHERE status = 'completed'", engine)
```

```python
# Transform — everything already learned in this unit
df["OrderDate"] = pd.to_datetime(df["OrderDate"])
df_clean = df.dropna(subset=["CustomerId"])
summary = df_clean.groupby("CustomerId").agg(Total=("Amount", "sum")).reset_index()
```

```python
# Load — writing back to the database
summary.to_sql("customer_summary", engine, if_exists="replace", index=False)
# creates/replaces the customer_summary table with the processed result — no manual SQL INSERT
```

## הסבר עיקרי

`requests.get` מקביל ל-`HttpClient` ב-C#, אבל בלי async מפורש — קריאת HTTP בפייתון עם `requests` דומה מאוד לרעיון שכבר מוכר מיחידת ASP.NET Core (בקשה, תשובה, `.json()` לפענוח) — רק שהתחביר הבסיסי (לא-async כברירת מחדל) פשוט יותר, מתאים לסקריפטים חד-פעמיים/מתוזמנים בניגוד לשרת שמשרת הרבה בקשות במקביל.

`to_sql` הוא Load אמיתי, לא רק "שמירת קובץ" — בניגוד ל-`to_csv` (כתיבה לקובץ, פשוטה יותר), `to_sql` כותב ישירות לטבלת DB אמיתית — שימושי כשתוצאת העיבוד צריכה "לחיות" במסד נתונים production, נגישה לשירותים אחרים (כמו ה-API ב-C# מיחידות קודמות) לא רק כקובץ מקומי.

`if_exists="replace"` הוא החלטה חשובה — כשה-Load רץ שוב (למשל בהרצה מתוזמנת יומית, נושא השיעור הבא), האם התוצאה החדשה **מחליפה** את הישנה (`"replace"`), **מתווספת** אליה (`"append"`), או שהריצה **נכשלת** אם הטבלה כבר קיימת (`"fail"`, ברירת המחדל)? הבחירה תלויה בטבע הנתונים — דוח יומי-מצטבר לרוב "replace" (הדוח החדש מייצג את כל התמונה), לוג אירועים לרוב "append" (מוסיפים לרשומה קיימת).

## יתרונות

`requests` נותן גישה פשוטה למקורות נתונים חיים (APIs), לא רק קבצים סטטיים; `to_sql` כותב תוצאות ישירות ל-DB בלי SQL ידני; שילוב Extract→Transform→Load בסקריפט אחד ממחיש את כל מחזור החיים של הנתונים.

## חסרונות

`requests.get` בלי טיפול שגיאות (timeout, סטטוס לא-2xx) עלול לקרוס בשקט על בעיות רשת; `to_sql` על DataFrame ענק עלול להיות איטי בהשוואה לכלי Bulk Insert ייעודיים.

## נקודות חשובות

• Extract-Transform-Load: שליפה ממקור, עיבוד, כתיבה ליעד — סדר תמידי

• `requests.get(url).json()` שולף ומפענח מ-API; `pd.read_sql` שולף מ-DB קיים

• `to_sql` כותב DataFrame ישירות לטבלה, עם `if_exists` (`replace`/`append`/`fail`) קובע התנהגות בהרצה חוזרת

• Extract/Load הם שני הקצוות; Transform הוא כל מה שכבר נלמד קודם ביחידה

## טעויות נפוצות

• לא לבדוק `response.status_code` לפני `.json()` — קריסה לא-ברורה אם ה-API החזיר שגיאה

• לבחור `if_exists="fail"` (ברירת מחדל) בטעות בהרצה חוזרת ולקבל שגיאה במקום עדכון מצופה

• לשכוח שה-Transform (ניקוי, סינון) צריך לקרות **לפני** ה-Load, לא אחריו

## סיכום

ETL הוא Extract (שליפה ממקור — API עם `requests`, או DB עם `read_sql`), Transform (עיבוד — כל מה שנלמד קודם ביחידה: merge/groupby/ניקוי), ו-Load (כתיבה ליעד — `to_sql` ל-DB). `if_exists` קובע איך הLoad מתנהג בהרצות חוזרות — `replace`/`append`/`fail`, החלטה שתלויה בטבע הדוח. זהו המסגרת המלאה שהפרויקט המסכם של היחידה יבנה עליה.

## דוקומנטציה רשמית

[Requests — Documentation](https://requests.readthedocs.io/)

[Pandas — to_sql](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html)

---

## תרגילים

### תרגיל 1 — Extract מ-API

**המשימה:** בחרו API ציבורי חינמי (למשל JSONPlaceholder), שלפו נתונים עם `requests.get`, והמירו ל-DataFrame.

**בדיקה:** ה-DataFrame מכיל את הנתונים מה-API, עם עמודות תואמות למבנה ה-JSON שהתקבל.

### תרגיל 2 — Load ל-SQL

**המשימה:** קחו DataFrame מעובד (מתרגיל קודם ביחידה) וכתבו אותו לטבלת SQLite/Postgres מקומית עם `to_sql`.

**בדיקה:** שאילתה ישירה על הטבלה (`SELECT * FROM ...`) מציגה את הנתונים שנכתבו.

---

## פרויקט מסכם

**המשימה:** בנו סקריפט ETL מלא: Extract מ-API ציבורי → Transform (ניקוי + groupby) → Load ל-DB מקומי.

**דרישות:**
1. Extract עם `requests`, כולל בדיקת `status_code` לפני עיבוד
2. Transform: ניקוי (`fillna`/`dropna` לפי הצורך) וקיבוץ (`groupby`+`agg`) לסיכום משמעותי
3. Load עם `to_sql`, ובחירה מנומקת של `if_exists` (`replace` או `append`) לפי טבע הנתונים

**בדיקה:** הרצת הסקריפט מקצה לקצה מייצרת טבלה ב-DB עם נתונים מעובדים ונכונים; הרצה חוזרת מתנהגת כצפוי לפי `if_exists` שנבחר.

## מה בפרק הבא

בפרק הבא — **Scheduling ולוגים**: איך מריצים את סקריפט ה-ETL אוטומטית מדי יום (cron/APScheduler), עם לוגים מובנים שמתעדים מה קרה בכל הרצה.
