본문 바로가기
Python

24. 정규표현식 - 문자열 처리의 끝판왕

by 샤나엘 2026. 2. 22.
반응형

정규표현식

24. 정규표현식 - 문자열 처리의 끝판왕

"이 텍스트에서 이메일만 쏙 뽑아줘", "전화번호 형식이 맞는지 확인해줘" — 이런 요구를 단 한 줄로 해결하는 방법이 있다. 바로 정규표현식(Regular Expression)이다.


정규표현식이란?

정규표현식(줄여서 regex)은 문자열에서 특정 패턴을 찾거나 치환하는 규칙이다. 파이썬에서는 re 모듈을 사용한다.

import re

text = "제 전화번호는 010-1234-5678입니다."
result = re.search(r"\d{3}-\d{4}-\d{4}", text)
print(result.group())   # 010-1234-5678

처음 보면 암호처럼 보이지만, 하나씩 배우면 생각보다 간단하다.

정규표현식 주요 패턴


기본 패턴

메타 문자

패턴 의미 예시
. 아무 문자 1개 (줄바꿈 제외) a.c → "abc", "a1c"
\d 숫자 1개 ([0-9]) \d\d → "42", "99"
\w 영문/숫자/밑줄 1개 \w+ → "hello", "a1"
\s 공백 문자 1개 (스페이스, 탭, 줄바꿈) \s → " ", "\t"
\D 숫자가 아닌 것 \D+ → "abc", "!@"
\W 영문/숫자/밑줄이 아닌 것 \W → "!", " "
\S 공백이 아닌 것 \S+ → "hello"
import re

text = "오늘은 2025년 2월 15일입니다."
numbers = re.findall(r"\d+", text)
print(numbers)   # ['2025', '2', '15']

반복 패턴

패턴 의미
* 0회 이상 반복
+ 1회 이상 반복
? 0회 또는 1회
{n} 정확히 n회
{n,m} n회 이상 m회 이하
import re

# * : 0회 이상
print(re.findall(r"ab*c", "ac abc abbc"))      # ['ac', 'abc', 'abbc']

# + : 1회 이상
print(re.findall(r"ab+c", "ac abc abbc"))      # ['abc', 'abbc']

# ? : 0회 또는 1회
print(re.findall(r"colou?r", "color colour"))   # ['color', 'colour']

# {n} : 정확히 n회
print(re.findall(r"\d{4}", "2025년 02월"))       # ['2025']

# {n,m} : n~m회
print(re.findall(r"\d{2,4}", "1 12 123 1234"))  # ['12', '123', '1234']

위치 패턴

패턴 의미
^ 문자열의 시작
$ 문자열의 끝
import re

print(re.search(r"^Hello", "Hello World"))    # 매치 됨
print(re.search(r"^Hello", "Say Hello"))      # None (시작이 아님)
print(re.search(r"World$", "Hello World"))    # 매치 됨

문자 클래스 [ ]

[ ] 안에 있는 문자 중 하나와 매치된다.

import re

# [abc] - a, b, c 중 하나
print(re.findall(r"[aeiou]", "hello"))        # ['e', 'o']

# [0-9] - 숫자 (\d와 같음)
print(re.findall(r"[0-9]+", "abc123def456"))  # ['123', '456']

# [a-zA-Z] - 모든 영문자
print(re.findall(r"[a-zA-Z]+", "Hello 세계 123"))  # ['Hello']

# [^abc] - a, b, c가 아닌 것 (^ = 부정)
print(re.findall(r"[^0-9]+", "abc123def"))    # ['abc', 'def']

re 모듈의 주요 함수

re.match() - 문자열 시작부터 매치

import re

result = re.match(r"\d+", "123abc")
print(result.group())    # 123

result = re.match(r"\d+", "abc123")
print(result)            # None (시작이 숫자가 아님)

re.search() - 문자열 어디서든 첫 매치

import re

result = re.search(r"\d+", "abc123def456")
print(result.group())    # 123 (첫 번째만)

re.findall() - 매치되는 모든 것

import re

result = re.findall(r"\d+", "abc123def456ghi789")
print(result)   # ['123', '456', '789']

re.sub() - 패턴을 찾아서 치환

import re

# 숫자를 모두 #으로 치환
result = re.sub(r"\d", "#", "전화번호: 010-1234-5678")
print(result)   # 전화번호: ###-####-####

# 여러 공백을 하나로
text = "너무    많은   공백이    있다"
result = re.sub(r"\s+", " ", text)
print(result)   # 너무 많은 공백이 있다

re.split() - 패턴으로 분리

import re

# 여러 구분자로 분리
text = "사과,바나나;포도 딸기"
result = re.split(r"[,;\s]+", text)
print(result)   # ['사과', '바나나', '포도', '딸기']

그룹 (Group)

괄호 ()로 패턴의 일부를 그룹으로 묶을 수 있다. 매치된 결과에서 원하는 부분만 뽑을 때 유용하다.

import re

# 날짜에서 년, 월, 일 추출
text = "오늘은 2025-02-15입니다."
result = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)

print(result.group())    # 2025-02-15 (전체 매치)
print(result.group(1))   # 2025 (첫 번째 그룹)
print(result.group(2))   # 02 (두 번째 그룹)
print(result.group(3))   # 15 (세 번째 그룹)

이름 있는 그룹

import re

pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
result = re.search(pattern, "2025-02-15")

print(result.group("year"))    # 2025
print(result.group("month"))   # 02
print(result.group("day"))     # 15

