דילוג לתוכן
ביטויים רגולריים

ביטויים רגולריים פייתון: כך משתמשים נכון ב־re.search, re.findall והחלפות

ביטויים רגולריים פייתון הם אחד הכלים הכי שימושיים כשצריך למצוא טקסט לפי תבנית: מספר טלפון, כתובת אימייל, מזהה הזמנה, שורות לוג, תגים בתוך קובץ, או כל טקסט שחוזר על עצמו במבנה צפוי. לא צריך ספרייה חיצונית ולא צריך התקנה מיוחדת: המודול 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() בלי לבדוק אם באמת נמצאה התאמה.

python
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) מחזיר רק את המספר. הפלט הצפוי הוא:

text
Full match: Order ID: 48321
Order number: 48321

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

קבוצות לכידה: לשלוף רק את החלק שמעניין

Regex שימושי באמת מתחיל כשמשתמשים בקבוצות. במקום להסתפק בשאלה “האם יש התאמה?”, אפשר לשאול “איזה חלק בדיוק מתאים?”. קבוצות נכתבות בסוגריים רגילים. לדוגמה, בתבנית r"User: (\w+)", החלק (\w+) הוא קבוצה.

אפשר להשתמש ביותר מקבוצה אחת:

python
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)

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

text
Username: daniel
Action: login
Code: 200

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

מציאת כל ההתאמות עם re.findall

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

python
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 פירושה “כל תו”. הפלט הצפוי:

text
PDF files:
report_2024.pdf
invoice_2025.pdf
summary_2026.pdf

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

החלפות עם re.sub

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

python
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 מתייחסת לקבוצה השנייה, כלומר לדומיין. הפלט הצפוי:

text
Contact: ***@example.com, backup: ***@example.org

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

ניקוי טקסט צעד אחר צעד

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

python
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)

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

text
Hello, Python! Regex

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

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

לפונקציות של re אפשר להעביר דגלים שמשנים את אופן החיפוש. הדגל re.IGNORECASE גורם לחיפוש להתעלם מהבדל בין אותיות גדולות וקטנות באנגלית. הדגל re.MULTILINE משנה את ההתנהגות של ^ ו־$, כך שיתייחסו גם להתחלה ולסוף של כל שורה, לא רק לכל המחרוזת.

python
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. בזכות הדגלים, החיפוש עובד גם כשיש אותיות גדולות וגם על כמה שורות. הפלט הצפוי:

text
['INFO', 'warning', 'ERROR', 'info']

דגלים הם פתרון נקי יותר מאשר להכניס לתבנית עצמה כל וריאציה אפשרית של אותיות. במקום לכתוב תבנית מסורבלת, נותנים למנוע ה־regex לעשות את העבודה.

טעויות נפוצות והודעות שגיאה אמיתיות

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

קריאה ל־group() בלי לבדוק אם יש התאמה

קוד כזה עלול להיכשל:

python
import re

text = "No order here"
match = re.search(r"Order ID: (\d+)", text)

print(match.group(1))

הודעת השגיאה תהיה:

output
AttributeError: 'NoneType' object has no attribute 'group'

הסיבה: re.search החזירה None. הפתרון הוא לבדוק:

python
import re

text = "No order here"
match = re.search(r"Order ID: (\d+)", text)

if match:
    print(match.group(1))
else:
    print("No match")

סוגריים לא סגורים בתבנית

אם כותבים תבנית עם סוגר פותח בלי סוגר סוגר:

python
import re

re.search(r"(\d+", "123")

תופיע שגיאה בסגנון:

text
re.error: missing ), unterminated subpattern at position 0

הפתרון הוא להשלים את הסוגריים:

python
import re

match = re.search(r"(\d+)", "123")
print(match.group(1))

העברת מספר במקום מחרוזת

הפונקציות של re מצפות למחרוזת או bytes. אם מעבירים מספר:

python
import re

value = 12345
print(re.findall(r"\d", value))

תופיע השגיאה:

output
TypeError: expected string or bytes-like object, got 'int'

הפתרון הוא להמיר למחרוזת:

python
import re

value = 12345
print(re.findall(r"\d", str(value)))

איך לבנות תבנית בלי ללכת לאיבוד

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

  1. כתבו דוגמת טקסט קטנה שמייצגת את הבעיה.
  2. מצאו קודם חלק פשוט, למשל רק מספרים עם \d+.
  3. הוסיפו גבולות, כמו מילה לפני המספר או סיומת אחרי המספר.
  4. הוסיפו קבוצות רק כאשר צריך לשלוף חלקים פנימיים.
  5. בדקו גם מקרה שלא אמור להתאים, כדי לוודא שהתבנית לא רחבה מדי.

למשל, אם צריך למצוא קודי מוצר כמו 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 הזה ולא אחר.