ביטויים רגולריים פייתון הם אחד הכלים הכי שימושיים כשצריך למצוא טקסט לפי תבנית: מספר טלפון, כתובת אימייל, מזהה הזמנה, שורות לוג, תגים בתוך קובץ, או כל טקסט שחוזר על עצמו במבנה צפוי. לא צריך ספרייה חיצונית ולא צריך התקנה מיוחדת: המודול re מגיע עם Python 3 כחלק מהספרייה הסטנדרטית.
במאמר הזה נכתוב קוד אמיתי, נריץ דוגמאות קטנות ובטוחות, ונראה מתי להשתמש ב־re.search, מתי ב־re.findall, ואיך מבצעים החלפות עם re.sub. אם אתם עדיין בשלבים הראשונים של פייתון, כדאי לשמור בצד גם את מדריך צעדים ראשונים ואת עמוד פייתון, אבל אפשר בהחלט להתקדם כאן גם עם ידע בסיסי בלבד.
מה צריך להכין מראש
כדי לעבוד עם הדוגמאות במאמר צריך סביבת Python 3 פעילה. אין תלות בחבילות חיצוניות, אין התקנה דרך pip, ואין צורך בקבצים מיוחדים. אפשר להריץ את הקוד מתוך קובץ כמו regex_demo.py, מתוך עורך קוד, או מתוך טרמינל.
- Linux/macOS: הרצה רגילה תיראה כך ב־bash:
python3 regex_demo.py - Windows: ב־PowerShell אפשר להריץ:
py regex_demo.py - מודול דרוש: רק
re, שמגיע עם Python.
טיפ חשוב לפני שמתחילים: כמעט תמיד כדאי לכתוב תבניות regex כמחרוזות גולמיות, כלומר עם האות r לפני המחרוזת: r"\d+". זה מונע בלבול בין תווי בריחה של Python לבין תווי בריחה של regex.
ביטויים רגולריים פייתון: הרעיון הבסיסי בלי להסתבך
ביטוי רגולרי הוא תבנית שמתארת טקסט. לדוגמה, \d פירושו ספרה, \w פירושו תו “מילולי” כמו אות, ספרה או קו תחתון, ו־+ פירושו “אחד או יותר”. לכן התבנית \d+ מוצאת רצף של ספרות.
הנה כמה סימנים שכדאי להכיר לפני הקוד:
| תבנית | משמעות | דוגמה שתתאים |
|---|---|---|
\d |
ספרה אחת | 7 |
\d+ |
ספרה אחת או יותר | 2026 |
. |
כמעט כל תו בודד | a, 9 |
\s |
רווח, טאב או ירידת שורה | רווח רגיל |
[A-Z] |
אות גדולה באנגלית | B |
^ |
תחילת מחרוזת | תלוי במיקום |
$ |
סוף מחרוזת | תלוי במיקום |
חשוב לא לנסות לזכור הכול ביום אחד. הדרך הנכונה ללמוד regex היא לקחת בעיה קטנה, לכתוב תבנית פשוטה, לבדוק אותה על טקסט אמיתי, ואז לשפר בהדרגה.
חיפוש ראשון עם re.search
הפונקציה re.search מחפשת התאמה כלשהי בתוך המחרוזת. אם נמצאה התאמה, היא מחזירה אובייקט התאמה. אם לא נמצאה התאמה, היא מחזירה None. זו נקודה קריטית, כי הרבה שגיאות מתחילות מקריאה ל־group() בלי לבדוק אם באמת נמצאה התאמה.
import re
text = "Order ID: 48321, status: shipped"
match = re.search(r"Order ID: (\d+)", text)
if match:
print("Full match:", match.group(0))
print("Order number:", match.group(1))
else:
print("No order ID found")הקוד מחפש טקסט שמתחיל ב־Order ID: ואחריו רצף ספרות. הסוגריים סביב \d+ יוצרים קבוצת לכידה, ולכן match.group(1) מחזיר רק את המספר. הפלט הצפוי הוא:
Full match: Order ID: 48321
Order number: 48321מתי משתמשים ב־re.search? כאשר מעניין אתכם לדעת אם משהו קיים בטקסט, או כשצריך לשלוף את ההופעה הראשונה בלבד. למשל: האם שורת לוג מכילה קוד שגיאה, האם הודעה כוללת מזהה, או האם קלט של משתמש כולל פורמט מסוים.
קבוצות לכידה: לשלוף רק את החלק שמעניין
Regex שימושי באמת מתחיל כשמשתמשים בקבוצות. במקום להסתפק בשאלה “האם יש התאמה?”, אפשר לשאול “איזה חלק בדיוק מתאים?”. קבוצות נכתבות בסוגריים רגילים. לדוגמה, בתבנית r"User: (\w+)", החלק (\w+) הוא קבוצה.
אפשר להשתמש ביותר מקבוצה אחת:
import re
line = "user=daniel action=login code=200"
match = re.search(r"user=(\w+)\s+action=(\w+)\s+code=(\d+)", line)
if match:
username = match.group(1)
action = match.group(2)
code = match.group(3)
print("Username:", username)
print("Action:", action)
print("Code:", code)הקוד מנתח שורה פשוטה ומוציא ממנה שלושה ערכים: שם משתמש, פעולה וקוד. הפלט הצפוי:
Username: daniel
Action: login
Code: 200שימו לב שהתבנית כאן קשיחה יחסית: היא מניחה סדר מסוים ומרווחים מסוימים. זה מצוין לדוגמה לימודית, אבל בטקסטים לא אחידים צריך לפעמים לבנות תבנית גמישה יותר או לפרק את הבעיה לכמה חיפושים קטנים.
מציאת כל ההתאמות עם re.findall
כאשר רוצים למצוא את כל ההופעות בטקסט, re.findall היא בחירה טבעית. היא מחזירה רשימה. אם אין קבוצות לכידה בתבנית, הרשימה תכיל את הטקסטים המלאים שנמצאו. אם יש קבוצות, ההתנהגות משתנה: היא תחזיר את תוכן הקבוצות.
import re
text = """
Files:
report_2024.pdf
notes.txt
invoice_2025.pdf
photo.png
summary_2026.pdf
"""
pdf_files = re.findall(r"\b\w+_\d{4}\.pdf\b", text)
print("PDF files:")
for filename in pdf_files:
print(filename)כאן אנחנו מחפשים שמות קבצים שמורכבים ממילה, קו תחתון, ארבע ספרות, והסיומת .pdf. הנקודה לפני pdf נכתבת כ־\., כי נקודה רגילה ב־regex פירושה “כל תו”. הפלט הצפוי:
PDF files:
report_2024.pdf
invoice_2025.pdf
summary_2026.pdfההבדל בין re.search לבין re.findall פשוט: הראשון עוצר בהתאמה הראשונה, השני אוסף את כולן. אם אתם צריכים גם מידע על המיקום של כל התאמה, כדאי להכיר את re.finditer, שמחזירה איטרטור של אובייקטי התאמה.
החלפות עם re.sub
חיפוש הוא רק חצי מהסיפור. לעיתים קרובות נרצה גם לנקות טקסט: להחליף רצפי רווחים ברווח אחד, למסך חלק ממזהה, לתקן פורמט, או להסיר תווים לא רצויים. בשביל זה משתמשים ב־re.sub.
import re
text = "Contact: [email protected], backup: [email protected]"
masked = re.sub(
r"([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,})",
r"***@\2",
text
)
print(masked)הקוד מוצא כתובות אימייל בסיסיות ומחליף את החלק שלפני @ בכוכביות. בקבוצת ההחלפה \2 מתייחסת לקבוצה השנייה, כלומר לדומיין. הפלט הצפוי:
Contact: ***@example.com, backup: ***@example.orgזו דוגמה שימושית למסוך מידע לפני הדפסה למסך או כתיבה ללוג. היא לא מתיימרת להיות ולידציה מלאה לכל כתובת אימייל אפשרית, אלא פתרון נקודתי לטקסט פשוט. זה כלל חשוב: regex טוב הוא כזה שמתאים לבעיה שלכם, לא בהכרח כזה שמנסה לתאר את כל העולם.
ניקוי טקסט צעד אחר צעד
נניח שקיבלתם טקסט עם רווחים מיותרים, מספרים בפורמט לא אחיד, וסימני פיסוק שחוזרים על עצמם. במקום לכתוב תבנית ענקית אחת, עדיף לבצע כמה פעולות קטנות וברורות. כך קל יותר לבדוק, להבין ולתקן.
import re
text = " Hello,,, Python!!! Regex "
clean = text.strip()
clean = re.sub(r",{2,}", ",", clean)
clean = re.sub(r"!{2,}", "!", clean)
clean = re.sub(r"\s+", " ", clean)
print(clean)הקוד מבצע ארבע פעולות: מסיר רווחים מהקצוות, מקצר רצף פסיקים לפסיק אחד, מקצר רצף סימני קריאה לסימן אחד, ואז מחליף כל רצף רווחים ברווח יחיד. הפלט הצפוי:
Hello, Python! Regexזו גישה מומלצת במיוחד למתחילים: כל שורה עושה דבר אחד. אם משהו משתבש, אפשר להדפיס את הטקסט אחרי כל שלב ולראות איפה הבעיה. למי שמחפש בסיס רחב יותר בשפה עצמה, כדאי להשלים גם תרגול מתוך מדריכי פייתון.
דגלים שימושיים: התעלמות מאותיות גדולות וקטנות, ושורות מרובות
לפונקציות של re אפשר להעביר דגלים שמשנים את אופן החיפוש. הדגל re.IGNORECASE גורם לחיפוש להתעלם מהבדל בין אותיות גדולות וקטנות באנגלית. הדגל re.MULTILINE משנה את ההתנהגות של ^ ו־$, כך שיתייחסו גם להתחלה ולסוף של כל שורה, לא רק לכל המחרוזת.
import re
log = """INFO started
warning low disk
ERROR failed to save
info finished"""
matches = re.findall(r"^(info|warning|error)\b", log, flags=re.IGNORECASE | re.MULTILINE)
print(matches)התבנית מחפשת שורות שמתחילות באחת משלוש מילים: info, warning או error. בזכות הדגלים, החיפוש עובד גם כשיש אותיות גדולות וגם על כמה שורות. הפלט הצפוי:
['INFO', 'warning', 'ERROR', 'info']דגלים הם פתרון נקי יותר מאשר להכניס לתבנית עצמה כל וריאציה אפשרית של אותיות. במקום לכתוב תבנית מסורבלת, נותנים למנוע ה־regex לעשות את העבודה.
טעויות נפוצות והודעות שגיאה אמיתיות
Regex יכול להרגיש מסתורי כשמקבלים שגיאה, אבל ברוב המקרים מדובר באחת מכמה טעויות שחוזרות על עצמן.
קריאה ל־group() בלי לבדוק אם יש התאמה
קוד כזה עלול להיכשל:
import re
text = "No order here"
match = re.search(r"Order ID: (\d+)", text)
print(match.group(1))הודעת השגיאה תהיה:
AttributeError: 'NoneType' object has no attribute 'group'הסיבה: re.search החזירה None. הפתרון הוא לבדוק:
import re
text = "No order here"
match = re.search(r"Order ID: (\d+)", text)
if match:
print(match.group(1))
else:
print("No match")סוגריים לא סגורים בתבנית
אם כותבים תבנית עם סוגר פותח בלי סוגר סוגר:
import re
re.search(r"(\d+", "123")תופיע שגיאה בסגנון:
re.error: missing ), unterminated subpattern at position 0הפתרון הוא להשלים את הסוגריים:
import re
match = re.search(r"(\d+)", "123")
print(match.group(1))העברת מספר במקום מחרוזת
הפונקציות של re מצפות למחרוזת או bytes. אם מעבירים מספר:
import re
value = 12345
print(re.findall(r"\d", value))תופיע השגיאה:
TypeError: expected string or bytes-like object, got 'int'הפתרון הוא להמיר למחרוזת:
import re
value = 12345
print(re.findall(r"\d", str(value)))איך לבנות תבנית בלי ללכת לאיבוד
הטעות הכי נפוצה בלימוד ביטויים רגולריים היא להתחיל מתבנית גדולה מדי. עדיף לעבוד בשלבים:
- כתבו דוגמת טקסט קטנה שמייצגת את הבעיה.
- מצאו קודם חלק פשוט, למשל רק מספרים עם
\d+. - הוסיפו גבולות, כמו מילה לפני המספר או סיומת אחרי המספר.
- הוסיפו קבוצות רק כאשר צריך לשלוף חלקים פנימיים.
- בדקו גם מקרה שלא אמור להתאים, כדי לוודא שהתבנית לא רחבה מדי.
למשל, אם צריך למצוא קודי מוצר כמו PRD-1234, אל תתחילו מתבנית ארוכה. התחילו מ־\d{4}, הוסיפו PRD-, ואז הוסיפו גבולות עם \b אם צריך.
מתי לא להשתמש ב־regex
למרות ש־regex חזק מאוד, הוא לא הפתרון לכל דבר. אם אתם מנתחים JSON, השתמשו במודול json. אם אתם עובדים עם CSV, השתמשו במודול csv. אם צריך לפרק HTML מורכב, regex בדרך כלל לא יהיה הכלי הנכון. המטרה היא לא “להוכיח שאפשר”, אלא לכתוב קוד שאפשר להבין ולתחזק.
Regex מתאים במיוחד ל:
- חיפוש תבניות קצרות בטקסט חופשי.
- ניקוי מחרוזות.
- חילוץ מזהים, מספרים ותאריכים פשוטים.
- בדיקות ראשוניות של פורמט.
- עיבוד שורות לוג במבנה צפוי.
Regex פחות מתאים ל:
- פורמטים עם מבנה היררכי עמוק.
- ולידציה מלאה של תקנים מורכבים.
- קוד שקשה להסביר למי שיקרא אותו אחריכם.
סיכום מעשי
אם לוקחים מהמאמר רק כמה נקודות, אלו החשובות ביותר: re.search מיועדת להתאמה הראשונה, re.findall מחזירה רשימה של כל ההתאמות, ו־re.sub מבצעת החלפות לפי תבנית. כתבו תבניות כמחרוזות גולמיות עם r"...", בדקו תמיד אם search החזירה התאמה, והעדיפו כמה צעדים פשוטים על פני ביטוי אחד ענק.
ביטויים רגולריים הם מיומנות שנבנית דרך שימוש חוזר. התחילו מטקסט קטן, הוסיפו מורכבות בהדרגה, ותעדו ליד התבנית מה היא אמורה למצוא. כך גם בעוד חודש, כשתחזרו לקוד, תדעו למה כתבתם דווקא את ה־regex הזה ולא אחר.


