newhaneul

[Advanced Python Programming] Lecture 18. Regular Expression 본문

4. University Study/Advanced Python Programming

[Advanced Python Programming] Lecture 18. Regular Expression

뉴하늘 2026. 6. 10. 17:34
728x90

포스팅은 인하대학교 허혜선 교수님의 [202601-EEC3408-001] 고급파이썬프로그래밍을 수강하고 공부한 내용을 정리하기 위한 포스팅입니다.

 

 

1. 정규표현식 (Regular Expression)

 정규표현식(Regular Expression)은 일정한 규칙(패턴)을 가진 문자열을 표현하는 방법이다. re 모듈을 사용하며 복잡한 문자열에서 특정 패턴을 검색·추출·치환할 때 활용한다.

 

match vs search

함수 설명 예시
re.match() 문자열 처음부터 패턴 검사 re.match('Hello', 'Hello, world!')
re.search() 문자열 어느 위치든 패턴 검사 re.search('world!$', 'Hello, world!')

 

 

위치 지정자 ^와 $

import re

print(re.search('^Hello', 'Hello, world!'))   # Hello로 시작 → 매칭
print(re.search('world!$', 'Hello, world!'))  # world!로 끝남 → 매칭

<re.Match object; span=(0, 5), match='Hello'>
<re.Match object; span=(7, 13), match='world!'>
import re

print(re.match('Hello', 'Hello, world!'))   # Hello로 시작 → 매칭
print(re.match('^Hello', 'Hello, world!'))  # 매칭 ← ^ 는 사실 match에서 의미 없음 (이미 맨 앞이니까)
print(re.match('world!$', 'Hello, world!'))  # $ 의미 없음

<re.Match object; span=(0, 5), match='Hello'>
<re.Match object; span=(0, 5), match='Hello'>
None

 

 

OR 패턴 (|)

 두 문자열 중 하나라도 포함되면 매칭된다. Python의 or 연산자와 같은 개념이다.

import re

print(re.match('hello|world', 'hello'))  # hello 또는 world → 매칭
print(re.search('world|hello', 'hello'))  # hello 또는 world → 매칭

<re.Match object; span=(0, 5), match='hello'>
<re.Match object; span=(0, 5), match='hello'>

 

 

2. 범위 판단 

 

반복 수량자 * + ? {}

기호 의미 예시
* 앞 문자 0개 이상 a*b  b, ab, aab 모두 매칭
+ 앞 문자 1개 이상 a+b  ab, aab 매칭 / b 불가
? 앞 문자 0개 또는 1개 abc?d  abd, abcd 매칭
. 임의 문자 1개 ab.d  abxd, ab3d 매칭
{n} 정확히 n개 h{3}  hhh
{m,n} m개 이상 n개 이하 [0-9]{3,4} → 3~4자리 숫자

 

import re

print(re.match('[0-9]*', '1234'))
print(re.match('[0-9]+', '1234'))
print(re.match('[0-9]+', 'abcd'))

<re.Match object; span=(0, 4), match='1234'>
<re.Match object; span=(0, 4), match='1234'>
None
import re

print(re.match('a*b', 'b'))
print(re.match('a+b', 'b'))
print(re.match('a*b', 'aab'))
print(re.match('a+b', 'aab'))

<re.Match object; span=(0, 1), match='b'>
None
<re.Match object; span=(0, 3), match='aab'>
<re.Match object; span=(0, 3), match='aab'>
import re

print(re.match('abc?d', 'abd'))
print(re.match('ab[0-9]?c', 'ab3c'))
print(re.match('ab.d', 'abxd'))

<re.Match object; span=(0, 3), match='abd'>
<re.Match object; span=(0, 4), match='ab3c'>
<re.Match object; span=(0, 4), match='abxd'>
import re

print(re.match('h{3}', 'hhhello'))
print(re.match('(hello){3}', 'hellohellohelloworld'))

<re.Match object; span=(0, 3), match='hhh'>
<re.Match object; span=(0, 15), match='hellohellohello'>
  • 문자 개수 판단하기
    • 문자{개수}
    • (문자열){개수}

 

import re

print(re.match('[0-9]{3}-[0-9]{4}-[0-9]{4}', '123-4567-8901'))
print(re.match('[0-9]{3}-[0-9]{4}-[0-9]{4}', '123-4567-890'))

<re.Match object; span=(0, 13), match='123-4567-8901'>
None
  • 특정 범위의 문자가 몇 개 있는지 판단하는 방법
    • [0-9]{개수}

 

