1. 정규표현식
정규표현식은 특정한 규칙, 패턴을 가진 문자열을 표현한다. 그래서 특정 패턴을 지닌 문자열을 찾는 데 많이 쓰인다. 파이썬은 정규표현식을 지원하기 위해 're' 모듈을 기본 라이브러리로 제공한다. re 모듈 안의 함수인 compile과 match, search, findall 등을 이용해 정규표현식을 컴파일한 결과를 객체로 돌려주고, 우리가 정규표현식으로 찾고자 하는 문자열이 해당 문자열 속에 있는지 찾는 과정을 거친다. 정규표현식은 간단히 정규식이나 Regex로 불리우기도 한다.
예를 들어, 'Hello World 안녕 python 튜토리얼'에서 python만 찾고 싶다고 했을 때, 파이썬 코드로 찾는다고 하면 반복문, python의 위치 등 고려해야 할 게 많다.
하지만 정규표현식을 활용하면 메타문자로 조건을 구현하고 조건에 알맞는 문자열을 찾을 수 있다.
보통 웹페이지에서 이메일같은 특정 텍스트를 찾는데 쓰인다고 해서, 이번에는 직접 웹페이지에서 텍스트를 추출하고 텍스트 중 아이디만 추출하는 실습을 통해 정규표현식을 공부해보겠다.
메타 문자(meta characters)
표현식 | 의미 | |
문자 클래스 | [ab] | 문자 클래스, a와 b 중 한 개 이상의 문자와 매치 |
[a-zA-Z] | 대소문자 상관없이 알파벳 모두 | |
[0-9] | 숫자 모두 | |
[^ab] | ^는 not의 의미, a와 b를 제외한 문자와 매치 | |
반복 | a.b | a와 b 사이에 줄 바꿈(\n)을 제외한 모든 문자와 매치 |
ca*t | * 앞의 a가 0부터 무한대로 반복하는 문자와 매치(a 0번 반복○) | |
ca+t | * 앞의 a가 최소 1번 이상 반복하는 문자와 매치(a 0번 반복X) | |
ca{2,5}t | a가 2번 이상 5번 이하로 반복 | |
ca?t | ? = {0,1} / a가 0번 or 1번 반복 | |
기타 | a|b | or의 의미, a 또는 b |
a? | 존재여부를 표현, a가 존재할 수도, 존재하지 않을 수도 있음을 의미 | |
(a) | 그루핑, 그룹을 표현 | |
(a)(b) | 그룹들의 집합을 표현, 앞에서부터 순서대로 번호를 부여 |
표현식 | 의미 |
\s | space. 공백 문자를 의미 |
\S | non space. 공백이 아님을 의미 |
\w | word. 알파뱃, 숫자, _중의 한 문자 |
\W | non word. 알파뱃, 숫자, _ 중 하나가 아님을 의미 |
\b | 문자와 공백 사이의 문자를 의미. '\bclass\b' -> 'no class at all'과 일치 |
\d | 숫자를 의미 |
\D | 숫자가 아님을 의미 |
\^ | '^'를 문자로 사용할 것임을 의미 |
^python | 문자열의 맨 처음에 python이 있는 문자와 매치 |
python$ | 문자열의 맨 끝에 python이 있는 문자와 매치 |
(?=:) | ':' 앞까지만 문자 추출, ':'는 추출 되지 않음 |
(?!bat) | 'bat'가 없는 문자만 추출 |
<.*> | '<문자>' 형식의 문자열 모두 추출 ex) <html><head><div> -> <html><head><div> |
<.*?> | '<문자>' 형식의 문자열 중 첫 번째 문자열만 추출 ex) <html><head><div> -> <html> |
2. 정규표현식과 BeautifulSoup 실습
이제 정규표현식 개념을 알았으니, 웹 크롤링 하는데에 주로 쓰이는 BeautifulSoup 모듈과 함께 웹 페이지에 있는 아이디만 불러오겠다.
이번 실습의 대상 사이트는 http://m.yes24.com/Event/EventWinnerDetail?iContentNo=59080&NoticeYn=Y yes24의 당첨자 발표 페이지이다.
링크를 들어가보면 위 이미지와 같이 이벤트에 당첨된 사람들의 아이디가 끝 3자리가 별표(*) 처리된 채로 쭉 나온다.
이 사이트에서 아이디에 관한 텍스트만 추출하려면 이렇게 계획할 수 있다.
- requests로 url의 텍스트 객체를 string으로 불러온다.
- 내가 찾고자 하는 아이디 텍스트의 구조를 파악하고, 정규표현식 형태로 정의한다.
- re 라이브러리로 정규표현식과 매치되는 모든 텍스트를 추출한다.
1번을 수행해보자.
import requests
import re
from bs4 import BeautifulSoup
test_url=requests.get('http://m.yes24.com/Event/EventWinnerDetail?iContentNo=59080&NoticeYn=Y')
soup=str(BeautifulSoup(test_url.text, "html.parser"))
# type이 string이어야 정상적으로 작동하기 때문에 str타입으로 변경
soup
>>>
위 코드를 실행하면 이렇게 페이지의 html 정보가 텍스트로 추출된다.
보다시피 html 소스는 너무 길고 글자가 주르륵 있어서 아이디가 있는 곳을 일일히 찾으려면 눈알이 빠질 것이다.
그래서 아이디 텍스트의 규칙을 반영한 정규표현식을 정의하고, 텍스트와 매치시키면 보다 효율적으로 추출할 수 있다.
당첨자 아이디 문자열의 규칙을 찾아보자.
아이디는 모두 알파벳으로 시작하고, ***(별 3개)로 끝난다.
이 규칙을 정규표현식을 이용해 구현해 보면, "[a-zA-Z0-9]+\*{3}"이다.
- [a-zA-Z0-9]+ 는 알파벳 대소문자나 숫자가 최소 1개 이상 있는 문자열을 의미하고,
- \*{3} 는 문자로 인식하기로 한 '*'이 알파벳 뒤에 3번 반복하는 문자열을 의미한다.
"\w"도 알파벳, 숫자, _ 등을 의미하지만,
그렇게 하면 한글로 시작하는 '명단***' 저 부분도 같이 추출되기 때문에 알파벳, 숫자로 시작하는 문자열만 추출되도록 적어주었다.
3번째로, re를 이용해 정규표현식과 매치되는 모든 문자열을 찾아보자.
result=re.findall(r"[a-zA-Z0-9]+\*{3}",soup)
for i in result:
print(i)
>>>
upnuc***
came***
ting***
seoulite***
ssaygch***
dhdhl***
hoona1***
no***
dlc***
toris***
ine***
neversu***
thtan***
youngh***
myster***
jx***
yoos***
wn1***
1004na77l***
hong840m***
space***
kensa***
goungj***
jv***
mul***
a77***
wosag***
iboj3***
bbin***
ridd***
rlarlgid***
2005059***
skgus1***
love0***
sua0***
ins***
fksl8***
arran1***
sang0***
jun***
kyees***
morning0***
>>>
re 라이브러리의 findall 함수는 텍스트에서 일치하는 문자열을 모두 찾아서 리턴한다.
참고로 정규표현식 앞에 위치한 r 은 raw string 으로,
\s나 \w같은 백슬래시 문자를 해석하지 않고 문자열로 남겨두기 때문에 백슬래시를 두 번 써야 하는 수고를 덜 수 있다.
+) IGNORECASE
p=re.compile(r"[a-z0-9]+\*{3}",re.I)
result1=p.findall(soup)
for i in result1:
print(i)
re.I 는 'ignorecase'의 뜻으로, 알파벳의 대소문자를 무시하고 매칭할 수 있는 함수여서 compile의 파라미터로써 써주면 정규표현식에 대문자를 포함하는 식( [A-Z] ) 을 쓰지 않아도 대문자를 포함한 문자열과도 매치가 되어 위의 코드와 같은 결과를 얻을 수 있다.
이렇게 페이지에서 추출하고 싶은 값의 패턴만 파악한다면, 정규표현식으로 구현해서 그 값만 쉽게 추출할 수 있다.
정규표현식은 자주 쓰이진 않지만, 알아두면 여러 곳에 적용해서 쓰기 유용할 것 같다.
*전체보기*
import requests
import re
from bs4 import BeautifulSoup
test_url=requests.get('http://m.yes24.com/Event/EventWinnerDetail?iContentNo=59080&NoticeYn=Y')
soup=str(BeautifulSoup(test_url.text, "html.parser"))
result=re.findall(r"[a-zA-Z0-9]+\*{3}",soup)
for i in result:
print(i)
번외) 다른 게시물의 이벤트 당첨자 아이디도 모조리 불러오고 싶다면?
'yes24' 홈페이지에는 위에 예제로 쓴 이벤트 당첨자 발표 주소말고 비슷한 형식의 여러 당첨자 발표 목록이 쫘르륵 있다.
하나의 이벤트 말고 다른 이벤트 당첨자들의 아이디까지 한 번에 추출하고 싶다면, 웹 페이지의 주소의 패턴을 잘 찾아야 한다.
연속된 당첨자 발표 페이지 두 개를 옮겨 가며 살펴보면 ? 뒷 부분 'ContentNo=' 부분의 숫자가 1만큼 더해진 걸 볼 수 있다.
앞주소는 똑같고 게시물 넘버만 변경되어서 for문을 이용해 차례로 넘버를 바꾸고, 아이디를 추출한다.
게시물 넘버가 59090 ~ 59100인 페이지의 텍스트를 뽑아본다.
import requests
import re
from bs4 import BeautifulSoup
import pandas as pd
for page in range(59090,59101):
# for문이 돌아가며 넘버만 차례대로 바뀜
raw=requests.get('http://m.yes24.com/Event/EventWinnerDetail?iContentNo='+str(page))
html=BeautifulSoup(raw.text, "html.parser")
title=html.select("span.tit_txt")[0].text
title=title.lstrip().rstrip("\r\n 발표")
p=re.compile(r"[a-zA-Z0-9]+\*{3}")
result=p.findall(str(html))
dict={title : result}
ids=pd.DataFrame(dict)
print(ids)
* 결과 *
[eBook] 만화_『삼각창의 밖은 밤』 댓글 이벤트 당첨자
0 ever***
1 papier***
2 img***
3 hanny5***
4 my0***
5 bobo0***
6 sint***
7 dae***
8 bulub***
9 ymh***
10 be***
11 ppai9***
12 joodda***
13 tre***
14 hyalein1***
15 olvve***
16 gwendo***
17 orchi***
18 sid***
19 rlath***
20 mor***
21 holl***
22 bullrus***
23 sidviciou***
24 springf***
25 ba***
26 bany***
27 yminli***
28 by110***
29 msa***
30 amorfa***
[eBook] 만화_『불멸의 그대에게』 리뷰 이벤트 당첨자
0 chchun1***
1 magicjet1***
2 moon8***
3 springf***
『전지적 독자 시점 PART 1』 친필 사인본 이벤트 당첨자
0 m10***
1 bemin5***
2 cor***
『메타버스 골드러시』 리뷰 이벤트 당첨자
0 bib***
1 uriyu***
2 freeman***
3 laur***
4 coolguy***
『수학을 읽어드립니다』 한줄평 이벤트 당첨자
0 hyalein1***
1 nanhmjj***
2 kiddis***
[eBook] 라이트노벨_『가정 마도사의 이세계 생활』 알림 이벤트 당첨자
0 msa***
1 moond***
2 by110***
3 bany***
4 soonshina***
.. ...
96 god7***
97 youyou***
98 vnfmsdksr***
99 khayjm***
100 ljgl***
[101 rows x 1 columns]
[eBook] 만화_『일하는 세포』 리뷰 이벤트 당첨자
0 springf***
에픽하이 (EPIK HIGH) [Epik High Is Here] 발매기념 VIDEO CALL EVENT 당첨자 안내
0 jaky***
1 bluenot***
2 ster1***
3 vigor771***
4 gksmf6***
5 piawerlin***
6 W***
7 B***
8 L***
9 epik***
10 mys2ek***
11 gk13***
12 ph***
13 Z***
14 P***
15 ini***
16 blizar***
17 ti***
18 403080***
19 C***
20 rksrk***
21 alfks0***
22 rlatnal***
23 amyl***
24 A***
25 B***
26 Z***
27 leebb***
28 sob***
29 daydream***
30 jgrace***
31 kimngan***
32 H***
33 N***
34 hyen***
35 lindac***
36 junor***
37 mayora***
38 J***
39 M***
40 M***
41 kej9***
42 liel***
43 siny***
44 X***
[eBook] 만화_『<주술회전 0> 극장판』 댓글 이벤트 당첨자
0 naoki0***
1 sub***
2 jj2***
3 mich***
4 alicel***
.. ...
105 dbwj***
106 inisi***
107 nani***
108 a88***
109 soonshina***
[110 rows x 1 columns]
[eBook] 만화_『학원 베이비시터즈』 댓글 이벤트 당첨자
0 myhus***
1 springf***
2 iknow***
3 to***
4 carrotdr***
.. ...
107 bree***
108 sly***
109 qj***
110 d***
111 compas***
[112 rows x 1 columns]
[eBook] 만화_『청루 오페라 11권』 댓글 이벤트 당첨자
0 khayjm***
1 mybaby6***
2 joke***
3 youyou***
4 nicc***
.. ...
57 by110***
58 stafus***
59 msa***
60 heag***
61 funn***
[62 rows x 1 columns]
위 코드대로 실행하면 어떤 이벤트의 당첨자인지를 구분하기 위한 { 게시물 제목 : 당첨자 아이디 } 의 딕셔너리가 만들어지고, 딕셔너리를 데이터프레임으로 변환했다.
결과물을 보면 한 눈에 잘 알아볼 수 있게 되었다.
이렇게 정규표현식과 requests, beautifulsoup을 잘 혼합하면 어떤 웹 크롤링도 가능하다!
'Programming > python' 카테고리의 다른 글
클래스(class)와 객체 지향 프로그래밍(object oriented programming) 개념 정리 (0) | 2022.06.13 |
---|---|
파이썬 선택 정렬 알고리즘(selection sort) 개념과 예제 (0) | 2022.06.06 |
웹에서 YAML 파일 가져오고 dataframe 으로 나타내기 (0) | 2021.11.04 |
파이썬 pandas.melt() 데이터 재구조화(reshape) ( melt vs pivot ) (0) | 2021.10.13 |
파이썬 python replace 함수 치환 (0) | 2021.09.29 |