Yeonnnnny

정규 표현식 part3 본문

Data Analysis

정규 표현식 part3

yeonny_do 2023. 11. 5. 23:53

■ 파이썬에서 정규 표현식 사용

 

 - re 모듈 사용

    re 모듈의 compile 함수를 이용하여 정규 표현식을 컴파일함

 


 import re
 p = re.compile('[a-z]+')
 

 

 

 - 정규식을 사용한 문자열 검색

   컴파일 된 객체를 아래의 함수를 이용하여 문자열 검색을 할 수 있음

 

 

match() 문자열의 처음 시작부터 검색하여 일치하지 않는 부분이 나올 때까지 찾음
search() 문자열 전체를 검색하여 처음으로 매치되는 문자열 찾음
findall() 정규식과 매치괴는 모든 문자열을 찾아 리스트로 반환
finditer() 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 반환 

 

컴파일과 매치 동시 수행
m = re.match( '[a-z]+' , 'python' )

 


□ match()

 

 
import re

p =re.compile('[a-z]+') # 알파벳 소문자가 1개 이상인지

m1= p.match('python')
print(m1)  # <re.Match object; span=(0, 6), match='python'>

m2 = p.match('pYthon')
print(m2)  # <re.Match object; span=(0, 1), match='p'>

m3 = p.match('Python')
print(m3)  # None

m4 = p.match('3 python')
print(m4)  # None
 

 

  ※ 매치하고자 하는 문자열과 패턴을 처음부터 비교

 

 


□ search()

 


import re

p =re.compile('[a-z]+') # 알파벳 소문자가 1개 이상인지

m=p.search('3 python')
print(m)         # <re.Match object; span=(2, 8), match='python'>
print(m.group()) #
 

  

 ※ 처음부터 매치되지 않아도 문자열 전체를 검색하여 매치되는 문자열이 있으면, 그 결과를 반환함

 

 

  ○ [문제] 전화번호 추출하기 : 다음의 전화번호 데이터에서 전화번호만 추출하는 정규표현식 작성

 

 
import re

phone =['홍길동:010-1234-5678','우리집:02-5555-3333']

# 1)

p=re.compile('\d{2,3}-\d{3,4}-\d{4}')

for i in phone:
    print(p.search(i).group())
 

 

   # 결과

010-1234-5678
02-5555-3333

 


□ findall()

 
import re

pattern = re.compile('[a-z]+')
result=pattern.findall('life is too short !')
print(result)
 

  

 

# 결과
['life', 'is', 'too', 'short']

 


□ finditer()

 

 
import re

pattern = re.compile('[a-z]+')
result=pattern.finditer('life is too short !')
for i in result:
    print(i)
 

 

 

# 결과
<re.Match object; span=(0, 4), match='life'>
<re.Match object; span=(5, 7), match='is'>
<re.Match object; span=(8, 11), match='too'>
<re.Match object; span=(12, 17), match='short'>

 

 


 

■ Match 객체 함수

 

group() 매치된 문자열을 반환
start() 매치된 문자열의 시작위치 반환
end() 매치된 문자열의 끝 위치 반환
span() 매치된 문자열의 시작, 끝 값을 튜플로 반

 


import re

p=re.compile('[a-z]+')
m=p.search('python')

print(m.group())
print(m.start())
print(m.end())
print(m.span())
 

 

# 결과
python
0
6
(0, 6)

 

 


 

■ 컴파일 옵션

 

옵션 약어 설명
DOTALL  S dot(.) 메타 문자가 줄바꿈 문자를 포함하여 모든 문자와 일치하도록 함
IGNORECASE I 대소문자에 관계엾이 일치
MULTILINE M 여러 줄의 문자열에 대해  ^, $ 메타문자를 적용할 수 있음
※ ^ 는 문자열의 처음을, $ 는 문자열의 마지막을 의미
VERBOSE X 정규식을 보기 편하게 만들고 주석 등을 사용할 수 있게 함

 

 

   □ DOTALL

 
# DOTALL
import re

m = re.match('a.b', 'a\nb')
print(m)  # None : dot에는 \n(개행문자) 포함 X

p = re.compile('a.b',re.DOTALL)
m = p.match('a\nb')
print(m)   # <re.Match object; span=(0, 3), match='a\nb'> : 매치됨
 

 

 
s = '''hello
python'''