import re

print(re.match('[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}', '123-456-8901'))
print(re.match('[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}', '12-67-1890'))

<re.Match object; span=(0, 12), match='123-456-8901'>
None
  • 문자 개수 판단하기
    • (문자){시작개수, 끝개수}
    • (문자열){시작개수, 끝개수}
    • [0-9]{시작개수, 끝개수}

 

import re

print(re.match('[a-zA-Z0-9]+', 'Hello1234'))
print(re.match('[A-Z0-9]+', 'hello'))

<re.Match object; span=(0, 9), match='Hello1234'>
None
  • 숫자와 영문 문자를 조합해서 판단하는 방법
    • a-z
    • A-Z

 

문자 범위 [ ]

import re

print(re.match('[^A-Z]+', 'Hello'))
print(re.match('[^A-Z]+', 'hello'))

None
<re.Match object; span=(0, 5), match='hello'>
  • 대괄호 안에 ^를 붙이면 해당 범위를 제외한다. [^A-Z]+는 대문자를 제외한 모든 문자와 매칭된다.
    • [^범위]*
    • [^범위]+

 

import re

print(re.match('^[A-Z]+', 'Hello'))
print(re.match('^[A-Z]+', 'hello'))

<re.Match object; span=(0, 1), match='H'>
None
  • 특정 문자 범위로 시작하는지 알 때는 ^를 문자 범위 앞에 붙여준다.
    • ^[범위]+
    • ^[범위]*
    • [범위]+$
    • [범위]*$

 

import re

print(re.search('\*+', '1 ** 2'))
print(re.match('[$()a-zA-Z0-9]+', '$(document)'))

<re.Match object; span=(2, 4), match='**'>
<re.Match object; span=(0, 11), match='$(document)'>

 

 

특수 이스케이프 시퀀스

표현 의미
\d [0-9]와 같음 — 모든 숫자
\D [^0-9]와 같음 — 숫자를 제외한 모든 문자
\w [a-zA-Z0-9_]와 같음 — 영문·숫자·밑줄
\W [^a-zA-Z0-9_]와 같음 — 그 외 모든 문자
\s 공백 문자 (스페이스, 탭, 줄바꿈 등)
\S 공백 이외의 문자
  • 정규표현식 메타문자(* + ? . ^ $ ( ) [ ] -) 자체를 검색하려면 앞에 \를 붙인다. 예: \*, \.

 

import re

print(re.match('\d+', '1234'))
print(re.match('\D+', 'Hello'))
print(re.match('\w+', 'Hello_1234'))
print(re.match('\W+', '(:)'))

<re.Match object; span=(0, 4), match='1234'>
<re.Match object; span=(0, 5), match='Hello'>
<re.Match object; span=(0, 10), match='Hello_1234'>
<re.Match object; span=(0, 3), match='(:)'>
import re

print(re.match('[a-zA-Z0-9]+', 'Hello 1234'))
print(re.match('[a-zA-Z0-9\s]+', 'Hello 1234'))

<re.Match object; span=(0, 5), match='Hello'>
<re.Match object; span=(0, 10), match='Hello 1234'>

 

 

3. 그룹 사용하기

 

소괄호로 그룹 지정

패턴을 ()로 묶으면 매칭된 문자열을 그룹별로 꺼낼 수 있다.

import re

m = re.match('([0-9]+) ([0-9]+)', '10 295')
print(m.group(1))   # '10'
print(m.group(2))   # '295'
print(m.group())    # '10 295'  (전체)
print(m.groups())   # ('10', '295')  (튜플)

10
295
10 295
('10', '295')

 

그룹에 이름 붙이기 (?P<이름>)

그룹이 많아질 때 숫자 대신 이름으로 접근할 수 있어 가독성이 좋아진다.

import re

m = re.match('(?P<func>[a-zA-Z_][a-zA-Z0-9_]+)\((?P<arg>\w+)\)', 'print(1234)')
print(m.group('func'))
print(m.group('arg'))

print
1234
  • (?P<이름>정규표현식)

 

findall (모든 매칭 추출)

import re

print(re.findall('[0-9]+', '1 2 Fizz 4 Buzz Fizz 7 8'))

['1', '2', '4', '7', '8']

 

Sub (문자열 치환)

import re

print(re.sub('apple|orange', 'fruit', 'apple box orange tree'))
print(re.sub('[0-9]+', 'n', '1 2 Fizz 4 Buzz Fizz 7 8'))

