티스토리 뷰

728x90
반응형

우선 크롤링 할 때 정규식을 알아야 할 필요가 있을까요? 네 있습니다. XPath 나 CSS Selector 로 모든 원하는 내용을 가져올 수 있다면 정말 좋겠지만, 웹사이트라는 게 정말 다양한 형태가 있을 수 있고, 내가 원하는 부분이 하필이면 보기 편한 형태로 있지 않을 수 있습니다. 그럴 경우에는 정규식을 사용하여 가져와야할 수도 있겠죠. 그래서 정규식을 알아두면 웹 스크래퍼나 웹 크롤러를 만들 때 용이합니다. 특히 제 경험상 <script> 태그 안의 내용물을 가져올 때 정규식을 사용하게 되었던 것 같습니다.

import re # regular expression

pattern = re.compile("ca.e")
# . (ca.e) : 하나의 문자를 의미 > care, cafe, case (O) | caffe (X)
# ^ (^de) : 문자열의 시작 > desk, destination (O) | fade (X)
# $ (se$) : 문자열의 끝 > case, base (O) | face (X)

def print_match(m):
	if m:
		print(m.group())
		print(m.string) # 입력받은 문자열 그대로
		print(m.start()) # 일치하는 문자열의 시작 index
		print(m.end()) # 일치하는 문자열의 끝 index
		print(m.span()) # 일치하는 문자열의 시작 / 끝 index
	else:
		print("매칭되지 않음")

m = p.match("careless") # 주어진 문자열의 처음부터 일치하는지 확인
print_match(m) # care

m = p.search("good care") # 주어진 문자열 중에 일치하는게 있는지 확인
print_match(m)

lst = p.findall("careless good care") # 일치하는 모든 것을 리스트 형태로 반환
print(lst) # ['careless', 'care']

Python에서 정규식을 사용하기 위해서는 re 모듈을 사용해야합니다. re 는 영어로 정규식인 Regular Expression의 앞의 알파벳만 따서 만든 모듈입니다.

 

p = re.compile(문자열)

re.compile 함수는 말그대로 정규식 형태를 가진 문자열을 실제 정규식 객체로 컴파일 해주는 기능을 합니다. 단순 문자열로 들어온 인자 값이 re.compile 함수를 거쳐서 p 변수에 반환되면서 정규식 객체로 만들어져서 match() 나 search() 함수와 같은 기타 함수들을 사용할 수 있게 해줍니다.

