개발 강좌/크롤링 강좌

크롤링 05 | select를 사용하여 값을 불러오자

건유1019 2020. 12. 20. 12:00

저번에는 ".find"를 통하여 성공적으로 크롤링을 마쳤습니다. 이번 시간에는 ".select"문을 활용하여 크롤링이 아닌 파싱을 해보도록 하겠습니다. 사실 이번 강좌가 크롤링의 마지막 강좌입니다. 지금까지 읽어주시는 모든 독자분들께 감사의 말씀드리겠습니다.

# 업로드전 수정, ".select"으로 하는것은 크롤링이 아닌 파싱이라고 합니다. 강의중에선 ".select"도 크롤링하는거라고 하지만 파싱이라고 하니 오해하지 마시기 바랍니다.


.select 문에 대해 알아보자.

우선 시작 전에 기존 시간처럼 ".select"문에 대해 알아보아야 합니다. 사실 ".select"는 ".find"는 여러 요소를 지정하여 찾을 수 있는 반면에 ".select"는 하나만 딱 지정한 CSS 스타일로 값을 찾을 수 있다고 서술이 되어 있습니다. 그래서 ".select"문을 사용하는 사람이 적을 수도 있다고 생각할 수 있지만 사실이 아닙니다. 그 이유를 알아보자면 이어서 사용 할 수 있습니다. 기존 시간에 ".find"는 이어사용 할려면 계속 ".find"문을 사용한다는 것을 알려드렸습니다. 그러나 ".select"문의 경우 한 문장으로 저 여러 ".find" 문을 요약할 수 있습니다. 예를 들어 아래의 코드를 한 줄로 줄일 수 있다는 강점이 있습니다

soup.find("span", {"class":"First_Tag"}).find("span", {"class":"Second_Tag"}).text

어떻게 줄이느냐에 대해서는 이제 설명해 드릴 테니 차근차근 읽어보시기 바랍니다. 그리고 또 다른 이유로는 Python3에는 BeautifulSoup가 있다면, Java라는 다른 언어에는 Jsoup라는 파싱 모듈이 존재합니다. Jsoup의 경우는 ".find"문을 지원하지 않고, ".select"과 비슷하게 지원하기 때문에 두 언어를 겸용하시는 유저의 경우 ".find"문보다 ".select"문을 더 선호하시는 분들이 많습니다. 이 두 가지 이유로 ".select"문을 사용하기도 합니다. 사실 ".find"와 ".select" 둘 다 결과는 똑같으며 선택은 개인 취향입니다. 이제부터 find와 다른 세계인 select를 활용하여 파싱을 해보도록 하겠습니다.

 

우선 select문의 사용 방법을 알아보아야겠죠? select문의 구성은 아래처럼 되어 있습니다.

soup.select('열린태그[속성="속성 값"]')

일단 시작부터 find와 달리 한 개의 인자로 불러온다는 것을 알아보실 수 있습니다. 그러나 ".select"문은 ".find"문이라고 보단 ".find_all"이라고 보셔야 알맞다고 봅니다. 왜냐하면 저렇게 사용하면 "find_all"처럼 배열 안에 저장하기 때문입니다.

<span class="Python3">None</span>

지난 강좌처럼 이렇게 구성된 HTML 코드가 있다고 가정해봅시다. 우선 항상 말하다시피, soup문을 먼저 선언해 주어야 합니다.

from bs4 import BeautifulSoup
#HTML안에 위 내용이 들어가 있다고 가정.
soup = BeautifulSoup(HTML,"html.parser")

그다음 저 값을 select를 사용해서 구하실 수 있습니다.

soup.select('span[class="Python3"]')

그렇다면 "[<span class="Python3">None</span>]"이라는 값이 반환되게 됩니다. 기존 ".find"와 달리 ".select"는 배열로 여러 값이 저장되게 됩니다. 만약에 여러 저렇게 생긴 여러 가지의 똑같은 태그와 속성, 속성 값을 가진 html 문이 있어도, 우리는 [0] 혹은 저 안에 값이 그 이상을 통해 우리가 찾고자 하는 값을 편하게 찾으실 수 있습니다. 

 