# 사용X
p = re.compile('hello.python')
m = p.match(s)
print(m) # None

# 사용O
p = re.compile('hello.python',re.DOTALL)
m = p.match(s)
print(m) # 매치 성공
 

 

 

   □ IGNORECASE

 
# IGNORECASE

# 사용 X
p = re.match('[a-z]+','pYthon')
print(p)

# 사용 O
p = re.compile('[a-z]+',re.IGNORECASE)
m = p.match('pYthon')
print(m) # 매치 성공
 

 

 

   □ MULTILINE

 
# MULTILINE

# 사용 X
p = re.compile('^python\s\w+')

text = '''python one
life is too short
python two
you need python
python three
'''

m = p.findall(text)
print(m) # ['python one']


# 사용 O
p = re.compile('^python\s\w+', re.MULTILINE)
m = p.findall(text)
print(m) # ['python one', 'python two', 'python three']


 

 
 # ^를 쓰지 않고, MULTILINE 사용하지 않은 경우
p=re.compile('python\s\w+')

text = '''python one
life is too short
python two
you need python
python three
'''

m=p.findall(text)
print(m)  #['python one', 'python two', 'python\npython']
 

 

 

 


 

■ 백슬래시

 

[가정]  "\section" 문자열을 찾기 위한 정규식을 만들어야 하는 상황
re.compile("\section") 이 정규식은 \s문자가 공백으로 해석되어 의도한 대로 매치가 이뤄지지 않음.
왼쪽의 정규식은 [\t\n\r\f\v]ection 과 같은 의미로 해석됨. 결국 정규식을 사용한 \ 문자가 문자열 자체임을 알려주기 위해 \를 2개 사용하여 이스케이프를 처리해야 함.
>>> re.compile("\\section")
하지만, 파이썬 정규식 엔진에는 파이썬 문자열 리터럴 규칙에 따라 \\ 이 \로 변경되어 결국 \section이 전달됨.
결국 파이썬 정규식 엔진에 \\문자를 전달하기 위해서는 백슬래시 4개 \\\\를 사용해야 함.

 

이러한 문제로 인해 파이썬 정규식에는 Raw String 규칙이 생기게 됨. 따라서 자음과 같이 정규식 문자열 앞에 r문자를 삽입하면 Raw String 규칙에 의해 \ 1개만 써도 동일한 의미를 갖게 됨. >>> re.compile(r'\\section')

 

 
# 백슬래시 문제

import re

p= re.compile('\\section')  #\section 으로 해석됨->[\t\r\n\v\f]ection 문자열과 일치한것을 찾음
m=p.search("What is \section and example?")
print(m) # None

m1=p.search("What is ection and example?")
print(m1) # <re.Match object; span=(7, 14), match=' ection'>

p =re.compile(r'\\section')
m=p.search("What is \section and example?")
print(m) # <re.Match object; span=(8, 16), match='\\section'>
 

 

 


 

■ 메타 문자

 

|  or 의미
^ 문자열의 처음과 일치함 의미
$ 문자열의 끝과 일치함 의미
\A 문자열의 처음과 일치함 의미. ^은 MULTILINE 옵션을 사용할 경우 각 줄의 문자열의 처음과 매치되지만 \A는 줄과 상관 없이 전체 문자열의 처음하고만 일치됨.
\Z 문자열의 끝과 일치함을 의미. 여러 줄로 작성된 문자열에서 중과 상관 엇ㅂ이 전체 문자열의 끝과 일치됨.
\b 단어의 경계를 나타냄. 단어 경계는 단어 문자와 비단어 문자 사이의 위치를 의미함. 여기서 단어 문자는 보통 알파벳, 숫자, 밑줄 문자를 포함하며, 비단어 문자는 그 외의 문자임. \b는 백스페이스를 의미하므로 단어 구분자로 사용되기 위해서는 Raw String 임을 알려주는 r을 반드시 붙여줘야 함.
\B \b와 반대로 단어 앞뒤가 공백으로 구분된 단어가 아닌 경우에만 일치됨.

 

 

   □ |

 
# |

import re
p=re.compile('Crow|Servo')
m=p.match('ServoHello')
print(m)  #<re.Match object; span=(0, 5), match='Servo'>
 

 

 

   □ $

 