fruit box fruit tree
n n Fizz n Buzz Fizz n n
import re

def multiple10(m):
    n = int(m.group())
    return str(n * 10)

print(re.sub('[0-9]+', multiple10, '1 2 Fizz 4 Buzz Fizz 7 8'))

10 20 Fizz 40 Buzz Fizz 70 80

 

찾은 문자열을 결과에 재활용 (\\숫자)

그룹으로 캡처한 내용을 바꿀 문자열 안에서 \\1, \\2로 참조할 수 있다.

import re

print(re.sub('([a-z]+) ([0-9]+)', '\\2 \\1 \\2 \\1', 'hello 1234'))
print(re.sub('({\s*)"(\w+)":\s*"(\w+)"(\s*})', '<\\2>\\3</\\2>', '{"name": "John"}'))

1234 hello 1234 hello
<name>John</name>

 

 

실습 문제 1: 한글 정규표현식

import re

print(re.match('[가-힣]+', '가나다라'))
print(re.findall('[가-힣]+', '가나다라'))
print(re.findall('[가-힣]', '가나다라'))
print(re.findall('[가-힣]+', '가A나B다C라D'))

<re.Match object; span=(0, 4), match='가나다라'>
['가나다라']
['가', '나', '다', '라']
['가', '나', '다', '라']

 

실습문제 2: 휴대폰 번호 정규표현식

import re

text = "제 번호는 010-1234-5678입니다."
pattern = r'\d{3}-\d{4}-\d{4}'
print(re.findall(pattern, text))

['010-1234-5678']

 

실습문제 3: 휴대폰, 지역번호

import re

text = "서울: 02-123-4567, 부산: 051-234-5678, 휴대폰: 010-1234-5678"
pattern = r'(\d{2,3})-(\d{3,4})-(\d{4})'
matches = re.findall(pattern, text)
print(matches)
for m in matches:
    print("-".join(m))

[('02', '123', '4567'), ('051', '234', '5678'), ('010', '1234', '5678')]
02-123-4567
051-234-5678
010-1234-5678

 

실습문제 4: 하이픈이 없는 경우까지 포함

import re

text = "전화: 01012345678 또는 010-1234-5678"
pattern = r'\d{3}-?\d{4}-?\d{4}'
matches = re.findall(pattern, text)
print(matches)

['01012345678', '010-1234-5678']

 

실습문제 5: 이메일 정규표현식

import re

text = "이메일: example123@gmail.com, 다른 이메일은 test.user@mail.co.kr"
pattern = r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9.]+'
print(re.findall(pattern, text))

['example123@gmail.com', 'test.user@mail.co.kr']

 

ㅈㅂ 1: 전화번호 유효성 검사

import re

# 테스트할 번호들
numbers = ['010-1234-5678', '02-123-4567', '010-123-456', '02-12-4567']

mobile_pattern   = r'\d{3}-\d{4}-\d{4}' # 휴대폰: 3자리-4자리-4자리
landline_pattern = r'\d{2,3}-\d{3,4}-\d{4}' # 일반전화: 2~3자리-3~4자리-4자리

for n in numbers:
    m = re.match(mobile_pattern, n) or re.match(landline_pattern, n)
    print(n, '→', '유효' if m else '무효')
    
010-1234-5678 → 유효
02-123-4567 → 유효
010-123-456 → 무효
02-12-4567 → 무효

 

ㅈㅂ 2: HTML 태그 제거

import re

html = '<h1>안녕하세요</h1><p>Python <b>정규표현식</b> 연습입니다.</p>'
print(re.findall(r'<[^>]+>', html))
print(re.sub(r'<[^>]+>', '', html))

['<h1>', '</h1>', '<p>', '<b>', '</b>', '</p>']
안녕하세요Python 정규표현식 연습입니다.

 

ㅈㅂ 3: 괄호 안에서 가져오기

import re

text = 'print(hello) max(1, 2) range(0, 10, 2) input(name)'
print(re.findall(r'\(([^)]+)\)', text))  # '\(   그룹([^)]+)   \)'
# findall은 그룹 ()이 없으면 → 매칭 전체 반환
# 그룹 ()이 있으면 → 그룹 안만 반환

['hello', '1, 2', '0, 10, 2', 'name']

 

ㅈㅂ 4: 숫자만 가져오기

import re

text = '사과 3개, 가격은 1500원이고 할인율은 12.5%입니다. 총 2봉지.'
result = re.findall(r'\d+\.?\d*|\d', text)
print(result)

