본문 바로가기
코딩/자동화

파이썬으로 웹 페이지에서 정보 추출하기(웹스크레이핑, 웹크롤링)/Request와 Beautifulsoup 이용하기

by 나홀로코더 2021. 11. 29.
반응형

서론

 

파이썬 입문 콘텐츠에서 가장 흔히 보이는 것이 바로 웹페이지에 게시된 정보들을 추출하여 활용하는 웹스크레이핑 방법인 것 같다.

 

이미 이에 대해 잘 설명하고 있는 수많은 자료들이 있으나, 필자 스스로 필요할 때 찾아볼 목적으로 이 포스팅을 한다.

 

 

본론

 

1. 필요한 라이브러리

 

웹스크레이핑을 하는 데 가장 기본적인 라이브러리는 requests와 beautifulsoup이다.

 

requests는 이름 그대로 HTTP 요청을 간단하게 할 수 있는 라이브러리이다.

 

HTTP 요청은 GET, POST, DELETE 등으로 다양한데, 웹스크레이핑을 할 때는 GET을 사용하여 웹페이지를 HTML 소스 형태로 불러온다.

 

그리고 beautifulsoup는 requests로 불러온 HTML 소스를 분석(parsing)하고, 필요한 정보를 추출하는 데 쓴다.

 

둘다 내장 라이브러리가 아니므로 pip install로 설치해줘야 한다.

 

2. HTML 문서 살펴보기

 

HTTP니 HTML이니 하는 용어들이 필자같은 코알못, 컴알못들에게는 낯설지도 모르겠다.

 

사실 우리가 매일 수없이 보는 웹페이지들은 아래 사진의 오른쪽에 보는 것처럼 HTML로 작성된 문서가, 왼쪽에 보는 것처럼 예쁘게 나타나는 것이다.

 

웹페이지 예시

 

우측의 HTML 소스를 보면, 아래 위로 <html> </html> 표시가 있고, 중간중간 <body>, <div> 등의 표시가 보이는데 이런 것들을 '태그'라고 한다.

 

위에서 말한 requests는 위와 같은 HTML 소스를 불러오고, beautifulsoup는 그 안의 태그나 텍스트 등을 추출하는 것이다.

 

3. requests 사용해보기

 

먼저 request를 사용해보자.

 

requests를 임포트하고, get 함수에 URL을 넘겨줘보자.

 

import requests
URL = 'https://www.google.com/'
response = requests.get(URL)

 

이렇게 하면 응답 결과가 response 변수에 저장이 되는데, response를 그냥 출력해보면 아래와 같이 한줄만 나온다.

 

<Response [200]>

 

HTML 요청이 정상적으로 처리되었다는 뜻인데, .text를 붙여준 뒤에 출력해보면 뭔가 많은 글자들이 출력될 것이다.

 

response.text

 

requests의 역할은 여기까지이다. 이제 beautifulsoup로 넘어가보자.

 

4. beautifulsoup로 파싱하기

 

requests로 불러온 HTML 소스에서 원하는 정보를 추출해내려면 우선 beautifulsoup를 이용해서 파싱을 해야한다.

 

아래와 같이 bs4를 임포트하고, Beautifulsoup 함수의 인자로 위에서 가져온 response.text와 함께 파싱에 사용할 파서의 이름을 같이 넘겨준다.(파서는 lxml 등 다른 것들도 있다.)

 

import bs4
soup = bs4.BeautifulSoup(response.text,"html.parser")

 

여기까지 한 뒤에 soup를 그냥 출력해보자. 그러면 아까 response.text 한 것과 비슷한 모습일 텐데, 이제는 beautifulsoup를 이용해 원하는 부분만 추출할 수 있다는 점에서 달라졌다.

 

5. find를 사용해서 원하는 정보 추출하기

 

이제 필요한 정보를 추출해볼 차례이다.

 

아래 코드를 입력해보자. div 태그를 찾아달라는 코드이다.

 

soup.find('div')

 

이번에도 뭔가 글자가 많이 나올 텐데, 아까보다는 적어졌고, <div>로 시작해서 </div>로 끝나는 것을 볼수 있을 것이다.

 

<div id="mngb"><div id="gbar"><nobr><b class="gb1">검색</b> <a class="gb1" href="https://www.google.co.kr/imghp?hl=ko&amp;tab=wi">이미지</a> <a class="gb1" href="https://maps.google.co.kr/maps?hl=ko&amp;tab=wl">지도</a> <a class="gb1" href="https://play.google.com/?hl=ko&amp;tab=w8">Play</a> <a class="gb1" href="https://www.youtube.com/?gl=KR&amp;tab=w1">YouTube</a> <a class="gb1" href="https://news.google.com/?tab=wn">뉴스</a> <a class="gb1" href="https://mail.google.com/mail/?tab=wm">Gmail</a> <a class="gb1" href="https://drive.google.com/?tab=wo">드라이브</a> <a class="gb1" href="https://www.google.co.kr/intl/ko/about/products?tab=wh" style="text-decoration:none"><u>더보기</u> »</a></nobr></div><div id="guser" width="100%"><nobr><span class="gbi" id="gbn"></span><span class="gbf" id="gbf"></span><span id="gbe"></span><a class="gb4" href="http://www.google.co.kr/history/optout?hl=ko">웹 기록</a> | <a class="gb4" href="/preferences?hl=ko">설정</a> | <a class="gb4" href="https://accounts.google.com/ServiceLogin?hl=ko&amp;passive=true&amp;continue=https://www.google.com/&amp;ec=GAZAAQ" id="gb_70" target="_top">로그인</a></nobr></div><div class="gbh" style="left:0"></div><div class="gbh" style="right:0"></div></div>

 

 

