เชี่ยวชาญ Regular Expression ด้วยโมดูล re ของ Python! คู่มือฉบับสมบูรณ์สำหรับการค้นหาและแทนที่ข้อความ
หากต้องการเรียกใช้ Python ผ่าน Command Prompt หรือ PowerShell บนคอมพิวเตอร์ของคุณ คุณจำเป็นต้องดาวน์โหลดและติดตั้ง Python ก่อน
หากคุณยังไม่ได้ติดตั้ง กรุณาดูบทความ การติดตั้ง Python และการตั้งค่าสภาพแวดล้อมการพัฒนา เพื่อทำการติดตั้ง Python
เวลาสร้างเว็บไซต์หรือประมวลผลข้อมูล เรามักจะเจอกับสถานการณ์อย่าง "อยากดึงแค่ข้อความที่มีรูปแบบเฉพาะจากข้อความนี้!" หรือ "อยากจัดรูปแบบข้อมูลที่กระจัดกระจายให้เป็นระเบียบในครั้งเดียว!" ใช่ไหมครับ/คะ? ในเวลาแบบนี้ สิ่งที่จะมาช่วยแสดงพลังอันมหาศาลก็คือ "Regular Expression" ครับ/ค่ะ ดูเหมือนจะยากใช่ไหม? ไม่เลย! ถ้าใช้โมดูล `re` ของ Python แม้แต่มือใหม่ก็สามารถเป็นเซียนด้านการจัดการสตริงได้อย่างง่ายดายอย่างน่าทึ่ง!
ในบทความนี้ เราจะอธิบายตั้งแต่พื้นฐานไปจนถึงการประยุกต์ใช้ Regular Expression ด้วยโมดูล `re` ของ Python พร้อมตัวอย่างโค้ดที่ก็อปไปใช้ได้เลยมากมาย เพื่อให้เข้าใจง่ายที่สุด ก่อนอื่นลองรันโค้ดเพื่อสัมผัสประสบการณ์ "อ๋อ มันทำงานอย่างนี้นี่เอง!" ด้วยตัวเองดูนะครับ/คะ เอาล่ะ เรามาดำดิ่งสู่โลกของ Regular Expression ไปพร้อมกันเลย!
Regular Expression และโมดูล re คืออะไรกันแน่?
Regular Expression หรือเรียกสั้นๆ ว่า "Regex" คือ "ชุดอักขระพิเศษที่ใช้อธิบายรูปแบบของข้อความ" พูดง่ายๆ ก็คือ มันเป็นเหมือนภาษากลางสำหรับค้นหาและแทนที่ข้อความตามเงื่อนไขที่ไม่ตายตัว เช่น "ตัวเลขที่เรียงกัน 3 ตัว" หรือ "ข้อความที่มีรูปแบบเหมือนอีเมล" เป็นต้น
และฟีเจอร์มาตรฐาน (ไลบรารี) สำหรับจัดการ Regular Expression ใน Python ก็คือโมดูล `re` นั่นเอง เพียงแค่ import โมดูล `re` เข้ามา คุณก็สามารถจัดการกับข้อความที่ซับซ้อนได้ด้วยโค้ดเพียงไม่กี่บรรทัด
เริ่มจากพื้นฐาน! 4 ฟังก์ชันสำหรับ 'การค้นหา' ข้อความ
ในโมดูล `re` มีฟังก์ชันสำหรับค้นหาข้อความอยู่หลายตัว แต่ก่อนอื่นเรามาทำความรู้จักกับ 4 ฟังก์ชันที่ใช้บ่อยที่สุดกันก่อน ฟังก์ชันเหล่านี้จะรับอาร์กิวเมนต์เป็น "รูปแบบ Regular Expression" และ "ข้อความที่ต้องการค้นหา"
1. `re.search()` - คืนค่าผลลัพธ์แรกที่พบ
`re.search()` จะทำการตรวจสอบทั้งสตริงและคืนค่าข้อมูลของส่วนที่ตรงกับรูปแบบเป็นอันแรก หากไม่พบจะคืนค่า `None` ซึ่งเป็นฟังก์ชันค้นหาที่ใช้บ่อยและง่ายที่สุด
ตัวอย่างเช่น เรามาลองค้นหาคำว่า "Python" จากในประโยคกัน
<?php
import re
text = "Hello, this is a pen. I love Python programming!"
pattern = "Python"
# ค้นหารูปแบบภายในสตริง
match = re.search(pattern, text)
if match:
print(f"เจอแล้ว!: '{match.group()}'")
print(f"ตำแหน่งเริ่มต้น: {match.start()}, ตำแหน่งสิ้นสุด: {match.end()}")
else:
print("ไม่พบ")
# ผลการรัน:
# เจอแล้ว!: 'Python'
# ตำแหน่งเริ่มต้น: 31, ตำแหน่งสิ้นสุด: 37
?>
2. `re.match()` - ตรวจสอบว่า "ส่วนหัว" ของสตริงตรงกันหรือไม่
`re.match()` คล้ายกับ `re.search()` แต่มีความแตกต่างที่สำคัญคือ มันจะตรวจสอบแค่ว่ารูปแบบนั้นตรงกับส่วนเริ่มต้นของสตริงหรือไม่เท่านั้น รูปแบบที่อยู่กลางสตริงจะถูกมองข้ามไป
เรามาลองใช้ `re.match()` กับประโยคเดิมดู "Hello" จะตรงกัน แต่ "Python" จะไม่ตรง
<?php
import re
text = "Hello, this is a pen. I love Python programming!"
# รูปแบบที่ 1: "Hello" (อยู่ที่จุดเริ่มต้นของสตริง)
match1 = re.match("Hello", text)
if match1:
print(f"ผลการค้นหา 'Hello': ตรงกัน! -> {match1.group()}")
else:
print("ผลการค้นหา 'Hello': ไม่ตรงกัน")
# รูปแบบที่ 2: "Python" (อยู่ที่กลางสตริง)
match2 = re.match("Python", text)
if match2:
print(f"ผลการค้นหา 'Python': ตรงกัน! -> {match2.group()}")
else:
print("ผลการค้นหา 'Python': ไม่ตรงกัน")
# ผลการรัน:
# ผลการค้นหา 'Hello': ตรงกัน! -> Hello
# ผลการค้นหา 'Python': ไม่ตรงกัน
?>
3. `re.findall()` - คืนค่า "ทั้งหมด" ที่ตรงกันในรูปแบบลิสต์
`re.findall()` จะค้นหาส่วนที่ตรงกับรูปแบบทั้งหมด และคืนค่ากลับมาเป็นลิสต์ของสตริง ซึ่งสะดวกมากในกรณีที่ต้องการดึงตัวเลขทั้งหมดที่อยู่ในประโยค
ในที่นี้ เราจะใช้รูปแบบ Regular Expression `\d+` ซึ่งหมายถึง "ลำดับของตัวเลขหนึ่งตัวขึ้นไป"
<?php
import re
text = "สินค้า A ราคา 100 บาทต่อ 1 ชิ้น, สินค้า B ราคา 250 บาทสำหรับ 3 ชิ้น หมายเลขคำสั่งซื้อคือ 8801"
# \d+ คือ regular expression ที่หมายถึง 'ตัวเลขหนึ่งตัวหรือมากกว่าที่ต่อเนื่องกัน'
pattern = r"\d+"
# ดึงส่วนที่ตรงกันทั้งหมดมาเป็นลิสต์
numbers = re.findall(pattern, text)
print(f"ลิสต์ของตัวเลขที่พบ: {numbers}")
# ผลการรัน:
# ลิสต์ของตัวเลขที่พบ: ['1', '100', '3', '250', '8801']
?>
※ ตัว `r` ที่อยู่หน้าแพตเทิร์นของ Regular Expression หมายถึง "raw string" ซึ่งเป็นเคล็ดลับเล็กๆ น้อยๆ ที่จะทำให้เครื่องหมาย backslash `\` ไม่ถูกประมวลผลเป็น escape character ขอแนะนำให้ใส่ไว้เสมอเมื่อเขียน Regular Expression เพื่อป้องกันข้อผิดพลาดที่ไม่จำเป็น
4. `re.finditer()` - คืนค่าทั้งหมดที่ตรงกันในรูปแบบ "Iterator"
`re.finditer()` คล้ายกับ `re.findall()` แต่ต่างกันตรงที่มันจะคืนค่าผลลัพธ์เป็น "Iterator" แทนที่จะเป็นลิสต์ ซึ่ง Iterator เหมาะสำหรับการประมวลผลข้อมูลทีละตัว เช่น ใน vòng lặp for
ฟังก์ชันนี้มีประโยชน์เมื่อคุณไม่เพียงต้องการแค่ข้อความที่ตรงกัน แต่ยังต้องการ Match Object (อ็อบเจกต์ที่มีข้อมูลเช่นตำแหน่งเริ่มต้น) ที่ `search` หรือ `match` คืนค่ากลับมาด้วย
<?php
import re
text = "วันเกิดของฉันคือ 31 ธันวาคม 1995 และวันเกิดของเขาคือ 5 พฤษภาคม 2003"
# ค้นหาตัวเลข 4 หลัก
pattern = r"\d{4}"
# ดึงข้อมูลที่ตรงกันมาเป็น Iterator
matches = re.finditer(pattern, text)
print("ปี ค.ศ. ที่พบ:")
for m in matches:
print(f"- {m.group()} (ตำแหน่ง: {m.start()}-{m.end()})")
# ผลการรัน:
# ปี ค.ศ. ที่พบ:
# - 1995 (ตำแหน่ง: 25-29)
# - 2003 (ตำแหน่ง: 61-65)
?>
"แทนที่" ข้อความได้อย่างอิสระ - re.sub()
อีกหนึ่งฟังก์ชันที่ทรงพลังของ Regular Expression คือ "การแทนที่" ด้วย `re.sub()` คุณสามารถแทนที่ส่วนที่ตรงกับรูปแบบด้วยข้อความอื่นได้ในครั้งเดียว
ตัวอย่างเช่น ลองแทนที่หมายเลขโทรศัพท์ในประโยคด้วยข้อความ "(ไม่เปิดเผย)" เพื่อความเป็นส่วนตัว เราจะใช้ Regular Expression `\d{2,4}-\d{2,4}-\d{4}` ซึ่งตรงกับรูปแบบหมายเลขโทรศัพท์ของญี่ปุ่น (เช่น 080-1234-5678)
<?php
import re
text = "สอบถามข้อมูล กรุณาติดต่อฝ่ายสนับสนุน คุณ Sato (080-1111-2222) หรือติดต่อฝ่ายขาย คุณ Suzuki (03-3333-4444)"
# Regular expression ที่ตรงกับหมายเลขโทรศัพท์
pattern = r"\d{2,4}-\d{2,4}-\d{4}"
replacement = "(ไม่เปิดเผย)"
# แทนที่ส่วนที่ตรงกับรูปแบบ
new_text = re.sub(pattern, replacement, text)
print(new_text)
# ผลการรัน:
# สอบถามข้อมูล กรุณาติดต่อฝ่ายสนับสนุน คุณ Sato ((ไม่เปิดเผย)) หรือติดต่อฝ่ายขาย คุณ Suzuki ((ไม่เปิดเผย))
?>
ขั้นสูง: การแทนที่โดยใช้กลุ่ม (Groups)
พลังที่แท้จริงของ `re.sub()` จะแสดงออกมาเมื่อใช้ร่วมกับฟีเจอร์ "กลุ่ม" ของ Regular Expression การใส่วงเล็บ `()` ครอบส่วนของรูปแบบ จะทำให้คุณสามารถนำส่วนนั้น (กลุ่ม) กลับมาใช้ใหม่ในข้อความที่จะแทนที่ได้
ตัวอย่างเช่น ลองสลับลำดับของชื่อที่อยู่ในรูปแบบ "นามสกุล-ชื่อ" ให้เป็น "ชื่อ นามสกุล"
<?php
import re
text = "ตัวละคร: Tanjiro-Kamado, Zenitsu-Agatsuma, Inosuke-Hashibira"
# (\w+) คือกลุ่ม กลุ่มที่ 1 ตรงกับนามสกุล กลุ่มที่ 2 ตรงกับชื่อ
pattern = r"(\w+)-(\w+)"
# \2 อ้างอิงถึงกลุ่มที่ 2 (ชื่อ), \1 อ้างอิงถึงกลุ่มที่ 1 (นามสกุล)
replacement = r"\2 \1"
new_text = re.sub(pattern, replacement, text)
print(new_text)
# ผลการรัน:
# ตัวละคร: Kamado Tanjiro, Agatsuma Zenitsu, Hashibira Inosuke
?>
【ลองรันได้เลย】มาสร้างเครื่องมือตรวจสอบ Regex กันเถอะ!
จากความรู้ที่ได้เรียนมา เรามาสร้าง "เครื่องมือตรวจสอบ Regex" ที่สามารถตรวจสอบการทำงานของ Regular Expression ได้แบบเรียลไทม์บนเบราว์เซอร์กัน!
คัดลอกโค้ด HTML ทั้งหมดด้านล่าง แล้วบันทึกเป็นไฟล์ชื่อ `checker.html` หรือชื่ออื่นๆ แล้วเปิดในเบราว์เซอร์ของคุณ ลองใส่ข้อความใน Textarea และใส่รูปแบบที่ต้องการทดสอบ (เช่น `\d+` หรือ `[A-Za-z]+`) ในช่องป้อนข้อมูล Regex แล้วกดปุ่ม "เริ่มไฮไลท์" ส่วนที่ตรงกันจะแสดงเป็นสีฟ้า นี่คือตัวอย่างที่ทำให้คุณได้สัมผัสประสบการณ์ "โค้ดที่ทำงานได้จริง" ครับ/ค่ะ!
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>เครื่องมือตรวจสอบ Regular Expression พร้อมไฮไลท์</title>
<style>
body {
background-color: #202124;
color: #e8eaed;
font-family: sans-serif;
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #669df6;
border-bottom: 1px solid #5f6368;
padding-bottom: 10px;
}
textarea, input[type="text"] {
width: 100%;
padding: 10px;
margin-bottom: 10px;
background-color: #3c4043;
color: #e8eaed;
border: 1px solid #5f6368;
border-radius: 4px;
box-sizing: border-box; /* เพื่อให้ padding ถูกรวมในการคำนวณความกว้าง */
}
button {
padding: 10px 20px;
background-color: #8ab4f8;
color: #202124;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
button:hover {
opacity: 0.9;
}
#result {
margin-top: 20px;
padding: 15px;
border: 1px solid #5f6368;
border-radius: 4px;
white-space: pre-wrap; /* เพื่อให้แสดงผลการขึ้นบรรทัดใหม่ */
word-wrap: break-word; /* เพื่อให้ตัดคำยาวๆ */
}
.highlight {
background-color: #3367d6; /* ไฮไลท์ด้วยสีฟ้าสว่าง */
color: #ffffff;
border-radius: 3px;
padding: 0 2px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>เครื่องมือตรวจสอบ Regular Expression พร้อมไฮไลท์</h1>
<label for="text-input">ข้อความที่ต้องการทดสอบ:</label>
<textarea id="text-input" rows="8">Python 3.10 is the latest version. My email is sample-user@example.com. Please call me at 090-1234-5678. The event is on 2025/07/26.</textarea>
<label for="regex-input">รูปแบบ Regular Expression:</label>
<input type="text" id="regex-input" value="\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b">
<button onclick="highlightMatches()">เริ่มไฮไลท์</button>
<label for="result" style="margin-top: 20px;">ผลลัพธ์:</label>
<div id="result"></div>
</div>
<script>
function highlightMatches() {
const text = document.getElementById('text-input').value;
const regexPattern = document.getElementById('regex-input').value;
const resultDiv = document.getElementById('result');
if (!text || !regexPattern) {
resultDiv.textContent = 'กรุณาป้อนข้อความและรูปแบบ Regular Expression';
return;
}
try {
// เพิ่ม flag g (ค้นหาทั้งหมด) และ i (ไม่สนใจตัวพิมพ์เล็ก/ใหญ่)
const regex = new RegExp(regexPattern, 'gi');
const highlightedText = text.replace(regex, (match) => {
return `<span class="highlight">${match}</span>`;
});
resultDiv.innerHTML = highlightedText;
} catch (e) {
resultDiv.textContent = 'รูปแบบ Regular Expression ไม่ถูกต้อง: ' + e.message;
}
}
// เรียกใช้งานเมื่อโหลดหน้าเว็บครั้งแรกด้วย
document.addEventListener('DOMContentLoaded', highlightMatches);
</script>
</body>
</html>
เทคนิคและข้อควรระวังที่ควรรู้!
สุดท้ายนี้ เราจะมาอธิบายเทคนิคบางอย่างที่จะช่วยให้คุณใช้ Regular Expression ได้อย่างคล่องแคล่วมากขึ้น รวมถึงข้อผิดพลาดที่มือใหม่มักจะเจอกัน
การจับคู่แบบละโมบ (Greedy) กับ ไม่ละโมบ (Non-Greedy)
โดยปกติแล้ว quantifiers ของ Regular Expression อย่าง `*` หรือ `+` จะทำงานแบบ "ละโมบ (Greedy)" ซึ่งหมายความว่ามันจะพยายามจับคู่กับข้อความที่ยาวที่สุดเท่าที่จะทำได้
ตัวอย่างเช่น สมมติว่าคุณต้องการดึงเฉพาะเนื้อหาจาก `<p>contents</p>` จะเกิดอะไรขึ้นถ้าเราใช้รูปแบบ `<.*>`?
<?php
import re
html = "<p>first paragraph</p><p>second paragraph</p>"
# การจับคู่แบบละโมบ (Greedy)
greedy_match = re.search(r"<.*>", html)
print(f"การจับคู่แบบละโมบ: {greedy_match.group()}")
# การจับคู่แบบไม่ละโมบ (Non-Greedy): ใส่ ? หลัง *
non_greedy_match = re.search(r"<.*?>", html)
print(f"การจับคู่แบบไม่ละโมบ: {non_greedy_match.group()}")
# ผลการรัน:
# การจับคู่แบบละโมบ: <p>first paragraph</p><p>second paragraph</p>
# การจับคู่แบบไม่ละโมบ: <p>
?>
ในการจับคู่แบบละโมบ มันจะดึงข้อความตั้งแต่ `<` ตัวแรกไปจนถึง `>` ตัวสุดท้าย ซึ่งคือทั้งหมดที่มันสามารถจับคู่ได้ ในทางกลับกัน `*?` ที่มี `?` ต่อท้าย จะทำงานแบบ "ไม่ละโมบ (Non-Greedy)" ซึ่งหมายความว่ามันจะพยายามจับคู่กับข้อความที่สั้นที่สุดเท่าที่จะทำได้ ทำให้มันหยุดจับคู่เมื่อเจอ `>` ตัวแรก ความแตกต่างนี้สำคัญมากในการแยกวิเคราะห์ (parsing) ข้อมูล HTML หรือ XML
เพิ่มประสิทธิภาพด้วย `re.compile()`
หากคุณต้องใช้รูปแบบ Regular Expression เดิมซ้ำๆ หลายครั้งในโปรแกรม ขอแนะนำให้ "คอมไพล์" รูปแบบนั้นไว้ล่วงหน้าโดยใช้ `re.compile()`
วิธีนี้จะช่วยลดภาระของ Python ในการวิเคราะห์รูปแบบทุกครั้ง ทำให้ประมวลผลได้เร็วขึ้น อ็อบเจกต์ของรูปแบบที่คอมไพล์แล้วจะมีเมธอดอย่าง `search()` หรือ `findall()` ให้ใช้งานได้เลย
<?php
import re
# คอมไพล์รูปแบบของอีเมล
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
# ค้นหาโดยใช้อ็อบเจกต์ที่คอมไพล์แล้ว
text1 = "ที่อยู่ติดต่อคือ a@b.com"
match1 = email_pattern.search(text1)
if match1:
print(f"พบใน Text1: {match1.group()}")
text2 = "ช่องทางสนับสนุนคือ support@example.co.jp"
match2 = email_pattern.search(text2)
if match2:
print(f"พบใน Text2: {match2.group()}")
# ผลการรัน:
# พบใน Text1: a@b.com
# พบใน Text2: support@example.co.jp
?>
นอกจากนี้ยังมีข้อดีคือทำให้โค้ดเป็นระเบียบและอ่านง่ายขึ้นด้วยนะครับ/คะ
สรุป
ในครั้งนี้เราได้แนะนำพื้นฐานของ Regular Expression โดยใช้โมดูล `re` ของ Python ในตอนแรกอาจจะรู้สึกว่ามันค่อนข้างซับซ้อน แต่เมื่อคุณได้เห็นถึงความสามารถของมันแล้ว คุณจะขาดมันในการประมวลผลข้อความไม่ได้อีกเลย
- `re.search()`: ค้นหาส่วนที่ตรงกับรูปแบบเป็นอันแรกจากในสตริง
- `re.match()`: ตรวจสอบว่าส่วนเริ่มต้นของสตริงตรงกับรูปแบบหรือไม่
- `re.findall()`: ดึงส่วนที่ตรงกับรูปแบบทั้งหมดมาเป็นลิสต์
- `re.sub()`: แทนที่ส่วนที่ตรงกับรูปแบบ
การผสมผสานพื้นฐานเหล่านี้และใช้ metacharacters ได้อย่างคล่องแคล่ว จะช่วยเพิ่มความสามารถในการประมวลผลข้อมูลของคุณได้อย่างก้าวกระโดด ลองคัดลอกโค้ดในบทความนี้ไปทดลองกับรูปแบบต่างๆ เพื่อทำความคุ้นเคยกับ Regular Expression กันได้เลยครับ/ค่ะ!
ขั้นตอนต่อไป
หลังจากที่คุณสามารถจัดการข้อมูลข้อความด้วย Regular Expression ได้อย่างอิสระแล้ว ลองมาท้าทายกับการจัดการไฟล์กันต่อไหมครับ/คะ? โดยเฉพาะไฟล์ CSV ซึ่งเป็นรูปแบบที่สำคัญและใช้กันอย่างแพร่หลายในเว็บแอปพลิเคชันและการวิเคราะห์ข้อมูล มาเรียนรู้วิธีการอ่านและเขียนไฟล์ CSV ด้วยโมดูล `csv` ของ Python ในบทความถัดไปกัน