정규표현식 (Regular Expressions) - 2
정규식은 검색할 때 특정한 기호나 다양한 옵션을 사용해서 검색하는 방법입니다.
python뿐만 아니라 대부분의 프로그래밍언어와 유닉스, 유닉스 같은 OS와 SQL에서도 많이 사용됩니다.
1. 주요 정규식 기호와 의미
sytax | description |
---|---|
. | 임의의 한 문자가 존재 |
? | 바로 앞의 문자가 존재하거나 존재하지 않음 |
* | 바로 앞의 문자가 존재하지 않거나 무한대로 존재 |
+ | 바로 앞의 문자가 한번 이상 존재 |
^ | 바로 뒤의 문자로 문자열이 시작 |
$ | 바로 앞의 문자로 문자열이 끝남 |
{숫자} | 숫자만큼 반복 |
{숫자,} | 숫자 이상만큼 반복 |
{숫자1,숫자2} | 숫자1 이상. 숫자2 이하 만큼 반복 |
(문자열) | 문자나 문자열을 묶음 |
[문자1,문자2…] | 대괄호 안에 있는 문자들이 존재하는지 검색 |
[^ ] | ^ 기호 바로 뒤에 문자가 존재하지 않음 |
[:alpha:] | 알파벳만 검색 |
[:alnum:] | 알파벳, 숫자만 검색 |
[:digit:] | 숫자만 검색 |
[:upper:] | 대문자만 검색 |
\ | (역슬래쉬) 글자 자체를 검색 |
\d | 모든 숫자를 검색. [0-9]와 동일 |
\D | 숫자를 제외한 모든 문자를 검색 |
\s | 공백을 검색 |
\S | 곰색이 아닌 문자를 검색 |
\w | 숫자 또는 문자를 검색 [a-zA-Z0-9] |
\W | 숫자 또는 문자가 아닌 것을 검색 |
정규식을 사용하기 위해 re
라는 모듈을 사용합니다.
re모듈 사용방법 - 1
>>> import re
>>> r = re.compile("[ab]")
>>> print(r.search("pizza"))
<_sre.SRE_Match object; span=(4, 5), match='a'>
>>> print(r.match("pizza"))
None
1: re 모듈 불러오기
2: 찾고 싶은 글자를 지정함. 여기서는 a나 b를 찾으라고 지정함.
[ ] 기호는 그 안에 들어 있는 글자들 중 하나라는 뜻
3: r 객체에서 search함수를 실행해서 해당 글자가 포함된 부분을 찾음.
정규식에 해당되는 부분이 있을 경우 Match object 출력
4: r 객체에서 match함수를 실행해서 해당 글자와 동일한 단어를 찾음.
정규식에 해당되는 단어가 없기 때문에 None을 출력
search()
함수는 해당 패턴이 하나라도 나오면 그 결과를 출력하는데 match()
함수는 정확한 글자만 출력합니다.
re.compile
은 찾는 글자나 패턴을 Python에게 전해주는 역할을 하는데, 어떤 글자나 패턴을 찾으라고 적어주며 Python은 입력한 글자나 패턴을 Python이 아는 글자로 인코딩합니다.
그리고 그 결과로 해당 글자를 찾게 되는데, 지금은 찾는게 몇 개 안되기 때문에 상관없지만, 찾는 글자나 패턴이 자주 발생하는 경우는 매번 파싱(Python이 아는 글자로 변환하는 작업)을 하기에 힘들어집니다.
그래서 아예 Python 안에 변환을 해서 저장을 해 놓고 사용을 하게 만들면 훨씬 더 편하고 속도 문제도 좋아지기 때문에 compile()
함수를 사용해서 변환을 하고 저장을 해서 사용을 하게 됩니다.
>>> r = re.compile("[pP]")
>>> print(r.search("apple"))
<_sre.SRE_Match object; span=(1, 2), match='p'>
>>> print(r.match("apple"))
None
>>> print(r.match("apPle"))
None
>>> print(r.match("pP"))
<_sre.SRE_Match object; span=(0, 1), match='p'>
1행에서 찾고 싶은 글자인 [pP]
(소문자p 나 대문자P)를 등록했습니다.
2행에서 소문자 apple에서 해당 패턴을 찾으라고 했더니 p와 매치된다고 검색했습니다.
3행에서 match()
함수를 써서 검색을 하니 완전히 일치하지 않아서 None가 나오고
4행에서도 match()
함수를 써서 검색을 하니 None가 나왔습니다.
5행에서는 정확히 찾는 글자만 있어도 match되었다고 결과가 나왔습니다.
이것이 search()
와 match()
함수의 차이입니다.
re모듈 사용방법 - 2
>>> re.search("[pP]","apPle")
<_sre.SRE_Match object; span=(1, 2), match='p'>
>>> re.match("[pP]","pP")
<_sre.SRE_Match object; span=(0, 1), match='p'>
re.search()
함수나 re.match()
와 같은 함수를 사용했는데 re
모듈에는 정규식을 우용하게 사용할 수 있도록 Python에서 만들어서 제공하는 다양한 함수들이 있습니다.
이 함수들을 잘 사용해야 정규식을 요긴하게 사용할 수가 있습니가.
key function name | description |
---|---|
compile (pattern[,flags]) |
주어진 pattern을 컴파일하여 정규식 객체를 반환 |
match (pattern, string[,flags]) |
주어진 string의 시작부분부터 해당 pattern이 존재하는지 검사하여 Match되는 object 반환 |
search (pattern, string[,flags]) |
주어진 string 전체가 주어진 pattern과 동일한지 검사하여 Match될 경우 결과를 반환 |
split (pattern, string[,maxplit=0]) |
주어진 pattern을 구분자로 기준 삼아 string을 분리하여 결과 값을 리스트로 반환 |
findall (pattern, string[,flags]) |
주어진 string에서 해당 pattern을 만족하는 문자열을 찾아서 리스트로 반환 |
sub (pattern, repl, string[,count=0]) |
주어진 string에서 pattern과 일치하는 부분을 주어진 repl 문자로 교체하여 결과 문자열을 반환 |
subn (pattern, repl, string[,count=0]) |
sub와 동일하나, 결과로(결과문자열, 매칭횟수)를 튜플로 반환 |
escape (string) |
영문자나 숫자를 제외한 문자들을 백슬래쉬로 지정해서 리턴 (임의의 문자열을 정규식 패턴으로 사용할 경우 유용) |
위 함수들을 사용해서 정규식들을 활용하면 아주 막강한 검색부터 데이터를 추출해 내는 기능을 구현 할 수 있습니다.
정규식을 활용하는 방법은 제법 어렵기 때문에 연습과 활용이 많이 필요합니다.
2. 정규식 기초 문법 사용 설명
(1) .
기호 - 임의의 한 문자를 의미합니다.
>>> r = re.compile("a.c")
>>> r.search("abc") # case 1
<_sre.SRE_Match object; span=(0, 3), match='abc'>
>>> r.search("afc") # case 2
<_sre.SRE_Match object; span=(0, 3), match='afc'>
>>> r.search("ac") # case 3
>>> r.search("asdfgc") # case 4
>>>
case 1에서는 ‘a’와 ‘c’사이에 ‘b’라는 한 문자가 존재하므로 조건에 적합하고 case도 마찬가지 입니다.
그런데 case 3 에서는 ‘a’와 ‘c’사이에 문자가 존재하지 않기 때문에 조건에 해당되지 않아서 결과가 아무것도 나오지 않습니다. case 4에는 한 문자가 넘기 때문에 조건에 해당되지 않습니다.
(2) ?
기호 - 바로 앞의 문자가 존재하거나 존재하지 않음(갯수 제한 있음)
>>> r = re.compile("kd?k")
>>> r.search("kk") # case 1
<_sre.SRE_Match object; span=(0, 2), match='kk'>
>>> r.search("kdk") # case 2
<_sre.SRE_Match object; span=(0, 2), match='kdk'>
>>> r.search("kddk") # case 3
>>> r.search("kdd") # case 4
>>> r.search("dddk") # case 5
?
기호는 ? 앞의 글자는 있어도 되고 없어도 되는데 ? 뒤의 글자는 반드시 있어야 할 경우 사용됩니다.(c, k?, w 으로 구분이 되는 것임)
case 1은 ? 앞의 글자가 없어도 출력이 됩니다.
그런데 case 3을 보면 ? 앞의 k가 2개가 나오니까 매칭이 안됩니다. 즉 ? 기호는 ? 앞의 글자가 0개 또는 1개일 경우에만 매칭이 되고 나머지 경우는 안됩니다. 이와 다르게 글자 개수가 제한이 없는 것이 * (아스타리크) 기호 입니다.
(3) *
기호 - 바로 앞의 문자가 존재하지 않거나 개수와 상관없이 존재
>>> r = re.compile("kd*k")
>>> r.search("kk") # case 1
<_sre.SRE_Match object; span=(0, 2), match='kk'>
>>> r.search("kdk") # case 2
<_sre.SRE_Match object; span=(0, 3), match='kdk'>
>>> r.search("kddk") # case 3
<_sre.SRE_Match object; span=(0, 4), match='kddk'>
>>> r.search("cdd")
>>> r.search("dddk")
앞의 ?와 동일한 예제에서 첫 번째 줄의 기호가 ?
대신 *
으로 바꼈습니다.
case 3을 보면 ? 기호일 때는 매치가 안 되었던 것이 *
으로 바뀐 후 잘됩니다.
(4) +
기호 - 바로 앞의 문자가 한번 이상 존재
>>> r = re.compile("kd+k")
>>> r.search("kdk") # case 1
<_sre.SRE_Match object; span=(0, 3), match='kdk'>
>>> r.search("kddddk") # case 2
<_sre.SRE_Match object; span=(0, 7), match='kddddk'>
>>> r.search("kddddd") # case 3
>>> r.search("kk")
+
기호 앞의 글자가 1회 이상 나오는 모든 경우를 찾으라는 뜻입니다. 그래서 case1,2 모두 매치 된다고 나옵니다.
그리고 case 3,4 는 각각 k와 d가 누락되어 결과가 나오지 않습니다.
(5) ^
기호 - 시작되는 문자를 지정함
>>> r = re.compile("^k")
>>> r.search("kdk") # case 1
<_sre.SRE_Match object; span=(0, 1), match='k'>
>>> r.search("sjs") # case 2
^
(캐럿) 문자는 시작되는 글자를 지정하는 정규식의 표현법입니다. 앞의 코드에서 ^k
는 소문자 k로 시작하는 것을 다 찾으라는 의미입니다.
(6) $
기호 - 끝나는 문자를 지정함
>>> r = re.compile("e$")
>>> r.search("apple") # case 1
<_sre.SRE_Match object; span=(4, 5), match='e'>
>>> r.search("banana") # case 2
$
기호는 끝나는 문자를 지정할 때 사용됩니다. e$
는 끝나는 글자가 소문자 e
인 경우를 찾으라는 의미입니다.
(7) [문자1, 문자2 ...]
기호 - 대괄호 안에 있는 문자들이 존재하는지 검색
>>> r = re.compile("[abcd]")
>>> r.search("pizza") # case 1
<_sre.SRE_Match object; span=(4, 5), match='a'>
>>> r.search("bread") # case 2
<_sre.SRE_Match object; span=(0, 1), match='b'>
>>> r.search("mashroom") # case 3
<_sre.SRE_Match object; span=(1, 2), match='a'>
>>> r.search("bckja")
<_sre.SRE_Match object; span=(0, 1), match='b'>
위의 코드는 a, b, c, d가 들어있는 것들을 골라내라는 의미입니다.
그래서 case1, 3에는 ‘a’, case2에는 ‘b’가 있어서 매치로 나옵니다.
search()
함수는 결과에 맞는 첫 번째 값을 출력합니다.
(8) [^문자1, 문자2 ...]
기호 - ^
기호 뒤에 문자들을 제외한 모든 문자를 검색
>>> re.search("[^ap]","apple") # case1
<_sre.SRE_Match object; span=(3, 4), match='l'>
>>> re.search("[^ap]","bread") # case2
<_sre.SRE_Match object; span=(0, 1), match='b'>
>>> re.search("[^ap]","orange") # case3
<_sre.SRE_Match object; span=(0, 1), match='o'>
>>> re.search("[^ap]","kapj") # case4
<_sre.SRE_Match object; span=(0, 1), match='k'>
case1을 보면 apple에서 ap를 제외한 글자를 찾으라고해서 match = ‘l’이 나왔습니다. 여기서 중요한 것은 a나 p를 제외한 문자들을 출력하라고 했는데 apple에서 a와 p가 연속적으로 나와서 둘다 제외하고 그 다음으로 먼저 나오는 ‘l’이 출력이 된 것입니다.
검색할 문자열에 a,p가 있으면 a나 p가 있는 문자 바로 뒤에 있는 글자(여러개일 경우 맨 처음것)만 출력이 되고, a,p 가 없다면 맨 앞 문자가 출력됩니다.
>>> re.search("[a-g]", "apple") # case1
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search("[0-5]", "123678") # case2
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.search("[가-사]", "강원도에서") # case3
<_sre.SRE_Match object; span=(0, 1), match='강'>
구간을 줄 때 -
(하이픈)을 사용해서 범위를 지정합니다. 위와 같이 사용하면 연속적인 구간을 줄 수 있어서 찾고 싶은 데이터를 보다 쉽게 찾을 수 있습니다.
위에서 숫자를 등을 사용할 때 다음과 같은 기호를 사용하면 훨씬 더 편리하게 사용할 수 있습니다.
sign | reg | description |
---|---|---|
\s | [\t\n\r\f\v] | 공백과 각종 이스케이프 코드 |
\S | [^ \t\n\r\f\v] | 공백과 각종 이스케이프 코드를 제외한 모든 문자 |
\d | [0-9] | 모든 숫자 |
\D | [^0-9] | 숫자를 제외한 모든 문자 |
\w | [a-zA-Z0-9] | 모든 알파벳과 숫자 |
\W | [^a-zA-Z0-9] | 모든 알파벳과 숫자를 제외한 문자 |
3. Match object 방법 활용하기
>>> re.search("\d+","햄버거가 무려 7000 원이나 하다니!!")
<_sre.SRE_Match object; span=(8, 12), match='7000'>
위의 정규식 내용은 주어진 문장에서 숫자(“\d+”)를 모두 찾으라는 의미입니다. 뒤에 +
를 안쓰면 아래처럼 7만 나오니 주의해야합니다.
>>> re.search("\d","햄버거가 무려 7000 원이나 하다니! "))
<_sre.SRE_Match object; span=(8, 9), match='7'>
span을 보면 정규식의 조건에 부합하는 문자열의 시작과 끝을 알 수 있습니다. match를 보면 정규식에 부합한 분자열을 알 수 있습니다. match object를 변수에 넣어주면 이 값들을 직접 리턴해 주는 함수를 사용할 수 있습니다.
>>> result = re.search("\d+","햄버거가 무려 7000원이나 하다니!!")
>>> result.start()
8
>>> result.end()
12
>>> result.span()
(8, 12)
>>> result.group()
'7000'
위 코드를 보면 start()
, end()
, span()
, group()
함수가 있는데 각 함수가 수행결과를 보면 어떤 역할을 하는지 알 수 있습니다. 특히 group()
함수는 정규식으로 찾은 결과를 출력하는 함수로 아주 많이 사용됩니다.
4. 정규식에서 사용하는 다양한 함수들
(1) search()
함수 - 문자열 전체에서 정규식에 부합하는지 문자열이 있는지 검색
>>> re.search("\d+", "마르멜로는 2015년에 데뷔했습니다")
<_sre.SRE_Match object; span=(6, 10), match='2015'>
(2) match()
함수 - 문자열의 처음이 정규식과 부합하는지 검색
>>> re.match("\d+", "마르멜로는 2015년에 데뷔했습니다")
>>> re.match("\d+", "2015년에 마르멜로는 데뷔했습니다")
<_sre.SRE_Match object; span=(0, 4), match='2015'>
(3) findall()
함수 - 정규식에 부합하는 모든 문자열을 리스트로 리턴
>>> re.findall("\d+", "마르멜로는 2017년 6월 7일에 정보를 수정하였습니다.")
['2017', '6', '7']
(4) split()
함수 - 주어진 문자열을 특정 패턴을 기준으로 분리함
>>> re.split('[:]+', 'Apple Orange : Grape Cherry')
['Apple Orange ', ' Grape Cherry']
>>> re.split('[: ]+', 'Apple Orange : Grape Cherry')
['Apple', 'Orange', 'Grape', 'Cherry']
(5) sub()
함수 - 주어진 패턴과 일치하는 문자를 변경함
>>> re.sub('-', '**', '851123-1234567')
'851123**1234567'
정규식에 관한 내용은 아무 많지만 크롤러를 만들 때 유용한 기능 위주로 살펴보았습니다.
다음 문제를 풀면서 내용을 더듬어 봅시다. (답을 구하는 방법은 여러 가지입니다.)
Q1. 괄호에 들어갈 적절한 정규식을 쓰세요.
>>> url = "https://kimdoky.github.io"
>>> re.split( )
['http:', '', 'kimdoky.github.io']
Q2. 괄호에 들어갈 적절한 정규식을 쓰세요.
>>> r = re.compile( )
>>> r2 = r.search("python is very fun!!!")
>>> r2
<_str.SRE_Match object; span=(18,21), match='!!!' >
Q3. 아래의 str1 변수에 숫자만 모두 골라내는 정규식을 쓰세요.
>>> str1 = "빵 5개, 우유 5팩, 사과 2개 주세요~"
>>> re.findall( )
['5', '5', '2']
답안.
re.split("[/]",url)
r = re.compile("!+")
orr = re.compile("[^\w\s]+")
re.findall("[0-9+]", str1)
orre.findall("\d+", str1)
[출처] 왕초보! 파이썬 배워 크롤러 DIY하다
Let me know what you think of this article in the comment section below!