>>> import re
>>> p = re.compile("ca.e")
>>> print(type(p))
<class 're.Pattern'>
>>> print(dir(p))
['__class__', '__class_getitem__', '__copy__', '__deepcopy__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'findall', 'finditer', 'flags', 'fullmatch', 'groupindex', 'groups', 'match', 'pattern', 'scanner', 'search', 'split', 'sub', 'subn']

 

m = p.match(문자열)

pattern.match 함수는 re.Pattern 클래스의 메소드로써 문자열의 시작 부분에서 0개 이상의 문자가 정규식 패턴과 일치하면, 해당 일치 객체(re.Match)를 반환합니다. (만약 문자열의 시작 부분이 아닌 문자열의 모든 위치에서 찾고자 한다면 match가 아닌 search를 사용해야합니다)

만약 문자열과 일치하는 패턴이 없다면, None을 반환하게 됩니다.

>>> m = p.match("careless")
>>> print(m)
<re.Match object; span=(0, 4), match='care'>
>>> print(type(m))
<class 're.Match'>
>>> m2 = p.match("dont care")
>>> print(m2)
None

 

m.group()

Match.group 함수는 re.Match 클래스의 메소드로써 일치하는 하나 이상의 서브 그룹을 반환합니다. 만약 결과가 한 개라면 단순 문자열을 반환하고, 여러개라면 튜플 타입으로 반환합니다. 괄호 안에 인자 값으로 groupN 값을 넣을 수 있는데, 아무런 값을 넣지 않으면 기본값으로 0이 설정됩니다. groupN 값은 서브 그룹의 index 값을 의미하게 됩니다.

>>> m = p.match("care cane")
>>> m.group()
'care'
>>> m.group(0)
'care'

>>> m = re.match("(\w+) (\w+)", "Domdomi Buha, blogger")
>>> print(type(m.group()))
<class 'str'>
>>> print(type(m.group(1, 2)))
<class 'tuple'>
>>> m.group()
'Domdomi Buha'
>>> m.group(0)
'Domdomi Buha'
>>> m.group(1)
'Domdomi'
>>> m.group(1, 2)
('Domdomi', 'Buha')
>>> m.groups()
('Domdomi', 'Buha')

복습 차원에서 다시 말하자면 Pattern.match 함수는 문자열의 처음부터 일치하는 패턴을 찾고 찾았으면 반환해주기 때문에 뒷부분에 일치하는 또다른 문자열이 오더라도 상관없다고 했습니다.

 

그리고 Pattern 클래스에서 match 함수 사용이 아닌 re 모듈 자체에서 match 함수를 사용하게 되면 인자 값을 두 개를 받게 되는데, re.match(정규식패턴문자열, 문자열) 형태로 받을 수 있습니다. 만약 정규식 패턴(p)를 재사용하지 않는 다면 re.match 함수를 이용해서 compile 하고 match 를 사용할 필요 없이 한번에 사용할 수도 있습니다.

 

또 Match.group 함수는 기본적으로 인자값으로 0이 주어진다고 했습니다. 만약 여러 서브 그룹을 반환 받고자 한다면 인자를 여러개 입력할 수도 있고, 그렇게 되면 tuple 타입의 반환값이 주어진다고 하였습니다.

Match.groups 함수는 생김새 그대로 여러 서브 그룹을 인자 없이 한번에 튜플 타입으로 반환해주는 함수인 걸 알 수 있습니다.

 

m = p.search(문자열)

Pattern.match 의 경우에는 문자열의 처음부터 일치하는 지 확인하는 것이라고 했다면 Pattern.search 는 반면에 문자열을 전부 스캔 해보고 일치하는 패턴이 있다면 해당 일치 객체(re.Match)를 반환하는 것입니다. 아래 예시로 보는 것이 쉽게 이해가 될 것 같습니다.

>>> import re
>>> p = re.compile("ca.e")
>>> m = p.match("good care")
>>> print(m)
None
>>> m = p.search("good care")
>>> print(m)
<re.Match object; span=(5, 9), match='care'>
>>> print(m.group())
care

p.match 부분을 보면 good care 안에 ca.e 패턴에 매칭되는 것이 있더라도 첫 문자열이 good 이므로 첫 문자열에서 매칭되지 않았기 때문에 None 을 반환하게 됩니다.

하지만 search 의 경우에는 문자열의 끝까지 스캔하기 때문에 good 뒤에 있는 care까지 확인하고 일치 객체(re.Match)를 반환하게 되는 것입니다.

 

lst = p.findall(문자열)

Pattern.search 가 문자열의 전체를 스캔 후 일치하는 것이 한 개라도 있다면 그 한 개 일치 객체(re.Match)를 반환했다면 Pattern.findall 메소드는 일치하는 모든 문자열을 리스트형태로 반환합니다. 아래 예시를 보시죠.

>>> import re
>>> p = re.compile("ca.e")
>>> m = p.search("i dont care, yes im careless")
>>> print(m)
<re.Match object; span=(7, 11), match='care'>
>>> print(m.group())
care
>>> lst = p.findall("i dont care, yes im careless")
>>> print(lst)
['care', 'care']

 

위 모든 내용의 자세한 사항은 Python 의 re 모듈 문서/레퍼런스를 참고하시면 도움이 되실 것 같습니다.

https://docs.python.org/ko/3/library/re.html

 

re — 정규식 연산 — Python 3.10.0 문서

scanf() 시뮬레이션 파이썬에는 현재 scanf()에 해당하는 것이 없습니다. 정규식은 일반적으로 scanf() 포맷 문자열보다 강력하지만, 더 장황하기도 합니다. 아래 표는 scanf() 포맷 토큰과 정규식 간의

docs.python.org

 

문서 말고 공부하기 좋은 사이트로는 W3school 을 참고해도 되고,

https://www.w3schools.com/python/python_regex.asp

 

Python RegEx

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

제가 또 자주 사용하고 유용하게 사용하는 사이트로는 regex101.com 입니다. 여기서는 정규식을 바로바로 사용해서 매칭되는 지 안되는 그래픽 인터페이스로 손쉽게 확인이 가능하고 도움말 기능도 잘 되어 있어서 초보자들에게는 추천하는 강추하는 사이트입니다. Python 뿐만 아니라 언어별로 정규표현식 설정할 수도 있어서 편합니다.

https://regex101.com/

 

regex101: build, test, and debug regex

Regular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, GO, JavaScript, Java. Features a regex quiz & library.

regex101.com

 

 

- 끝 -

728x90
반응형
댓글