근데 이번에도 지난번처럼 None이란 값을 구하기 위해서는 어떻게 해야 할까요? ".find"처럼 ".text"를 사용해야 할까요? 아닙니다. 오히려 에러가 발생합니다. ".text"보단 ".get_text()"라는 BeautifulSoup에서 제공하는 함수를 사용해야 합니다.

soup.select('span[class="Python3"]')[0].get_text()

이렇게 하시면 저번처럼 None값을 반환받을 수 있습니다.


만약에 기존처럼 아래처럼 html 코드가 복잡하게 구성되어 있다면 어떻게 해야 할까요?

지난번에도 html은 저렇게 한 줄로 구성된 것이 아닙니다. 개발자는 오히려 더 많은 정보를 보다 편리하게 보여주기 위하여 많은 html 코드를 사용합니다. 만약에 아래처럼 구성되면 어떻게 해야 할까요?

<span class="First_Tag">
	<span class="Second_Tag">undefined</span>
</span>

저번처럼 select문을 연속해서 사용해야 할까요? 사실 가능합니다. 그러나 추천드리고 싶진 않습니다.

soup.select('span[class="First_Tag"]>span[class="Second_Tag"]')[0].get_text()

이렇게 한 번에 구할 수 있습니다. 여러 개 사용하는 대신 ">" 한 개로 새로운 함수를 불러오는 과정을 생략할 수 있습니다. 그리고 저번처럼 단축해서 사용할 수도 있습니다.

soup.select('span[class="Second_Tag"]')[0].get_text()

이렇게 해도 우리가 원하는 undefinded 값을 반환받을 수 있습니다.

 

select문의 장점은 이것입니다. 굳이 길게 적을 필요가 없습니다. 한 번에 ">"를 활용하여 여러 번을 사용한 것처럼 만드실 수 있습니다.


이제 바로 써볼까요?

어떠신가요? find문과 기능은 똑같으면서도 간편함을 느낄 수도 있고 못 느낄 수도 있습니다. 사용자마다 개인의 취향이 있으니까요. 이젠 이 ".select"문만 사용해서 기존 시간처럼 "강남구 날씨" 말고 "용산구 날씨"를 얻어보기로 합시다.

 

그전에 우선 soup라는 변수 안에 값을 저장하는 것은 모두 똑같습니다.

import requests
from bs4 import BeautifulSoup #bs4 모듈안에 있는 BeautifulSoup를 불러옴.

resp = requests.get("https://search.naver.com/search.naver?query=용산구+날씨")
soup = BeautifulSoup(resp.text,"html.parser")

이렇게 soup안에 soup형태의 용산구 날씨 값을 넣게 되었습니다. 이번에도 저번처럼 크롬을 활용하여 파싱을 해볼 것입니다.

우선 우리가 뜯어볼 사이트의 형태는 이렇게 생겼습니다. 기존과 다른 변화가 있다면 지역명이 바뀌었다는 겁니다.

그다음 F12를 눌러 개발자 도구를 엽니다. 꼭 F12키 말고 우클릭 -> 검사(N)를 통하여 활성화할 수 있습니다.

우리가 필요한 값은 "서울특별시 용산구 후암동", "10" 그리고 그 아래에 작성된 "흐림, 어제보다 0℃ 높아요"라는 문구입니다. 우측 Elements 왼쪽으로 두 칸에 있는 첫 번째 아이콘을 통해 우리는 쉽게 불러올 수 있습니다.

 

일단 날씨 정보라는 박스를 기존과 동일하게 저장해 줄 예정입니다. 왜냐하면 한 번에 하면 중복된 코드가 발생하기 때문에 비효율적이라고 생각합니다.

이 박스의 태그는 "section"이며, 속성과 그 값은 "class"와 "sc_new cs_weather _weather"이군요. 그러면 이를 기존 find와 달리 select를 활용하여 box라는 변수 안에 저장해보도록 하겠습니다. 혹시나 해서 find문도 작성해보았으니 비교해보시기 바랍니다.

 

