일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 백준
- django widget
- form
- 알고리즘 풀이
- es6
- HTML
- PYTHON
- django rest framework
- java
- js
- Django
- MAC
- django ORM
- javascript
- Baekjoon
- API
- c++
- Algorithm
- 알고리즘 연습
- react
- CSS
- 파이썬
- 알고리즘 문제
- web
- AWS
- 파이썬 알고리즘
- Git
- 장고
- DRF
- 알고리즘
- Today
- Total
수학과의 좌충우돌 프로그래밍
[crawling] 03. BeautifulSoup으로 웹 크롤링 하기 본문
BeautifulSoup 으로 웹 크롤링하기
앞에서 우리는 이미 BeuatifulSoup 를 설치 하였고 import 가 잘 되는지도 확인해보았습니다. 혹시 BeuatifulSoup4 버젼을 설치해야한다고 했던 게 기억나시나요?
그냥 BeuatifulSoup 를 설치하게 되면, 이는 BeuatifulSoup3 버젼이고 python3과 호환이 되지 않습니다. 이 점 다시 한 번 유의해주시기 바랍니다.
말하는 김에 한 가지 더 유의해야 할 점이 있습니다. 우리는 우리가 원하는 정보를 얻기 위해서 각 태그에 접근을 할 것입니다. 그리고 개발자도구
를 통해 어떤 태그인지를 확인 할 것 입니다. 여기서 문제가 발생합니다. 웹 페이지는 일종의 문서이기 때문에 웹 브라우저가 이를 해석하는 방식의 차이가 있습니다. 즉 해석하면서 없던 태그가 생겨나기도 한다는 것입니다. 그리고 requests
를 통해서는 최초의 html코드만 가져올 수 있기 때문에 새롭게 생긴 태그에 대해서는 데이터를 가져올 수 없습니다.
이제 본격적으로 BeautifulSoup 사용법에 대해 알아보도록 하겠습니다.
BeautifulSoup 맛보기
requests 를 통해 html 코드를 가져와야 하지만 지금은 BeautifulSoup 가 어떻게 동작하는지 알아볼 것이기 때문에 html 코드는 가져왔다는 전제하에 진행하겠습니다.
html = '''
<ol class="fruit">
<li>사과</li>
<li>바나나</li>
<li>딸기</li>
<li>수박</li>
</ol>
<ol class="vegetable">
<li>무</li>
<li>상추</li>
<li>콩나물</li>
<li>깻잎</li>
</ol>
'''
우리의 목표은 채소에 해당하는 항목들을 가져오는 것입니다. 그리고 그러기 위해서는 BeautifulSoup 함수를 사용합니다. 첫번째 인자로 가져온 데이터를 두 번째 인자로 어떤 도구로 데이터를 원하는 형태로 만들어 낼 지를 결정합니다. 그리고 이 도구를 parser라고 부릅니다. parser는 여러 종류가 있지만 BeautifulSoup 에서 기본제공하는 html.parser 를 사용하도록 하겠습니다.
soup = BeautifulSoup(html, 'html.parser')
지금은 html 이 이미 원하는 형태로 존재하기 때문에 soup
와 html
은 차이가 없습니다. 뒤에 활용하면서 차이에 대해 다시 한 번 언급하도록 하겠습니다.
다음으로는 우리가 원하는 세부적인 데이터로 접근해야합니다. 이 때 접근하는 방법은 select
메소드와 css선택자
를 통해 접근 할 수 있습니다. 위에서는 li 태그 안에 있는 채소의 항목에 대한 정보가 필요하므로 아래와 같이 접근합니다.
soup.select('li')
결과는 다음과 같이 list 형태입니다.
[<li>사과</li>,
<li>바나나</li>,
<li>딸기</li>,
<li>수박</li>,
<li>무</li>,
<li>상추</li>,
<li>콩나물</li>,
<li>깻잎</li>]
과일의 항목 역시 li 태그를 사용하였기에 원하지 않은 과일의 정보가 포함되어 있습니다. 좀 더 세부적인 접근이 필요합니다.
soup.select('.vegetable li')
아래와 같이 채소 목록만 불러올 수 있습니다.
[<li>무</li>,
<li>상추</li>,
<li>콩나물</li>,
<li>깻잎</li>]
이제 각 list 안의 각 원소에 접근하기 위해서 반복문을, 태그 안의 내용에 접근하기 위해 text 메소드를 사용하겠습니다.
vegetable_list = []
for tag in soup.select('.vegetable li'):
vegetable_list.append(tag.text)
정상적으로 출력되는걸 확인할 수 있습니다.
# vegetable_list
['무', '상추', '콩나물', '깻잎']
BeautifulSoup 활용하기
이번에는 '네이버 웹툰' 의 인기 급상승 만화 부분을 크롤링해서 가져와보도록 하겠습니다. 바로 이 부분입니다.
각각 인기순과 업데이트순에 따라 1위 부터 10위의 해당되는 만화가 있습니다. 이 내용을 가져와 보도록 하죠.
response = requests.get('https://comic.naver.com/webtoon/weekday.nhn')
html = response.text
requests를 이용하여 해당 url 의 html 코드를 가져왔습니다.
'\r\n\r\n\r\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<!DOCTYPE html>\r\n<html lang="ko">
\r\n<head>\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\t<meta http-equiv=
"X-UA-Compatible" content="IE=edge,chrome=1">\r\n\t\t\r\n\t<meta http-equiv=
"Content-type" content="text/html; charset=UTF-8">\r\n\t<title>네이버 만화 > 요일별 웹툰 > 전체웹툰</title>
[생략]
제어문자들이 너무 많아서 알아보기가 힘듭니다. 이 때 위에서 언급 했던 parser를 통해 데이터를 원하는 형태로 바꿔줄 수 있습니다.
soup = BeautifulSoup(html, 'html.parser')
깔끔하게 html 코드만 남은 걸 확인할 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"/>
<meta content="text/html; charset=utf-8" http-equiv="Content-type"/>
<title>네이버 만화 > 요일별 웹툰 > 전체웹툰</title>
<meta content="네이버 웹툰" property="og:title"/>
[생략]
이제 개발자도구에서 원하는 부분을 찾아 어떤 css 선택자로 접근해야하는지 알아봅시다.
' 인기급상승 만화 ' 라고 친절하게 주석으로 표시가 되어있네요. 이는 div 태그로 묶여있으며 이 때 class는 asideBox
입니다.
인기순과 업데이트순은 각각 id 값으로 realTimeRankFavorite와 realTimeRankUpdate 를 가집니다.
우리가 원하는 만화의 제목은 li 태그 안 a 태그 안에 위치해있습니다.
인기순에 해당하는 favor_list 와 업데이트순에 해당하는 update_list를 만들어 이 안에 각 만화 제목들을 추가해주도록 하겠습니다. 코드는 다음과 같습니다.
favor_list = []
update_list = []
for tag in soup.select('.asideBox'):
for favor_tag in tag.select('#realTimeRankFavorite li a'):
favor_list.append(favor_tag.text)
for update_tag in tag.select('#realTimeRankUpdate li a'):
update_list.append(update_tag['title'])
아마 여기서 이상한 부분을 눈치채셨을 겁니다. 바로 updade_tag['title'] 입니다. 물론 각 리스트의 결과는 문제 없이 잘 출력이 됩니다.
# favor_list
['외모지상주의-227화 가출팸 [09]',
'갓 오브 하이스쿨-403화',
'스위트홈-74화',
'걸어서 30분-79화 조금 더 옆에 있었으면',
'개를 낳았다-41화',
'화장 지워주는 남자-47화',
'용비불패 완전판-21화',
'금붕어-8화',
'내 여자친구는 상남자-75. 그러지마',
'테러맨-시즌2 49화']
#update_list
['꽃미남 저승사자-37화',
'금붕어-8화',
'밥 먹고 갈래요?-봄동부침개 화',
'몽홀-제 32화 혼란과 음모 - 4',
'걸어서 30분-79화 조금 더 옆에 있었으면',
'가우스전자 시즌3~4-시즌4 275화 담판',
'1초-1화',
'내 여자친구는 상남자-75. 그러지마',
'자취로운 생활-65. 멋 좀 부려보자',
'냐한남자-54화']
update_tag 를 favor_tag 와 마찬가지로 text 를 통해서 표현해보았습니다.
for update_tag in tag.select('#realTimeRankUpdate li a'):
update_list.append(update_tag.text)
결과가 예상과 다르게 제어문자를 많이 포함하고 있습니다.
['\r\n\t\t\t\t\t\r\n\t\t\t\t\t꽃미남 저승사자-37화\r\n\t\t\t\t',
'\r\n\t\t\t\t\t\r\n\t\t\t\t\t금붕어-8화\r\n\t\t\t\t',
'\r\n\t\t\t\t\t\r\n\t\t\t\t\t밥 먹고 갈래요?-봄동부침개 화\r\n\t\t\t\t',
'\r\n\t\t\t\t\t\r\n\t\t\t\t\t몽홀-제 32화 혼란과 음모 - 4\r\n\t\t\t\t',
'\r\n\t\t\t\t\t\r\n\t\t\t\t\t걸어서 30분-79화 조금 더 옆에 있었으면\r\n\t\t\t\t', '\r\n\t\t\t\t\t\r\n\t\t\t\t\t가우스전자 시즌3~4-시즌4 275화 담판\r\n\t\t\t\t', '\r\n\t\t\t\t\t\r\n\t\t\t\t\t1초-1화\r\n\t\t\t\t',
'\r\n\t\t\t\t\t\r\n\t\t\t\t\t내 여자친구는 상남자-75. 그러지마\r\n\t\t\t\t', '\r\n\t\t\t\t\t\r\n\t\t\t\t\t자취로운 생활-65. 멋 좀 부려보자\r\n\t\t\t\t', '\r\n\t\t\t\t\t\r\n\t\t\t\t\t냐한남자-54화\r\n\t\t\t\t']
왜 이런 일이 발생하는지 html 코드를 확인해보도록 합시다. 이유는 모르겠지만 a 태그 안에서도 비어있는 공간이 많습니다. 그래서 사용하기 위해서는 별도의 가공이 필요하죠.
[생략]
<ol id="realTimeRankUpdate" class="asideBoxRank" style="display:none;">
<li class="rank01">
<a onclick="nclk_v2(event,'rnk*u.cont','721461','1')" href="/webtoon/detail.nhn?titleId=721461&no=38" title="꽃미남 저승사자-37화">
꽃미남 저승사자-37화
</a>
[생략]
a 태그의 title속성을 보면 웹툰의 제목과 같음을 볼 수 있습니다. 따라서 다음과 같이 title속성에 접근하였습니다.
for update_tag in tag.select('#realTimeRankUpdate li a'):
update_list.append(update_tag['title'])
'웹프로그래밍 > crawling' 카테고리의 다른 글
[crawling] 02. request를 통한 HTTP 요청 - GET (0) | 2019.03.21 |
---|---|
[crawling] 01.크롤링을 알기 전, HTTP (0) | 2019.03.14 |