실전 활용: 이메일 검증

import re

def is_valid_email(email):
    """이메일 형식 검증"""
    pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    return bool(re.match(pattern, email))

# 테스트
emails = [
    "user@example.com",
    "my.name@company.co.kr",
    "invalid@",
    "@no-user.com",
    "spaces in@email.com"
]

for email in emails:
    result = "유효" if is_valid_email(email) else "무효"
    print(f"{email:30s} → {result}")

출력:

user@example.com               → 유효
my.name@company.co.kr          → 유효
invalid@                       → 무효
@no-user.com                   → 무효
spaces in@email.com            → 무효

실전 활용: 전화번호 추출 및 정규화

import re

def normalize_phone(text):
    """다양한 형식의 전화번호를 통일"""
    # 다양한 형식 매치: 010-1234-5678, 01012345678, 010 1234 5678
    pattern = r"(01[016789])[\s.-]?(\d{3,4})[\s.-]?(\d{4})"
    match = re.search(pattern, text)
    if match:
        return f"{match.group(1)}-{match.group(2)}-{match.group(3)}"
    return None

# 테스트
texts = [
    "전화번호: 010-1234-5678",
    "연락처 01098765432 입니다",
    "010 5555 6666으로 전화주세요",
    "회사 번호: 02-555-1234"
]

for t in texts:
    result = normalize_phone(t)
    if result:
        print(f"추출: {result}")
    else:
        print(f"휴대폰 번호 없음: {t}")

자주 쓰는 패턴 모음

import re

# 1. 한글만 추출
text = "Hello 안녕하세요 World 세계"
korean = re.findall(r"[가-힣]+", text)
print(korean)   # ['안녕하세요', '세계']

# 2. HTML 태그 제거
html = "<h1>제목</h1><p>내용입니다.</p>"
clean = re.sub(r"<[^>]+>", "", html)
print(clean)    # 제목내용입니다.

# 3. URL 추출
text = "사이트: https://www.example.com 방문하세요"
urls = re.findall(r"https?://[\w./\-?=&]+", text)
print(urls)     # ['https://www.example.com']

# 4. 중복 공백 정리
text = "  너무    많은   공백  "
clean = re.sub(r"\s+", " ", text).strip()
print(clean)    # 너무 많은 공백

직접 해보기

문제 1. 문자열에서 모든 숫자를 추출해 합계를 구하는 함수 sum_numbers(text)를 작성해보자.

문제 2. 비밀번호가 다음 조건을 만족하는지 검증하는 함수를 작성해보자: 8자 이상, 영문 대문자 1개 이상, 소문자 1개 이상, 숫자 1개 이상.

문제 3. 텍스트에서 모든 이메일 주소를 찾아 리스트로 반환하는 함수를 작성해보자.

문제 4. "2025-02-15" 형식의 날짜를 "2025년 02월 15일" 형식으로 바꾸는 함수를 작성해보자.

정답 보기
import re
‍
# 문제 1 - 숫자 합계
def sum_numbers(text):
    numbers = re.findall(r"\d+", text)
    return sum(int(n) for n in numbers)
‍
print(sum_numbers("사과 3개, 바나나 5개, 포도 12개"))  # 20
print(sum_numbers("점수: 85 + 92 + 78"))              # 255
‍
‍
# 문제 2 - 비밀번호 검증
def is_valid_password(pw):
    if len(pw) < 8:
        return False
    if not re.search(r"[A-Z]", pw):    # 대문자
        return False
    if not re.search(r"[a-z]", pw):    # 소문자
        return False
    if not re.search(r"\d", pw):       # 숫자
        return False
    return True
‍
print(is_valid_password("MyPass123"))    # True
print(is_valid_password("short1A"))      # False (8자 미만)
print(is_valid_password("alllowercase1")) # False (대문자 없음)
‍
‍
# 문제 3 - 이메일 추출
def find_emails(text):
    pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
    return re.findall(pattern, text)
‍
text = "연락처: user@example.com, admin@test.co.kr 로 보내세요"
print(find_emails(text))
# ['user@example.com', 'admin@test.co.kr']
‍
‍
# 문제 4 - 날짜 형식 변환
def convert_date(date_str):
    return re.sub(
        r"(\d{4})-(\d{2})-(\d{2})",
        r"\1년 \2월 \3일",
        date_str
    )
‍
print(convert_date("2025-02-15"))   # 2025년 02월 15일
print(convert_date("오늘은 2025-01-01입니다"))
# 오늘은 2025년 01월 01일입니다

오늘의 정리

항목 내용
re.match() 문자열 시작에서 패턴 매치
re.search() 문자열 어디서든 첫 번째 매치
re.findall() 매치되는 모든 결과를 리스트로 반환
re.sub() 패턴을 찾아서 치환
메타 문자 \d 숫자, \w 영문숫자, \s 공백, . 아무 문자
반복 * 0+회, + 1+회, ? 0~1회, {n,m} n~m회
그룹 (패턴)으로 부분 추출, group(1)로 접근

다음 편 예고: 가상환경과 프로젝트 관리 - 프로처럼 세팅하기

프로젝트마다 다른 라이브러리 버전을 써야 한다면? 가상환경(venv)과 pip로 깔끔하게 관리하는 방법을 알아보자.


태그: 파이썬 Python 파이썬독학 정규표현식 regex re모듈 패턴매칭 문자열처리 파이썬중급 IT교육

반응형

댓글