# $

print(re.search('short$','life is too short'))
print(re.search('short$','life is too short, you need python'))
#<re.Match object; span=(12, 17), match='short'>
#None
 

 

 

   □ \A

 
# \A
# MULTILINE 옵션 안먹힘 !!!
# MULTILINE 옵션 적용하지 않은 ^와 동일한 의미

p = re.compile('\Apython\s\w+',re.MULTILINE)

text='''python one
life is too short
python two
you need python
python three'''

m=p.findall(text)
print(m)
 

 

 

   □ \b

 
 
# \b
# 단어의 앞뒤가 공백으로 구분되어 있는지를 검사, 즉 단어가 독립적으로 있는지 확인
# \s는 공백을 포함하는지 검사

p=re.compile(r'\bclass\b')
print(p.search('no class at all'))
print(p.search('class at all'))
print(p.search('one subclass is'))
print(p.search('class '))

# 결과
# <re.Match object; span=(3, 8), match='class'>
# <re.Match object; span=(0, 5), match='class'>
# None
# <re.Match object; span=(0, 5), match='class'>
 

 

 
 # \s의 경우

p=re.compile(r'\sclass\s')
print(p.search('no class at all'))
print(p.search('class at all'))
print(p.search('one subclass is'))
print(p.search('class '))

# 결과
# <re.Match object; span=(2, 9), match=' class '>
# None
# None
# None


 

 

   □ \

 
 # \
# 문자열 안에 포함된 메타문자(. ? $ 등)을 원재 문자 자체로 사용

m = re.search('안녕하세요?', '여러분 안녕하세요?')
print(m)  #<re.Match object; span=(4, 9), match='안녕하세요'>
m = re.search('안녕하세요\?', '여러분 안녕하세요?')
print(m)  #<re.Match object; span=(4, 10), match='안녕하세요?'>
 

 

 


 

■ 그룹핑

 

   - () 메타문자는 그룹을 만듦

   - 그룹을 만들면 group()함수를 사용하여 그룹핑된 부분의 문자열만 뽑아 낼 수 있음

   - '\번호'를 이용하면 번호에 해당하는 그웁을 재참조함. 표현식은 Raw String으로 선언해야 함

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

 

group(0) 일치된 전체 문자열, group()와 동일
group(1) 첫 번째 그룹에 해당하는 문자열
group(n) n 번째 그룹에 해당하는 문자열

 

 

 
import re

p =re.compile('(ABC)+')
m =p.search('ABCABCABC OK?')
print(m)
print(m.group(0)) # or group(0) :패턴과 일치된 전체 문자열 반환
print(m.group(1)) # 첫 번째 그룹에 해당하는 문자열만 반환
#print(m.group(2)) # 에러 : group(n) n은 패턴에서 지정한 그룹의 수를 의미함.
# ABC가 3번 반복되었다고 해서 그룹이 3개가 생기는 것이 아님.

# 결과
# <re.Match object; span=(0, 9), match='ABCABCABC'>
# ABCABCABC
# ABC
 

 

 
 # 이름과 전화번호를 그룹으로 지정하여 분리
s='park 010-1234-5678'
p=re.compile('(\w+)\s+(\d+-\d+-\d+)')
m=p.search(s)

print(m.group())   # park 010-1234-5678
print(m.group(1))  # park
print(m.group(2))  # 010-1234-5678
 

 

 
# 국번만 추출하고자 할 때
# 그룹이 중첩되어 있는 경우는 바깥쪽부터 시작하여 안쪽으로 들어갈 수록 인덱스 증가함

s='park 010-1234-5678'
s1='kim 02-356-7890'

p=re.compile('(\w+)\s+((\d{2,3})-\d{3,4}-\d{4})')
m=p.search(s)
m1=p.search(s1)

print(m.group(3))   # 010
print(m1.group(3))  # 02
 

 

 
# '\번호'를 이용한 그룹 재참조
# ** Raw String 이용해야 함
import re
re.match(r'(a)(b)\1\2','abab') # r'(a)(b)\1\2' == abab
 

 

 
# 동일한 단어가 연속적으로 사용된 문자열 찾기

