עבודה עם api היא אחת המשימות הכי נפוצות באוטומציה בפייתון: משיכת נתונים ממערכת אחת, שליחת אירועים למערכת אחרת, בדיקת סטטוס, יצירת דוחות, או חיבור בין כלים פנימיים. בפועל, רוב העבודה אינה “לקרוא URL”, אלא להבין איך שולחים בקשה נכונה, איך מפענחים JSON, איך מוסיפים אימות, ומה עושים כשהשרת מחזיר שגיאה.
במאמר הזה נבנה דוגמאות קטנות, רצות ובטוחות עם requests. כדי לא להיות תלויים בשירות חיצוני, כל דוגמה תקים שרת מקומי קטן על המחשב שלכם ואז תשלח אליו בקשות. כך אפשר ללמוד את העיקר בלי להירשם לשירות, בלי מפתחות אמיתיים ובלי הפתעות.
אם אתם עדיין בתחילת הדרך עם סביבת עבודה, כדאי לעבור קודם על צעדים ראשונים. אם אתם רוצים לחזק את הבסיס של השפה עצמה, יש גם מדריך נפרד על פייתון.
מה צריך להכין לפני שמתחילים
הדוגמאות כאן מיועדות ל־Python 3. החבילה המרכזית היא requests, שאינה חלק מהספרייה הסטנדרטית של פייתון ולכן צריך להתקין אותה בנפרד.
Linux/macOS עם bash:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install requestsWindows עם PowerShell:
py -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install requestsהפקודות יוצרות סביבת עבודה מקומית ומתקינות לתוכה את requests. אם אתם עובדים בפרויקט קיים, ודאו שהסביבה הפעילה היא באמת זו שבה התקנתם את החבילה. הרבה תקלות מתחילות מערבוב בין כמה התקנות של פייתון.
מה זה בעצם API, ומה הקשר ל־JSON
API הוא ממשק שמאפשר לתוכנה אחת לדבר עם תוכנה אחרת. כשמדברים על עבודה ברשת, בדרך כלל הכוונה היא ל־HTTP API: שולחים בקשת HTTP לכתובת מסוימת ומקבלים תשובה.
JSON הוא פורמט טקסטואלי פשוט להעברת נתונים. בפייתון הוא מתורגם בדרך כלל ל־dict, list, מחרוזות, מספרים, True, False ו־None.
| מושג | מה הוא אומר בפועל | דוגמה |
|---|---|---|
GET |
בקשת קריאה | קבלת פרטי משתמש |
POST |
שליחת נתונים ליצירה או פעולה | יצירת הערה חדשה |
headers |
מידע נלווה לבקשה | אימות, סוג תוכן |
status_code |
קוד התוצאה מהשרת | 200, 404, 500 |
timeout |
כמה זמן לחכות לפני שמוותרים | מניעת סקריפט שנתקע לנצח |
קריאת api פשוטה עם requests.get
בדוגמה הראשונה נקים שרת מקומי שמחזיר פרטי משתמש ב־JSON, ואז נקרא אליו עם requests.get. זה מדמה שירות אמיתי, אבל הכול נשאר על המחשב שלכם.
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/users/1":
data = {"id": 1, "name": "Dana", "active": True}
body = json.dumps(data).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
return
server = HTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
base_url = f"http://127.0.0.1:{server.server_port}"
response = requests.get(f"{base_url}/users/1", timeout=3)
response.raise_for_status()
user = response.json()
print(user["name"])
print(user["active"])
finally:
server.shutdown()מה קורה כאן? השרת המקומי מאזין על פורט פנוי, מחזיר JSON עבור הנתיב /users/1, והלקוח קורא אותו עם requests. הקריאה ל־raise_for_status() חשובה: היא הופכת קודי שגיאה כמו 404 או 500 לחריגה במקום לתת לקוד להמשיך כאילו הכול תקין.
פלט צפוי:
Dana
Trueשליחת JSON עם POST ואימות Bearer
הרבה ממשקים דורשים אימות. אחת השיטות הנפוצות היא כותרת HTTP בשם Authorization עם הערך Bearer .... בדוגמה הבאה לא נשתמש במפתח אמיתי. במקום זה נקרא ערך ממשתנה סביבה בשם DEMO_API_TOKEN, ואם הוא לא קיים נשתמש בערך מקומי ברור לדמו.
import json
import os
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests
TOKEN = os.environ.get("DEMO_API_TOKEN", "local-demo-token")
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/notes":
self.send_response(404)
self.end_headers()
return
expected = f"Bearer {TOKEN}"
actual = self.headers.get("Authorization")
if actual != expected:
body = json.dumps({"error": "unauthorized"}).encode("utf-8")
self.send_response(401)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
length = int(self.headers.get("Content-Length", "0"))
raw_body = self.rfile.read(length)
payload = json.loads(raw_body.decode("utf-8"))
result = {"id": 101, "title": payload["title"], "saved": True}
body = json.dumps(result).encode("utf-8")
self.send_response(201)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
return
server = HTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
base_url = f"http://127.0.0.1:{server.server_port}"
headers = {"Authorization": f"Bearer {TOKEN}"}
payload = {"title": "Check backups", "priority": "normal"}
response = requests.post(
f"{base_url}/notes",
json=payload,
headers=headers,
timeout=3,
)
response.raise_for_status()
print(response.status_code)
print(response.json())
finally:
server.shutdown()שימו לב לפרמטר json=payload. הוא עושה שני דברים שימושיים: ממיר את המילון ל־JSON ומוסיף כותרת מתאימה של סוג תוכן. זה עדיף ברוב המקרים על שליחה ידנית דרך data=..., אלא אם יש לכם סיבה ספציפית לשלוט בכל פרט.
פלט צפוי:
201
{'id': 101, 'title': 'Check backups', 'saved': True}בפרויקט אמיתי אל תכתבו טוקנים בתוך הקוד. השתמשו במשתני סביבה, קובץ הגדרות שאינו נכנס ל־Git, או מנגנון סודות מתאים לסביבת ההרצה שלכם.
טיפול נכון בשגיאות: סטטוס, JSON לא תקין וזמן המתנה
קוד שעובד רק כשהכול מושלם הוא קוד שיישבר מהר. צריך לחשוב מראש על שלושה מצבים נפוצים:
- השרת החזיר קוד שגיאה, למשל
404. - התשובה אינה JSON למרות שציפינו ל־JSON.
- הבקשה נתקעה או נכשלה ברשת.
הדוגמה הבאה בונה פונקציה קטנה בשם fetch_json שמחזירה נתונים במקרה תקין, ומדפיסה הודעת שגיאה ברורה במקרה בעייתי.
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/ok":
body = json.dumps({"status": "ok"}).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
if self.path == "/bad-json":
body = b"not json"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
return
def fetch_json(url):
try:
response = requests.get(url, timeout=3)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as error:
print(f"HTTP error: {error}")
except requests.exceptions.JSONDecodeError as error:
print(f"JSON error: {error}")
except requests.exceptions.RequestException as error:
print(f"Request error: {error}")
return None
server = HTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
base_url = f"http://127.0.0.1:{server.server_port}"
print(fetch_json(f"{base_url}/ok"))
print(fetch_json(f"{base_url}/missing"))
print(fetch_json(f"{base_url}/bad-json"))
finally:
server.shutdown()הפונקציה מפרידה בין סוגי הכשל. זו לא רק אסתטיקה: כשיש תקלה בלוגים של סקריפט אוטומטי, ההבדל בין שגיאת HTTP לבין JSON שבור חוסך זמן. שימו לב שגם בסוף הפונקציה יש return None, כדי שהקוד שקורא לה ידע שאין נתונים תקינים.
פלט צפוי ייראה בערך כך, עם מספר הפורט המקומי שמשתנה בכל הרצה:
{'status': 'ok'}
HTTP error: 404 Client Error: Not Found for url: http://127.0.0.1:PORT/missing
None
JSON error: Expecting value: line 1 column 1 (char 0)
Noneאיך לבנות לקוח API קטן ולא קוד ספגטי
כשסקריפט גדל, קל למצוא את עצמכם עם עשרות קריאות requests.get מפוזרות בקובץ. עדיף לרכז את הפרטים החוזרים במקום אחד: כתובת בסיס, כותרות, זמן המתנה וטיפול בסיסי בשגיאות.
אפשר להתחיל פשוט:
- שמרו את כתובת הבסיס במשתנה אחד, למשל
base_url. - הגדירו
timeoutבכל בקשה. אל תשאירו את ברירת המחדל ללא הגבלה. - השתמשו ב־
requests.Sessionאם יש הרבה בקשות לאותו שירות. - אל תבלעו חריגות בשקט. הדפיסו הודעה או החזירו ערך שמסמן כשל.
- הפרידו בין “שליפת נתונים” לבין “עיבוד נתונים”. זה מקל על בדיקות ותיקון תקלות.
Session שימושי במיוחד כשיש כותרות קבועות או cookies, אבל אין חובה להשתמש בו בסקריפט קצר. אל תסבכו מוקדם מדי; פשוט הימנעו מכפילויות שמזמינות טעויות.
טעויות נפוצות והודעות השגיאה שתראו בפועל
ModuleNotFoundError: No module named 'requests'
המשמעות: פייתון שרץ כרגע לא מוצא את החבילה requests. בדרך כלל התקנתם אותה בסביבה אחרת, או לא הפעלתם את ה־virtual environment.
מה עושים:
- הפעילו את הסביבה הווירטואלית של הפרויקט.
- הריצו
python -m pip install requestsמתוך אותה סביבה. - בדקו ש־
pythonו־pipשייכים לאותה התקנה.
requests.exceptions.MissingSchema: Invalid URL 'example.com/users': No scheme supplied. Perhaps you meant https://example.com/users?
המשמעות: כתבתם כתובת בלי http:// או https://. requests לא מנחש לבד באיזה פרוטוקול להשתמש.
מה עושים: כתבו כתובת מלאה, למשל https://example.com/users בסביבה אמיתית, או http://127.0.0.1:8000/users בהרצה מקומית.
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
המשמעות: קראתם ל־response.json(), אבל גוף התשובה אינו JSON תקין. זה יכול להיות דף HTML של שגיאה, מחרוזת ריקה, או טקסט רגיל.
מה עושים:
- בדקו קודם את
response.status_code. - קראו ל־
response.raise_for_status()לפני פענוח JSON. - בזמן דיבוג הדפיסו בזהירות את
response.text, בלי לחשוף מידע רגיש בלוגים.
KeyError: 'access_token'
המשמעות: הקוד מניח שקיים שדה בשם access_token בתשובת JSON, אבל הוא לא שם. אולי האימות נכשל, אולי שם השדה שונה, ואולי קיבלתם תשובת שגיאה במבנה אחר.
מה עושים: אל תיגשו מיד עם סוגריים מרובעים אם השדה לא מובטח. בזמן פיתוח בדקו את כל התשובה, ובקוד יציב השתמשו ב־data.get("access_token") ואז טפלו במקרה שבו הערך חסר.
requests.exceptions.ConnectTimeout
המשמעות: לא התקבל חיבור בזמן שהוגדר. זה יכול לקרות בגלל כתובת לא נכונה, שירות לא זמין, חסימת רשת או עומס זמני.
מה עושים: ודאו שהכתובת נכונה, שהשירות זמין, ושיש timeout סביר. בסקריפטים אוטומטיים אפשר לשקול ניסיון חוזר, אבל בזהירות: לא להריץ לולאה אינסופית ולא להציף שירות.
אימות, סודות וקבצי קוד: מה לא לעשות
הכלל הפשוט: לא מכניסים סודות לקוד. לא טוקן, לא סיסמה, לא מפתח API, גם לא “רק לבדיקה”. ברגע שזה נכנס ל־Git, קשה לדעת לאן זה יגיע בהמשך.
במקום זה:
- קראו סודות ממשתני סביבה עם
os.environאוos.environ.get. - אל תדפיסו כותרות אימות ללוגים.
- אל תשמרו תשובות מלאות אם הן כוללות מידע אישי או מזהים רגישים.
- תנו להרשאות להיות מינימליות: טוקן לקריאה לא צריך הרשאת מחיקה.
אם אתם עובדים עם Git, הוסיפו קבצים מקומיים שמכילים הגדרות רגישות ל־.gitignore. אם סוד כבר נכנס להיסטוריה, מחיקה מהקובץ הנוכחי אינה מספיקה; צריך לבטל את המפתח בצד השירות ולהפיק חדש.
בדיקות ידניות עם שורת הפקודה לפני כתיבת קוד
לפני שמאשימים את הקוד, כדאי לבדוק אם השירות מגיב בכלל. בכלים כמו curl אפשר לשלוח בקשה פשוטה מהטרמינל. במערכות Linux/macOS זה בדרך כלל זמין, וב־Windows אפשר להשתמש גם ב־PowerShell.
הרעיון אינו להחליף את פייתון, אלא לצמצם את מקור הבעיה: אם גם בקשה ידנית נכשלת, ייתכן שהכתובת, ההרשאות או השירות עצמו הם הבעיה. אם הבקשה הידנית מצליחה והקוד נכשל, בדקו headers, גוף בקשה, קידוד ו־timeout.
סיכום: תבנית עבודה יציבה עם API ו־JSON בפייתון
עבודה טובה עם API בפייתון נשענת על כמה הרגלים קטנים: להשתמש ב־requests בצורה ברורה, להגדיר timeout, לבדוק סטטוס עם raise_for_status(), לפענח JSON רק אחרי שהבקשה הצליחה, ולא לשים סודות בקוד.
כדאי להתחיל מדוגמה קטנה שעובדת, להוסיף אימות, ואז להוסיף טיפול בשגיאות. זה עדיף בהרבה על כתיבת סקריפט ארוך ורק בסוף לגלות שלא ברור איפה הוא נופל. מי שמחזק במקביל את יסודות פייתון יגלה שהחיבור בין אוטומציה, JSON ושורת הפקודה הופך מהר מאוד לכלי עבודה יומיומי.


