Regular Expressions
1. Giới thiệu
Phần tiêu đề “1. Giới thiệu”Regular Expression (biểu thức chính quy), thường gọi tắt là Regex, là một công cụ mạnh mẽ để tìm kiếm, so khớp và xử lý chuỗi theo các mẫu (pattern).
Regex giúp bạn:
- Tìm kiếm văn bản theo mẫu phức tạp
- Validate format (email, số điện thoại, URL,…)
- Thay thế văn bản có điều kiện
- Tách chuỗi theo pattern
- Trích xuất thông tin từ văn bản
2. Module re
Phần tiêu đề “2. Module re”Python cung cấp module re để làm việc với regex:
import re3. Các hàm cơ bản
Phần tiêu đề “3. Các hàm cơ bản”3.1 - re.search() - Tìm kiếm
Phần tiêu đề “3.1 - re.search() - Tìm kiếm”Tìm lần xuất hiện đầu tiên của pattern:
import re
text = "My phone is 0123-456-789"result = re.search(r"\d+", text)
if result: print(result.group()) # 0123 print(result.start()) # 13 (vị trí bắt đầu) print(result.end()) # 17 (vị trí kết thúc)3.2 - re.match() - So khớp từ đầu
Phần tiêu đề “3.2 - re.match() - So khớp từ đầu”Chỉ so khớp ở đầu chuỗi:
import re
text = "Python is great"
# Match thành côngresult = re.match(r"Python", text)print(result.group() if result else "No match") # Python
# Match thất bại (không ở đầu)result = re.match(r"is", text)print(result.group() if result else "No match") # No match3.3 - re.findall() - Tìm tất cả
Phần tiêu đề “3.3 - re.findall() - Tìm tất cả”Trả về list tất cả kết quả khớp:
import re
text = "My numbers: 123, 456, 789"numbers = re.findall(r"\d+", text)print(numbers) # ['123', '456', '789']
# Tìm tất cả từtext = "Hello World Python"words = re.findall(r"\w+", text)print(words) # ['Hello', 'World', 'Python']3.4 - re.finditer() - Iterator
Phần tiêu đề “3.4 - re.finditer() - Iterator”Trả về iterator của Match objects:
import re
pattern = r"\w+@\w+\.\w+"
for match in re.finditer(pattern, text): print(f"Found: {match.group()} at {match.start()}-{match.end()}")3.5 - re.sub() - Thay thế
Phần tiêu đề “3.5 - re.sub() - Thay thế”Thay thế chuỗi khớp với pattern:
import re
text = "My phone is 0123-456-789"# Thay thế số bằng 'X'result = re.sub(r"\d", "X", text)print(result) # My phone is XXXX-XXX-XXX
# Thay thế với limitresult = re.sub(r"\d", "X", text, count=4)print(result) # My phone is XXXX-456-7893.6 - re.split() - Tách chuỗi
Phần tiêu đề “3.6 - re.split() - Tách chuỗi”Tách chuỗi theo pattern:
import re
text = "apple,banana;orange:grape"fruits = re.split(r"[,;:]", text)print(fruits) # ['apple', 'banana', 'orange', 'grape']
# Tách theo khoảng trắngtext = "Hello World Python"words = re.split(r"\s+", text)print(words) # ['Hello', 'World', 'Python']4. Pattern Syntax (Cú pháp)
Phần tiêu đề “4. Pattern Syntax (Cú pháp)”4.1 - Ký tự đặc biệt
Phần tiêu đề “4.1 - Ký tự đặc biệt”| Pattern | Ý nghĩa | Ví dụ |
|---|---|---|
. |
Bất kỳ ký tự nào | a.c → “abc”, “a1c” |
^ |
Đầu chuỗi | ^Hello → “Hello world” |
$ |
Cuối chuỗi | world$ → “Hello world” |
* |
0 hoặc nhiều lần | ab*c → “ac”, “abc”, “abbc” |
+ |
1 hoặc nhiều lần | ab+c → “abc”, “abbc” |
? |
0 hoặc 1 lần | ab?c → “ac”, “abc” |
| |
Hoặc (OR) | cat|dog → “cat” hoặc “dog” |
4.2 - Character Classes
Phần tiêu đề “4.2 - Character Classes”| Pattern | Ý nghĩa | Ví dụ |
|---|---|---|
[abc] |
a, b, hoặc c | [aeiou] → nguyên âm |
[^abc] |
Không phải a, b, c | [^0-9] → không phải số |
[a-z] |
Từ a đến z | [a-zA-Z] → chữ cái |
[0-9] |
Từ 0 đến 9 | [0-9]+ → số |
4.3 - Predefined Character Classes
Phần tiêu đề “4.3 - Predefined Character Classes”| Pattern | Ý nghĩa | Tương đương |
|---|---|---|
\d |
Chữ số | [0-9] |
\D |
Không phải chữ số | [^0-9] |
\w |
Chữ/số/underscore | [a-zA-Z0-9_] |
\W |
Không phải \w | [^a-zA-Z0-9_] |
\s |
Khoảng trắng | [ \t\n\r\f\v] |
\S |
Không phải khoảng trắng | [^ \t\n\r\f\v] |
4.4 - Quantifiers (Bộ lượng từ)
Phần tiêu đề “4.4 - Quantifiers (Bộ lượng từ)”| Pattern | Ý nghĩa | Ví dụ |
|---|---|---|
{n} |
Chính xác n lần | \d{3} → 3 chữ số |
{n,} |
Ít nhất n lần | \d{2,} → 2+ chữ số |
{n,m} |
Từ n đến m lần | \d{2,4} → 2-4 chữ số |
* |
0 hoặc nhiều | \d* |
+ |
1 hoặc nhiều | \d+ |
? |
0 hoặc 1 | \d? |
5. Ví dụ Pattern cơ bản
Phần tiêu đề “5. Ví dụ Pattern cơ bản”5.1 - Tìm số
Phần tiêu đề “5.1 - Tìm số”import re
text = "I have 3 apples and 5 oranges"numbers = re.findall(r"\d+", text)print(numbers) # ['3', '5']5.2 - Tìm từ
Phần tiêu đề “5.2 - Tìm từ”import re
text = "Hello World! Python123 is_great"words = re.findall(r"\w+", text)print(words) # ['Hello', 'World', 'Python123', 'is_great']
# Chỉ chữ cáiletters_only = re.findall(r"[a-zA-Z]+", text)print(letters_only) # ['Hello', 'World', 'Python', 'is', 'great']5.3 - Tìm email
Phần tiêu đề “5.3 - Tìm email”import re
emails = re.findall(r"\w+@\w+\.\w+", text)
# Pattern tốt hơnemails = re.findall(r"[\w.-]+@[\w.-]+\.\w+", text)5.4 - Tìm URL
Phần tiêu đề “5.4 - Tìm URL”import re
text = "Visit https://example.com or http://test.org"urls = re.findall(r"https?://[\w.-]+", text)print(urls) # ['https://example.com', 'http://test.org']5.5 - Tìm số điện thoại
Phần tiêu đề “5.5 - Tìm số điện thoại”import re
text = "Call me at 0123-456-789 or (012) 345-6789"phones = re.findall(r"\d{3,4}[-\s]?\d{3}[-\s]?\d{3,4}", text)print(phones) # ['0123-456-789']
# Pattern phức tạp hơnphones = re.findall(r"[\d\(\)\s-]+", text)print(phones) # ['0123-456-789', '(012) 345-6789']6. Groups và Capturing
Phần tiêu đề “6. Groups và Capturing”6.1 - Basic Groups
Phần tiêu đề “6.1 - Basic Groups”import re
match = re.search(r"(\w+)@(\w+\.\w+)", text)
if match: print(match.group(1)) # alice (group 1) print(match.group(2)) # example.com (group 2)6.2 - Named Groups
Phần tiêu đề “6.2 - Named Groups”import re
text = "Date: 2024-12-25"pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"match = re.search(pattern, text)
if match: print(match.group('year')) # 2024 print(match.group('month')) # 12 print(match.group('day')) # 25 print(match.groupdict()) # {'year': '2024', 'month': '12', 'day': '25'}6.3 - Non-capturing Groups
Phần tiêu đề “6.3 - Non-capturing Groups”import re
# (?:...) không capturetext = "Price: $100"match = re.search(r"(?:\$)(\d+)", text)
if match: print(match.group(1)) # 100 # Không có group cho $6.4 - Ví dụ: Parse log
Phần tiêu đề “6.4 - Ví dụ: Parse log”import re
log = "[2024-12-10 14:30:45] ERROR: Connection timeout"pattern = r"\[(?P<date>\d{4}-\d{2}-\d{2}) (?P<time>\d{2}:\d{2}:\d{2})\] (?P<level>\w+): (?P<message>.*)"
match = re.search(pattern, log)if match: print(f"Date: {match.group('date')}") print(f"Time: {match.group('time')}") print(f"Level: {match.group('level')}") print(f"Message: {match.group('message')}")7. Flags (Cờ)
Phần tiêu đề “7. Flags (Cờ)”7.1 - re.IGNORECASE (re.I)
Phần tiêu đề “7.1 - re.IGNORECASE (re.I)”import re
text = "Hello World"# Không phân biệt hoa thườngresult = re.findall(r"hello", text, re.IGNORECASE)print(result) # ['Hello']7.2 - re.MULTILINE (re.M)
Phần tiêu đề “7.2 - re.MULTILINE (re.M)”import re
text = """Line 1: StartLine 2: MiddleLine 3: End"""
# ^ khớp đầu mỗi dòngresults = re.findall(r"^Line \d+", text, re.MULTILINE)print(results) # ['Line 1', 'Line 2', 'Line 3']7.3 - re.DOTALL (re.S)
Phần tiêu đề “7.3 - re.DOTALL (re.S)”import re
text = "Hello\nWorld"
# . không khớp \nresult = re.search(r"Hello.World", text)print(result) # None
# . khớp tất cả kể cả \nresult = re.search(r"Hello.World", text, re.DOTALL)print(result.group()) # Hello\nWorld7.4 - re.VERBOSE (re.X)
Phần tiêu đề “7.4 - re.VERBOSE (re.X)”import re
# Pattern dễ đọc hơnpattern = r""" (\d{3}) # Mã vùng [-\s]? # Dấu gạch ngang hoặc khoảng trắng (tùy chọn) (\d{3}) # 3 số tiếp theo [-\s]? # Dấu gạch ngang hoặc khoảng trắng (tùy chọn) (\d{4}) # 4 số cuối"""
text = "Phone: 012-345-6789"match = re.search(pattern, text, re.VERBOSE)if match: print(match.group(0)) # 012-345-67897.5 - Kết hợp nhiều flags
Phần tiêu đề “7.5 - Kết hợp nhiều flags”import re
text = "Hello\nWorld"result = re.search(r"^hello.world$", text, re.IGNORECASE | re.DOTALL | re.MULTILINE)print(result.group() if result else "No match")8. Ví dụ thực tế
Phần tiêu đề “8. Ví dụ thực tế”Ví dụ 1: Validate email
Phần tiêu đề “Ví dụ 1: Validate email”import re
def is_valid_email(email): pattern = r"^[\w.-]+@[\w.-]+\.\w+$" return re.match(pattern, email) is not None
print(is_valid_email("invalid.email")) # Falseprint(is_valid_email("@example.com")) # FalseVí dụ 2: Validate số điện thoại
Phần tiêu đề “Ví dụ 2: Validate số điện thoại”import re
def is_valid_phone(phone): # Format: 0XXX-XXX-XXX hoặc 0XXX XXX XXX pattern = r"^0\d{3}[-\s]?\d{3}[-\s]?\d{3}$" return re.match(pattern, phone) is not None
print(is_valid_phone("0123-456-789")) # Trueprint(is_valid_phone("0123 456 789")) # Trueprint(is_valid_phone("0123456789")) # Trueprint(is_valid_phone("123-456-789")) # FalseVí dụ 3: Trích xuất hashtags
Phần tiêu đề “Ví dụ 3: Trích xuất hashtags”import re
text = "Love #Python and #Programming! #AI is amazing #MachineLearning"hashtags = re.findall(r"#\w+", text)print(hashtags) # ['#Python', '#Programming', '#AI', '#MachineLearning']
# Chỉ lấy text (không có #)hashtags = re.findall(r"#(\w+)", text)print(hashtags) # ['Python', 'Programming', 'AI', 'MachineLearning']Ví dụ 4: Remove HTML tags
Phần tiêu đề “Ví dụ 4: Remove HTML tags”import re
html = "<p>Hello <b>World</b>!</p>"clean = re.sub(r"<[^>]+>", "", html)print(clean) # Hello World!Ví dụ 5: Extract prices
Phần tiêu đề “Ví dụ 5: Extract prices”import re
text = "Items: $10.99, $25, $100.50, and $5"prices = re.findall(r"\$(\d+\.?\d*)", text)print(prices) # ['10.99', '25', '100.50', '5']
# Convert to floatprices = [float(p) for p in prices]print(f"Total: ${sum(prices):.2f}") # Total: $141.49Ví dụ 6: Parse CSV line
Phần tiêu đề “Ví dụ 6: Parse CSV line”import re
csv_line = 'Alice,25,"New York, NY",Engineer'
# Split nhưng bỏ qua dấu phẩy trong quotespattern = r',(?=(?:[^"]*"[^"]*")*[^"]*$)'fields = re.split(pattern, csv_line)print(fields) # ['Alice', '25', '"New York, NY"', 'Engineer']
# Clean quotesfields = [f.strip('"') for f in fields]print(fields) # ['Alice', '25', 'New York, NY', 'Engineer']Ví dụ 7: Password strength
Phần tiêu đề “Ví dụ 7: Password strength”import re
def check_password_strength(password): if len(password) < 8: return "Quá ngắn (cần ít nhất 8 ký tự)"
checks = { "chữ thường": r"[a-z]", "chữ hoa": r"[A-Z]", "số": r"\d", "ký tự đặc biệt": r"[!@#$%^&*(),.?\":{}|<>]" }
for name, pattern in checks.items(): if not re.search(pattern, password): return f"Thiếu {name}"
return "Mật khẩu mạnh"
print(check_password_strength("abc")) # Quá ngắnprint(check_password_strength("abcdefgh")) # Thiếu chữ hoaprint(check_password_strength("Abcdefgh")) # Thiếu sốprint(check_password_strength("Abcdefgh1")) # Thiếu ký tự đặc biệtprint(check_password_strength("Abcdefgh1!")) # Mật khẩu mạnhVí dụ 8: Extract URLs from text
Phần tiêu đề “Ví dụ 8: Extract URLs from text”import re
text = """Visit our website at https://example.comDownload from http://files.example.org/file.zipCheck https://blog.example.com/post/123"""
urls = re.findall(r"https?://[\w.-]+(?:/[\w./?=&-]*)?", text)for url in urls: print(url)Ví dụ 9: Censor bad words
Phần tiêu đề “Ví dụ 9: Censor bad words”import re
def censor_text(text, bad_words): for word in bad_words: pattern = r"\b" + re.escape(word) + r"\b" text = re.sub(pattern, "*" * len(word), text, flags=re.IGNORECASE) return text
text = "This is a badword and another BADWORD here"bad_words = ["badword"]censored = censor_text(text, bad_words)print(censored) # This is a ******* and another ******* hereVí dụ 10: Format phone number
Phần tiêu đề “Ví dụ 10: Format phone number”import re
def format_phone(phone): # Loại bỏ tất cả ký tự không phải số digits = re.sub(r"\D", "", phone)
# Format: XXXX-XXX-XXX if len(digits) == 10: return f"{digits[:4]}-{digits[4:7]}-{digits[7:]}" return phone # Giữ nguyên nếu không hợp lệ
print(format_phone("0123456789")) # 0123-456-789print(format_phone("012-345-6789")) # 0123-456-789print(format_phone("(012) 345-6789")) # 0123-456-7899. Compile Pattern
Phần tiêu đề “9. Compile Pattern”9.1 - Tại sao compile?
Phần tiêu đề “9.1 - Tại sao compile?”import re
# Không compile - phải parse pattern mỗi lầnfor text in texts: result = re.search(r"\d+", text)
# Compile - parse 1 lần, dùng nhiều lần (nhanh hơn)pattern = re.compile(r"\d+")for text in texts: result = pattern.search(text)9.2 - Ví dụ compile
Phần tiêu đề “9.2 - Ví dụ compile”import re
# Compile patternemail_pattern = re.compile(r"[\w.-]+@[\w.-]+\.\w+")
texts = [ "No email here"]
for text in texts: match = email_pattern.search(text) if match: print(f"Found: {match.group()}")9.3 - Compile với flags
Phần tiêu đề “9.3 - Compile với flags”import re
pattern = re.compile(r"hello", re.IGNORECASE)result = pattern.search("HELLO World")print(result.group()) # HELLO10. Lookahead và Lookbehind
Phần tiêu đề “10. Lookahead và Lookbehind”10.1 - Positive Lookahead (?=…)
Phần tiêu đề “10.1 - Positive Lookahead (?=…)”import re
# Tìm số theo sau bởi "USD"text = "Price: 100 USD, 200 EUR, 300 USD"prices = re.findall(r"\d+(?= USD)", text)print(prices) # ['100', '300']10.2 - Negative Lookahead (?!…)
Phần tiêu đề “10.2 - Negative Lookahead (?!…)”import re
# Tìm số KHÔNG theo sau bởi "USD"text = "Price: 100 USD, 200 EUR, 300 USD"prices = re.findall(r"\d+(?! USD)", text)print(prices) # ['200']10.3 - Positive Lookbehind (?<=…)
Phần tiêu đề “10.3 - Positive Lookbehind (?<=…)”import re
# Tìm số đứng sau "$"text = "Prices: $100, €200, $300"prices = re.findall(r"(?<=\$)\d+", text)print(prices) # ['100', '300']10.4 - Negative Lookbehind (?<!…)
Phần tiêu đề “10.4 - Negative Lookbehind (?<!…)”import re
# Tìm số KHÔNG đứng sau "$"text = "Prices: $100, 200, $300"prices = re.findall(r"(?<!\$)\d+", text)print(prices) # ['200']11. Best Practices
Phần tiêu đề “11. Best Practices”1. Dùng raw string
Phần tiêu đề “1. Dùng raw string”# TỐT - Raw stringpattern = r"\d+\.\d+"
# TRÁNH - Phải escape nhiều lầnpattern = "\\d+\\.\\d+"2. Compile pattern khi dùng nhiều lần
Phần tiêu đề “2. Compile pattern khi dùng nhiều lần”# TỐTpattern = re.compile(r"\d+")for text in texts: result = pattern.search(text)
# TRÁNH (chậm hơn)for text in texts: result = re.search(r"\d+", text)3. Dùng named groups
Phần tiêu đề “3. Dùng named groups”# TỐT - Dễ đọcpattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
# TRÁNHpattern = r"(\d{4})-(\d{2})-(\d{2})"4. Không lạm dụng regex
Phần tiêu đề “4. Không lạm dụng regex”# TỐT - Đơn giảnif "@" in email and "." in email: pass
# TRÁNH - Phức tạp không cần thiếtif re.search(r"@.*\.", email): pass5. Test regex kỹ
Phần tiêu đề “5. Test regex kỹ”# Test với nhiều trường hợptest_cases = [ "invalid.email", "@invalid.com"]
pattern = r"[\w.-]+@[\w.-]+\.\w+"for email in test_cases: result = re.match(pattern, email) print(f"{email}: {bool(result)}")12. Tools hữu ích
Phần tiêu đề “12. Tools hữu ích”Các website để test regex:
- regex101.com
- regexr.com
- pythex.org
Bài giảng trên YouTube
Phần tiêu đề “Bài giảng trên YouTube”Cập nhật sau