# \b(\w+)\b : 단어 경계를 기준으로 문자와 숫자로 이루어진 한 단어를 찾음. 그리고 그 단어를 그룹으로 지정
# \s+ : 하나 이상의 공백문자를 지정
# \b\1\b : 단어 경계를 기준으로 한 단어를 찾으며, 그룹 참조를 통해서 이전에 그룹화된 단어와 동일한 단어를 찾음

p = re.compile(r'\b(\w+)\b\s+\b\1\b')
m =p.search('I have a dog dog in my house')
print(m.group())   #dog dog
print(m.group(1))  #dog
 

 


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

p = re.compile('(?P<name>\w+)\s+(?P<phone>\d+-\d+-\d+)')
m = p.search('park 010-1234-5678')
print(m.group())
print(m.group('name'))
print(m.group(1))
print(m.group('phone'))
print(m.group(2))

# 결과
# park 010-1234-5678
# park
# park
# 010-1234-5678
# 010-1234-5678


 

 


 

■ 전방 탐색

 

표현식1(?=표현식2) 긍정 전방 탐색 표현식 1 뒤의 문자열이 표현식 2와 매치되면 표현식 1 일치. 표현식 2의 문자열은 결과로 반환하지 않음
표현식1(?! 표현식2) 부정 전방 탐색 표현식 1 뒤의 문자열이 표현식 2와 매치되지 않으면 표현식 1 일치. 표현식 2의 문자열은 결과로 반환하지 않음.

 

 
# 긍정 전방 탐색

import re

# url에서 프로토콜의 이름만 가져오기
p = re.compile('.+(?=:)')
m=p.search('http://naver.com')
print(m.group()) # http
 

 

 
# 부정 전방 탐색
# 파일 이름의 확장자 중 bat파일만 제외하고 추출
file_name = ['autoexe.bat','python.exe','sysinfo.cf']

p =re.compile('[a-zA-Z]+\w*[.](?!bat)') # 확장자 앞의 파일의 이름만 가져와서 리스트에 존재하는 파일명 가져옴
for f in file_name:
    if p.search(f):
        print(f)

# 결과
# python.exe
# sysinfo.cf


print()

p1= re.compile('[a-zA-Z]+\w*[.](?!bat)[a-zA-Z]+')
for f in file_name:
    if p1.search(f):
        print(f)

# 결과
# python.exe
# sysinfo.cf
 

 

 

 


 

■ 후방 탐색

: 텍스트를 반환하기 전에 뒤쪽을 탐색하는 것

 

(?<=표현식2) 표현식1 긍정 후방 탐색 표현식1 앞의 문자열이 표현식 2와 매치되면 표현식1 일치. 표현식2의 문자열은 결과로 반환하지 않음 
(?<!표현식2) 표현식1 부정 후방 탐색 표현식 1 앞의 문자열이 표현식 2와 매치되지 않으면 표현식1 일치. 표현식 2의 문자열은 결과로 반환하지 않음

 

 
# 긍정 후방 탐색
p=re.compile('(?<=\$)\d+[.]\d+') # 통화기호가 $인 경우 금액(소수점 존재하는)만 뽑는 패턴
p1=re.compile('(?<=\$)[0-9]+[.]?[0-9]*') # 소수점이하가 없는 경우도 적용가능한 패턴

m=p.search('ABC01: $23.1')
m1=p1.search('ABC01: $23')
print(m.group())  #23.1
print(m1.group()) #23
 

 


 

■ 전후방 동시 탐색

 

 
# 전후방 동시 탐색
# <p>~ </p> 태그 사이의 문자열 추출하기
# 뒤에서 탐색했을 시 <p>까지 일치가 되어야 하고, 앞에서 탐색했을 시에는 </p>까지 일치되어야 함

p=re.compile('(?<=<p>)\w+(?=</p>)')

print(p.search('kakao <p>ryan</p> keep a straight face').group()) #ryan
print(p.search('<p>HelloRyan</p>').group()) #HelloRyan


 

 

 


 

■ 문자열 바꾸기

 

 
# pattern객체.sub(바꿀 문자열, 대상 문자열, 바꿀 횟수)