find 함수를 사용하면 인자로 넘겨준 태그를 찾아 돌려주는데, 같은 태그가 여러 개일 경우에는 맨 앞의 것을 돌려준다.

 

이번에는 위의 코드에다 .text를 붙여보자.

 

위에서 찾은 태그에서 텍스트만 뽑아서 돌려줄 것이다.

 

'검색 이미지 지도 Play YouTube 뉴스 Gmail 드라이브 더보기 »웹 기록 | 설정 | 로그인'

 

이제야 좀 볼만해졌다.

 

반응형

 

그런데 여기에서 "이미지"만 뽑아내고 싶다면 어떻게 하면 될까?

 

위의 div 태그로 돌아가서 한번 자세히 들여다 보자.

 

div 태그가 열리고, 그다음에 div 태그가 하나 더 열리고, 그다음에는 b 태그가 하나 열리고, 그다음에 있는 a 태그 안에 "이미지"가 있다.

 

그러면 아마 위 태그에서 a 태그를 찾아달라고 하면 될 것 같다.

 

soup.find('div').find('a')

 

출력해보면 아래와 같이 a 태그만 뽑아준다.

 

<a class="gb1" href="https://www.google.co.kr/imghp?hl=ko&amp;tab=wi">이미지</a>

 

여기에다가 다시 .text를 붙여보자. 이젠 "이미지"만 출력될 것이다.

 

soup.find('div').find('a').text

 

a 태그에서 텍스트가 아니라 href= 다음에 있는 URL을 뽑고 싶을 때는 어떻게 할까?

 

href나 class를 보면 "이미지" 텍스트와 달리 여는 태그와 닫는 태그 사이에 있는 것이 아니라, 여는 태그 안에 있다.

 

이런 것을 태그의 속성이라고 하는데, 태그의 속성을 뽑아낼 때는 아래와 같이 대괄호를 사용한다.

 

soup.find('div').find('a')['href']

 

자, 이제 다시 앞에서 뽑아낸 div 태그로 돌아가보자.

 

앞에서부터 쭉 훑어보면, a 태그가 여러 번 열리고 닫히는데, 이 중에 class가 gb4인 a 태그가 뒤에서 3분의 1 정도 지점에서 처음 나온다.

 

find 함수로 이 태그를 가져오고 싶다면, 아래와 같이 find 함수의 인자로 class를 같이 넘겨준다.

 

soup.find('div').find('a', {'class':'gb4'})

 

이번에는 위에서 본 것과 다른 a 태그가 출력될 것이다.

 

<a class="gb4" href="http://www.google.co.kr/history/optout?hl=ko">웹 기록</a>

 

6. find로 찾은 태그의 다음 태그 찾기(next_sibling)

 

앞에서 찾은 a 태그의 다음에 있는 a태그를 보면 class가 gb4로 이전 것과 같다.

 

find는 첫번째 것만 돌려주므로 find만 가지고는 접근이 안되는데, 이런 태그는 어떻게 접근할까?

 

아래와 같이 .next_sibling을 두번 붙여주면 다음 a 태그를 찾아준다.

 

soup.find('div').find('a', {'class':'gb4'}).next_sibling.next_sibling

 

7. find_all로 한번에 여러 태그 가져오기

 

find_all 함수를 이용하면, 한번에 여러 개의 태그를 가져올 수도 있다.

 

위의 예제에서 find를 find_all로만 바꾸면 된다.

 

soup.find('div').find_all('a', {'class':'gb4'})

 

결과물로는 아래와 같이 태그의 리스트를 돌려준다.

 

[<a class="gb4" href="http://www.google.co.kr/history/optout?hl=ko">웹 기록</a>, 
<a class="gb4" href="/preferences?hl=ko">설정</a>, 
<a class="gb4" href="https://accounts.google.com/ServiceLogin?hl=ko&amp;passive=true&amp;continue=https://www.google.com/&amp;ec=GAZAAQ" id="gb_70" target="_top">로그인</a>]

 

여기에서 텍스트만 뽑아내고 싶다면?

 

리스트에 들어가 있으니 바로 .text를 붙여선 안되겠고, 아래와 같이 find_all로 만든 리스트에 대해서 for문을 이용해 각각의 항목에 .text를 해주면 된다.

 

tags = soup.find('div').find_all('a', {'class':'gb4'})
for tag in tags:
    print(tag.text)

 

for문 안에다 print가 아닌 다른 함수를 사용하여 어떤 형태든 원하는 형태로 뽑아내면 될 것이다.

 

 

마무리

 

이 정도만 이해하면 웹스크레이핑에 필요한 기본적인 내용은 다 익힌 것이다.

 

이를 활용한 실제 웹스크레이핑 예제에 관한 포스팅을 곧 올릴 계획이다.

 

참고로 requests로는 바로 접근하기 어려운 웹페이지들이 있는데, 그런 경우에는 selenium을 활용해서 HTML 소스를 가져올 수가 있다.

 

이에 대해서도 곧 다뤄보려고 한다.

반응형

댓글