['3', '1500', '12.5', '2']

 

ㅈㅂ 5: = 앞 변수명 가져오기

import re

text = "name = 'John'; _age=25; _student='kwon'"
print(re.findall(r"([a-zA-Z_]\w*)\s?=", text)) # key
print(re.findall(r"=\s?'?([a-zA-Z0-9]+)'?", text)) # value

['name', '_age', '_student']
['John', '25', 'kwon']

 

Q 3. 아래 HTML에서 <a> 태그 안의 링크 텍스트만 추출해라.

# Q 3. 아래 HTML에서 <a> 태그 안의 링크 텍스트만 추출해라.
import re

html = """
<a href="https://google.com">구글</a>
<a href="https://naver.com">네이버</a>
<a href="https://github.com">깃허브</a>
"""
pattern = r'<a\shref=[^>]+>([가-힣]+)</a>'
print(re.findall(pattern, html))

['구글', '네이버', '깃허브']

 

Q 4. 아래 HTML에서 href 안의 URL만 추출해라.

# Q 4. 아래 HTML에서 href 안의 URL만 추출해라.
import re

html = """
<a href="https://google.com">구글</a>
<a href="https://naver.com">네이버</a>
<a href="https://github.com">깃허브</a>
"""
pattern = r'<a\shref="([^"]+)">.+</a>'
print(re.findall(pattern, html))

['https://google.com', 'https://naver.com', 'https://github.com']

 

Q 5. 아래 HTML에서 <span class="price"> 안의 숫자만 추출해라.

# Q 5. 아래 HTML에서 <span class="price"> 안의 숫자만 추출해라.
import re

html = """
<span class="price">12,900</span>
<span class="title">상품명</span>
<span class="price">35,000</span>
<span class="price">8,500</span>
"""
pattern = r'<span\sclass="price">([0-9,]+)</span>'
print(re.findall(pattern, html))

['12,900', '35,000', '8,500']

 

Q 6. 아래 문자열에서 날짜만 추출해라. (형식: YYYY-MM-DD)

# Q 6. 아래 문자열에서 날짜만 추출해라. (형식: YYYY-MM-DD)

import re

text = """
주문일: 2024-01-15
배송일: 2024-02-03
취소일: 24-3-5
문의일: 2024-12-31
"""
pattern = r'[0-9]{4}-[0-9]{2}-[0-9]{2}'
print(re.findall(pattern, text))

['2024-01-15', '2024-02-03', '2024-12-31']

 

 Q 7. 아래 문자열에서 2024년 이후의 날짜만 추출해라. (형식: YYYY년 MM월 DD일)

# Q 7. 아래 문자열에서 2024년 이후의 날짜만 추출해라. (형식: YYYY년 MM월 DD일)

import re

text = """
2023년 11월 05일 주문
2024년 01월 15일 배송
2024년 12월 31일 완료
2025년 03월 22일 예약
"""
pattern = r'(?:202[4-9]|20[3-9][0-9])년\s[0-9]{2}월\s[0-9]{2}일'
print(re.findall(pattern, text))

['2024년 01월 15일', '2024년 12월 31일', '2025년 03월 22일']

 

Q 9. 아래 문자열에서 key=value 쌍을 딕셔너리로 만들어라.

# Q 9. 아래 문자열에서 key=value 쌍을 딕셔너리로 만들어라.

import re

text = "name=John; age=25; city=Seoul; job=engineer"

# 여기에 패턴 작성
pairs = re.findall(r'([a-zA-Z_][a-zA-Z_0-9]*)=([a-zA-Z0-9]+)', text)
result = {k: v for k, v in pairs}
print(result)

{'name': 'John', 'age': '25', 'city': 'Seoul', 'job': 'engineer'}

 

Q 10. 아래 로그에서 ERROR 레벨의 메시지만 추출해라.

# Q 10. 아래 로그에서 ERROR 레벨의 메시지만 추출해라.

import re

log = """
[INFO] 2024-01-15 08:00:01 서버 시작
[ERROR] 2024-01-15 08:05:23 DB 연결 실패
[WARNING] 2024-01-15 08:10:11 메모리 부족
[ERROR] 2024-01-15 08:15:44 타임아웃 발생
[INFO] 2024-01-15 08:20:00 재시작 완료
"""

# 여기에 패턴 작성
result = re.findall(r'\[ERROR\]\s\S+\s\S+\s([^\n]+)', log)
print(result)

['DB 연결 실패', '타임아웃 발생']
 
728x90