p= re.compile('blue|white|red')
p.sub('color','blue socks and red shoes',count=0) # count=0 : 매치되는 문자열 전체 다 바꿈
# > 'color socks and color shoes'
p.sub('color','blue socks and red shoes',count=1)
# > 'color socks and red shoes'
p.sub('color','blue socks and red shoes',count=2)
# > 'color socks and color shoes'
 

 

 

 

 


 

[ 문제 1 ] 이메일 형식 검증   

: 사용자로부터 입력받은 임의의 이메일 주소에 대해 유효한 형식인지를 검증하는 함수 작성   

def valid_email(email):
        return '이메일 검증 결과'   

일반적인 이메일 형식 : 알파벳 및 숫자 @도메인이름    

위 함수는 다음의 이메일을 올바른 이메일 형식으로 결과 값을 반환한다.   
- mimke@korea.co.kr   
- mike@daum.net   
- mike.kim@gmail.com   

위 함수는 다음의 이메일을 올바른 이메일 형식이 아님을 결과 값으로 반환한다   
- mysite.com   
- mike@good

 

 
import re

def valid_email(email):
    p = re.compile('[a-zA-Z]\w*[_]*\w*[@]\w+[.]\w+([.]\w+)*')
    #p = re.compile('[a-zA-Z]+\w*[.]?\w*[@]\w+[.]\w+[.]?\w{2,3}')
    if p.match(email):
        return True
    else:
        return False


email = input('이메일 입력 : ')
if valid_email(email):
    print('유효한 이메일 형식입니다.')
else:
    print('유효하지 않은 이메일입니다.')
 

 


 

[ 문제 2 ]  한글 찾기, 한글 제거

 

s = 'i am 신뢰에요. 알겠죠? 그런 식이면 Just 멈춰줘요'

m1 = re.findall('[ㄱ-힡]+',s) # 한글만 찾기
m = re.findall('[a-zA-Z]+',s) # 한글만 제거
print(m1) # ['신뢰에요', '알겠죠', '그런', '식이면', '멈춰줘요']
print(m) # ['i', 'am', 'Just']
 

 


[ 문제 3 ]  주민번호의 유효성 검증   

: 사용자로부터 입력 받은 임의의 주민등록번호가 올바른 형식인지 검증하는 정규표현식 작성   
: 주민번호 형식 : 생년월일(6자리)-성별(1자리)나머지 6자리 숫자   

- 년도 : 숫자 두 자리   
- 월 : 앞자리가 0일 때 뒷 자리는 1~9까지 허용, 1일 때는 0~2까지만 허용   
- 일 : 앞자리가 0일 때 뒷 자리는 1~9까지, 앞자리가 1또는 2일 때 뒷자리는 0~9까지 허용,
        앞자리 3일 때는 0과 1만 허용   
- 성별 숫자 : 1~4   
- 나머지 6자리 :  숫자인지만 체크

 

 
 # 1)

import re
def valid_id (id):
    pattern = re.compile('[0-9]{2}(0[0][1-9]|1[0-2])(0[1-9]|(1|2)\d|3[0-1])-[1-4]\d{6}')
    if pattern.match(id):
        return True
    else:
        return False

id = input('주민번호 입력 : ')

if valid_id(id):
    print(f'{id[:7]}-{"*"*7}은 유효한 주민번호 형식입니다.')
else:
    print(f'{id[:7]}-{"*"*7}은 유효한 주민번호 형식이 아닙니다.')
 

 

 

 
 # 2) 문자열 대체 방식 사용

import re
def valid_id (id):
    pattern = re.compile('([0-9]{2}(0[1-9]|1[0-2])(0[1-9]|(1|2)\d|3[0-1]))-[1-4]\d{6}')
    if pattern.match(id):
        print(pattern.sub(r'\1-*******',id),'은 유효한 주민 번호 형식입니다.',sep='')
    else:
        print(pattern.sub(r'\1-*******',id),'은 유효한 주민 번호 형식이 아닙니다.',sep='')
   
       

id = input('주민번호 입력 : ')
valid_id(id)
 

 

 


Reference : https://sesoc.tistory.com/252 

'Data Analysis' 카테고리의 다른 글

BeautifulSoup 모듈  (0) 2023.11.07
Selenium 모듈  (0) 2023.11.06
정규 표현식 part2  (1) 2023.11.01
정규 표현식 part1  (0) 2023.11.01
[Web Scrapping] requests 모듈  (0) 2023.11.01