목차
1. 정규표현식이란?
정규표현식(Regular Expression, Regex)은 특정 패턴을 가진 문자열을 검색, 추출, 변환하기 위한 형식 언어입니다.
단순히 "특정 단어 찾기"를 넘어, 이메일·전화번호·URL처럼 규칙을 가진 문자열 패턴을 정의하고 한 번에 처리할 수 있습니다.
쉽게 말하면, 정규표현식은 문자열에 대한 "검색 공식" 입니다.
정규표현식을 사용하는 이유
코드 10줄로 처리할 작업을 정규표현식 1줄로 끝낼 수 있습니다. 문자열 처리 로직이 복잡할수록 정규표현식의 가치는 극대화됩니다.
정규표현식이 없다면 어떻게 될까요? 이메일 형식 검증을 예로 들겠습니다.
# 정규표현식 없이 이메일 검증 (복잡하고 불완전)
def is_email(text):
if '@' not in text:
return False
parts = text.split('@')
if len(parts) != 2:
return False
if '.' not in parts[1]:
return False
# ... 계속 늘어나는 조건들 ...
return True
# 정규표현식으로 이메일 검증 (간결하고 정확)
import re
pattern = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
result = pattern.match('user@example.com')
print(bool(result)) # True
정규표현식이 꼭 필요한 상황
- 웹 크롤링에서 HTML 태그 제거 또는 특정 요소 추출
- 로그 파일에서 에러 코드, IP 주소, 타임스탬프 추출
- 사용자 입력 데이터 유효성 검사 (이메일, 전화번호, 비밀번호)
- 대용량 텍스트 데이터에서 패턴 기반 정보 추출
- 코드 리팩토링 시 특정 패턴의 변수/함수명 일괄 변경
2. Python re 모듈 기초
Python은 표준 라이브러리에 re 모듈을 내장하고 있어 별도 설치 없이 정규표현식을 사용할 수 있습니다.
모듈 re는 regular expression의 약자로 외워두시면 좋을 것 같습니다.
import re
re 모듈의 주요 함수
함수 동작 반환값
| re.match() | 문자열 시작부터 매칭 | Match 객체 또는 None |
| re.search() | 문자열 전체에서 첫 매칭 | Match 객체 또는 None |
| re.findall() | 모든 매칭 문자열 반환 | 리스트(list) |
| re.finditer() | 모든 매칭 이터레이터 | Iterator |
| re.sub() | 매칭 문자열 치환 | 치환된 문자열 |
| re.split() | 패턴으로 문자열 분리 | 리스트(list) |
3. re.compile() — 왜 사용하는가?
re.compile()은 정규표현식 패턴을 미리 컴파일하여 재사용 가능한 패턴 객체를 만드는 함수입니다.
re.compile() 기본 사용법
import re
pattern = re.compile('python')
# findall - 모든 매칭 반환
result = pattern.findall('I love python and python programming')
print(result) # ['python', 'python']
# search - 첫 번째 매칭
m = pattern.search('I love python')
print(m.group()) # python
# sub - 치환
new_text = pattern.sub('Java', 'I love python')
print(new_text) # I love Java
4. 정규표현식 핵심 문법
기본 메타 문자
패턴 의미 예시
| . | 임의의 문자 1개 (줄바꿈 제외) | a.c → abc, a1c |
| [] | 문자 클래스 (안의 문자 중 1개) | [abc] → a, b, c |
| [^] | 부정 문자 클래스 | [^0-9] → 숫자 제외 |
| | | OR 연산자 | cat|dog → cat 또는 dog |
| () | 그룹화 및 캡처 | (ab)+ → ab, abab |
수량자(Quantifier)
패턴 의미 예시
| + | 1개 이상 반복 | a+ → a, aa, aaa |
| * | 0개 이상 반복 | a* → '', a, aa |
| ? | 0개 또는 1개 | colou?r → color, colour |
| {n} | 정확히 n번 반복 | a{3} → aaa |
| {n,} | n번 이상 반복 | a{2,} → aa, aaa, ... |
| {n,m} | n번 이상 m번 이하 | a{2,4} → aa, aaa, aaaa |
앵커(Anchor) & 특수 시퀀스
패턴 의미 예시
| ^ | 문자열/줄의 시작 | ^Hello → Hello로 시작 |
| $ | 문자열/줄의 끝 | world$ → world로 끝 |
| \d | 숫자 [0-9] | \d+ → 123, 42 |
| \D | 비숫자 [^0-9] | \D+ → abc, !@# |
| \w | 단어 문자 [a-zA-Z0-9_] | \w+ → hello, var_1 |
| \W | 비단어 문자 | \W+ → !!, ... |
| \s | 공백 문자 | \s+ → 스페이스, 탭 |
| \b | 단어 경계 | \bcat\b → cat (단독) |
5. 실전 예제 — HTML 태그 찾기
웹 크롤링과 데이터 파싱에서 가장 자주 마주치는 작업 중 하나가 HTML 태그 처리입니다.
HTML 태그 패턴 분석
pattern = re.compile(r"<[a-z0-9]+>|</[a-z0-9]+>", re.IGNORECASE)
패턴 요소 설명
| < | HTML 태그 시작 기호 (리터럴 매칭) |
| [a-z0-9] | 태그 이름: 알파벳 소문자 또는 숫자 |
| + | 태그 이름은 1자 이상 (p, div, h1 등) |
| > | HTML 태그 종료 기호 (리터럴 매칭) |
| | | OR — 여는 태그 또는 닫는 태그 |
| </[a-z0-9]+> | 닫는 태그 패턴 (</p>, </div> 등) |
| re.IGNORECASE | <DIV>, <div>, <Div> 모두 매칭 |
실행 코드 및 결과
import re
text = "<p>Hello</p> <DIV>World</DIV> <H1>Title</H1>"
# 태그 찾기
pattern = re.compile(r"<[a-z0-9]+>|</[a-z0-9]+>", re.IGNORECASE)
tags = pattern.findall(text)
print("찾은 태그:", tags)
# 찾은 태그: ['<p>', '</p>', '<DIV>', '</DIV>', '<H1>', '</H1>']
# 태그 제거 (텍스트만 추출)
clean_text = pattern.sub("", text)
print("순수 텍스트:", clean_text)
# 순수 텍스트: Hello World Title
6. re 옵션 플래그
re.compile()의 두 번째 인자로 플래그를 지정하면 매칭 동작을 세밀하게 제어할 수 있습니다.
import re
# re.IGNORECASE (re.I) — 대소문자 무시
p1 = re.compile('hello', re.IGNORECASE)
print(p1.findall('Hello HELLO hello')) # ['Hello', 'HELLO', 'hello']
# re.MULTILINE (re.M) — ^, $ 가 각 줄의 시작/끝에 적용
p2 = re.compile('^line', re.MULTILINE)
text = 'line1\nline2\nline3'
print(p2.findall(text)) # ['line', 'line', 'line']
# re.DOTALL (re.S) — . 이 줄바꿈도 매칭
p3 = re.compile('<.*?>', re.DOTALL)
# 플래그 결합 — | 로 여러 개 동시 사용
p4 = re.compile('pattern', re.IGNORECASE | re.MULTILINE)
7. 실전 활용 예제
1. 이메일 주소 추출
import re
text = "문의: admin@company.com 또는 support@help.co.kr"
email_pattern = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
emails = email_pattern.findall(text)
print(emails) # ['admin@company.com', 'support@help.co.kr']
2. 전화번호 검증
import re
phone_pattern = re.compile(r'01[016789]-?\d{3,4}-?\d{4}')
phones = ['010-1234-5678', '01012345678', '011-999-9999', '02-1234-5678']
for p in phones:
is_valid = bool(phone_pattern.fullmatch(p))
print(f'{p}: {"유효" if is_valid else "무효"}')
# 010-1234-5678: 유효
# 01012345678: 유효
# 011-999-9999: 유효
# 02-1234-5678: 무효
3. 로그 데이터 파싱
import re
log = '192.168.1.100 - [2024/01/15 10:30:00] "GET /api/data" 200 1234'
# IP 주소 추출
ip_pattern = re.compile(r'(\d{1,3}\.){3}\d{1,3}')
ip = ip_pattern.search(log).group()
print('IP:', ip) # 192.168.1.100
# 상태 코드 추출
status_pattern = re.compile(r'"\s(\d{3})\s')
status = status_pattern.search(log).group(1)
print('Status:', status) # 200
# 날짜 추출
date_pattern = re.compile(r'\[(.*?)\]')
date = date_pattern.search(log).group(1)
print('Date:', date) # 2024/01/15 10:30:00
4. URL에서 도메인 추출
import re
urls = [
'https://www.google.com/search?q=python',
'http://blog.example.co.kr/post/123',
'https://api.github.com/users/octocat',
]
domain_pattern = re.compile(r'https?://([\w.-]+)')
for url in urls:
domain = domain_pattern.search(url).group(1)
print(domain)
# www.google.com
# blog.example.co.kr
# api.github.com
8. 그룹 캡처 — 더 강력하게 활용하기
괄호 ()를 사용하면 매칭된 패턴 안에서 특정 부분만 추출할 수 있습니다. 이를 그룹 캡처라고 합니다.
import re
# 날짜 형식에서 연/월/일 각각 추출
date_pattern = re.compile(r'(\d{4})-(\d{2})-(\d{2})')
m = date_pattern.search('오늘은 2024-01-15 입니다')
print(m.group(0)) # 2024-01-15 (전체 매칭)
print(m.group(1)) # 2024 (첫 번째 그룹: 연도)
print(m.group(2)) # 01 (두 번째 그룹: 월)
print(m.group(3)) # 15 (세 번째 그룹: 일)
# 이름 있는 그룹 (Named Group) - 더 가독성 좋은 방법
named_pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})')
m2 = named_pattern.search('2024-01-15')
print(m2.group('year')) # 2024
print(m2.group('month')) # 01
print(m2.group('day')) # 15
자주 하는 실수
- Raw string 미사용: r'\d+' 대신 '\\d+'처럼 이중 이스케이프가 필요해져 가독성이 떨어집니다. 항상 r''를 사용하세요.
- re.match vs re.search 혼동: match()는 문자열 시작에서만 탐색하고, search()는 전체를 탐색합니다.
마무리
정규표현식은 처음 마주하면 암호처럼 보이지만, 핵심 패턴 몇 가지만 익히면 문자열 처리 능력이 비약적으로 향상됩니다.
중요한 것은 외우는 것이 아니라, 필요할 때 찾아보고 직접 테스트해보는 것입니다.
'Python' 카테고리의 다른 글
| [Python] sort(), reverse()정리 (0) | 2026.03.27 |
|---|---|
| [Python] Selenium에서의 innerHTML, innerText, textContent 차이점 (0) | 2026.03.18 |
| [Python] pytest를 이용한 테스트 코드 작성법 (0) | 2026.03.11 |
| [Python] json.dumps() 사용법, 사용이유 (1) | 2026.03.10 |
| [Python] API 요청 방법 (0) | 2026.03.09 |