(".find"문을 활용했을 경우)

box = soup.find("section",{"class":"sc_new cs_weather _weather"})

(".select"문을 활용했을 경우)

box = soup.find('section[class="sc_new cs_weather _weather"]')[0]

이렇게 box라는 변수 안에 기존 시간처럼 날씨 정보를 넣어보았습니다.

 

그다음으로 구해볼 값은 "서울특별시 용산구 후암동" 겠죠?

저 값이 태그 em이라는 곳 안에 있지만 오차율을 줄이기 위해 위에 있는 태그를 사용 후에 em 태그를 불러오도록 합시다. 여기서 왜 오차율이 발생하냐면 em이라는 태그 하나만 있으면 다른 여러 em태그도 잡히기 때문입니다.

 

위 box 모듈에는 날씨정보 박스 안에 있는 값들이 저장되어 있으니 이어나가서 사용합니다.

우선 그 상위 코드의 태그는 "span"이고, 속성에는 "role"과 "class"가 있으나 편의상 "class"를 사용하겠습니다. 그리고 속성 값은 "btn_select" 인 것을 확인할 수 있습니다. 

 

기존과 똑같은 위치에 존재하네요. 이렇게 구성된 경우 find 대신 select문이 더 좋다고 느껴지게 됩니다.

local_name = box.select('span[class="btn_select"]>em')[0].get_text()

어떻게 생각하십니까? 4편에서 적은 아래의 코드와 비교해보십시오.

local_name = box.find("span",{"class":"btn_select"}).find("em").text

기존에는 ".find"문을 2번 사용한 것과 달리 ".select"는 단 한 번에 불러올 수 있다는 장점이 있습니다.

이렇게 하시면 local_name이라는 변수 안에는 "서울특별시 용산구 후암동"이 저장되게 됩니다.

 

다음은 "10℃"를 구해보도록 하겠습니다.

"10℃"값은 확인해본 결과 태그는 "span", 속성과 속성 값은 각각 "class" 그리고 "todaytemp"네요. 그러면 아래처럼 작성하시면 됩니다.

temp = box.select('span[class="todaytemp"]')[0].get_text()

이렇게 작성하시면 temp라는 변수 안에 현재 용산구의 현재 온도가 저장되게 됩니다.

 

마지막으로 우리가 구해볼 값은 "구름많음, 어제보다 0 높아요." 라는 내용입니다.

이번에는 "p"라고 하는 태그에 "class"라는 속성, 그리고 "cast_txt"라는 속성 값을 찾을 수 있습니다.

content = box.select('p[class="cast_txt"]')[0].get_text()

이렇게 보다 쉽게 content라는 변수 안에 값을 저장할 수 있습니다. 한번 어떻게 나오는지 알아볼까요? 과연 우리가 원하는 값이 나올까요? 저는 저번처럼 Visual Studio Code를 통하여 작성하였고 Visual Studio Code Extension을 통해 디버그 해 보았습니다.

 

왼쪽 사진과 오른쪽 사진을 비교해보시기 바랍니다. 잘 나오는 것 같네요. 기존 4편과 같이 잘 나옵니다.


지금까지 파이썬 BeautifulSoup를 기반으로 둔 크롤링, 파싱 강좌를 마치겠습니다. 지금까지 읽어주신 모든 분들 감사합니다. 이번에는 저번 discord.py 강좌에 비해 많이 개선해보려고 해보았습니다. 기존 강좌는 뭔가 3편 이후 위키식 느낌이 오히려 더 난다고 생각했으며 이번에는 실제 예제를 통하여 많이 개선해보려고 노력해보았습니다. 혹시 지금까지 강의를 보면서 의견을 댓글로 작성해주시면 감사하겠습니다. 수고하셨고 지금까지 읽어주셔서 진심으로 감사합니다.

 

이 모든 코드는 제 깃허브에 올려두었으니 참고해두시면 좋을듯 합니다. 감사합니다.