[파이썬 챗봇] 1. 네이버 날씨 웹 크롤링(+디버깅)

지수·2021년 7월 14일
0

플레이데이터

목록 보기
19/50

미니 프로젝트 [파이썬 챗봇] 만들기를 통해 자신감이 조금..쪼끔 붙은 자의 리뷰 (2021.07.12)
이 제목을 달기 무섭게 예외사항 발견 오류 폭발..디버깅의 늪 시작🔧🔧🔧 (2021.07.13)

📢📢📢 코드리뷰 후 대규모 수정작업이 진행됐습니다! (2021.07.15)

[ 수정.ver로 갑시다! ]

대규모 수정이라 이제 이 글의 내용은 현 코드와 대부분 다르지만 이 글은 기록용으로 남겨두기로..ㅎ (코드는 싹 달라도 내용 자체는 동일합니다.)
이 글로 들어오셨다면 위에 링크를 타고 수정.ver로 가세욧!


(이 글만 몇번째 수정하는 건지 모르겠는데..처음에 박아놓은 이 사진 정말 민망 뻘줌..ㅎ)

💡 아이디어

[ 파이썬 챗봇 ] 사실 챗봇이라기보단 자동응답기..지만..핳
파이썬 미니 프로젝트를 검색하면 정말 많은 자료들이 쏟아져 나오지만, 프로젝트 팀원별로 사전지식 수준도 조금씩 다르고, 너무 어려운 것을 하면 클론 코딩 밖에 되지 않겠다는 생각에ㅜ 우리 힘으로 할 수 있는 프로젝트를 선정하고자 하였다..!

학부 시절 파이썬 교양 강의를 들으면서 조건문과 반복문을 통해 자판기, 야구 게임을 만들던 것이 생각났고, 이것을 좀 더 발전시켜 여러 기능을 탑재한 무언가를 만들면 좋겠다 생각했다. 별 것 없는 아이디어였는데, 팀원 분들과 함께 이야기하다보니 쭉쭉 이어져 👾챗봇👾이라는 그럴듯한 프로젝트를 만들 수 있게 되었다!

조건문, 반복문 등 파이썬 기초 문법 및 기능으로 여러 기능을 함수로 구현, 챗봇을 동작하게 하는 것이 해당 미니 프로젝트의 핵심이다.


초기 구상에서는 다음 기능들 구현을 목표로 했다.

  • 오늘 날씨 정보 출력
  • 날짜별 플레이데이터 시간표 출력
  • 교육 입실,퇴실 알림 출력
  • 점심메뉴 고르기(cf.결정장애를 위한 점심메뉴 고르기 표) + 랜덤 기능
  • 미니게임(터미널에서 진행하는 게임)

그리고 나는 이 중 오늘 날씨 정보를 크롤링하여 출력하는 부분을 맡았다.
전에 학부 수업에서 웹 크롤링을 경험해본 적이 있고, 이후에 다른 웹 개발 프로젝트에서 웹 크롤링 기술을 꼭 써보고 싶어서 도전했다.


💻 구현

0. 구상

처음에는 주소지 값을 사용자로부터 입력 받아서 해당 위치 날씨를 검색, 출력하고자했지만 나는...아직 말하는 감자이기 때문에.. 입력-검색-크롤링-출력+사이사이 예외사항 디버깅은 아직 힘들다는 판단을 내렸다..

▶ 코드 구현을 끝내고보니 request.get()으로 특정 url의 html을 가져올 때, url 내 검색 값을 받는 부분에 변수를 넣어서 실행하면 가능하지 않을까..!하는 생각이 들었다.
확인해 본 결과 가능하다! 다른 팀원 분께서 이 기능으로 오늘의 뉴스 타이틀을 크롤링하는 코드(news.py)를 구현하셨으니, 궁금하다면 요기(깃헙)로!
▶ 하지만 네이버 날씨 웹의 경우, 입력 받은 위치값에 대한 날씨 정보가 없어서 인근 위치의 날씨를 출력하거나 위치별로 특정 정보가 있거나 없거나..하기 때문에 변수가 많아질 것으로 예상, 디버깅을 많이 해줘야할 것 같다.
감자의 웹 크롤링 정복기..앞으로도 쭉 이어진다..to be continued..

이후 구글링을 통해 네이버에서 '날씨'를 검색하면 자동적으로 현재 위치를 파악하여 날씨 정보를 출력해준다는 사실을 확인했다. (얏호!)

때문에 BeautifulSoup을 통한 네이버 날씨 크롤링이 웹 크롤링 기초 예제로 많이 쓰이고 있었다. 공개된 예제 코드들은 참고용으로 사용하고 기능을 수정, 응용하여 코드를 작성하였다.
(네이버 날씨 페이지 html이 변경되어서인지 제대로 작동하지 않는 예제 코드도 더러 있었음으로 각 모듈과 함수의 기능을 파악하고 진행하는 것을 추천한다! 난이도가 엄청 높은 것은 아니라 나처럼 부딪혀보는 것도 굿ㅋㅋ)

🚩 네이버 날씨 웹 크롤링을 위해 설치해야하는 패키지

  • pip install bs4
  • pip install request

1. 구현 포인트

될 듯 말 듯 아리까리의 늪에 빠져 어제 하루 종일 코드를 실행, 수정, 실행, 수정 X ∞ 했다. (🤦 나란 사람..일 못 끝내면 못 자는 사람..)

사실 코드 자체가 엄청 어렵다거나 복잡한 것은 아니다.

✅ 다만, 처음에는 어떻게 특정 태그, 특정 클래스 값을 받아오는지 방법을 명확하게 알 지 못한 채 시작을 해서, 필요한 값을 제대로 받아오지 못하는 경우가 많았다.
▶ 값을 받아올 때마다 pprint()print()로 확인하다보니 노가다에서 오는 노하우..같은 게 생겼다.
▶ html에서 태그명이나 클래스명이 겹치는 경우도 많기 때문에, 처음 html을 파싱하여 받은 값(soup)에서 find()하는 것보다 필요한 값이 담겨있는 하위 영역을 부분집합처럼 변수(data1,data2,data3)에 받아와서 찾아야 값이 제대로 받아진다.

✅ 얼마 전에 생활코딩으로 html, css 강의를 수강하였는데, 브라우저 개발자 도구를 보면서 태그, 클래스를 찾아 find 위치를 지정하는데에 많은 도움이 되었다!

✅ 구글링해서 찾을 수 있는 웹 크롤링 예제 코드들도 도움이 많이 됐다. 크롤링하는 정보에 따라, 또 코드 작성자분에 따라 코드가 약간씩 달라서 각 메서드의 기능을 이해하는데에 도움이 많이 되었다. (서로 지식을 공유하는 개발자 문화..너무 멋있다..🤓)

✅ 그대로 하면 재미도 없고, 배움도 없기 때문에 최대한 내 힘으로 코드를 작성하려고 노력했다.
▶ 대부분의 네이버 날씨 크롤링 예제에서 포함하고 있지 않는 오전/오후 강수 확률, 미세먼지/초미세먼지/오존의 상태 정보('좋음','보통' 등)를 추가적으로 크롤링 했다.


2. 구현 상 문제점/유의점

여기서 충격적인 사실을 알게 되었는데,
미세먼지/초미세먼지/오존의 정보는 상태에 따라서 각각 다른 클래스에 담긴다..!
(좋음일 때 정보: class = lv1, 보통일 때 정보: class = lv2, ...)
= 상태 정보를 미리 예측할 수 없기 때문에, 클래스명을 'lv1' 혹은 'lv2'로 고정하여 해당 위치 값을 받아올 수 없음...

그래서 세부 클래스명을 지정해서 값을 받아오는 방식이 아니라,
상위 클래스 전체를 data3으로 받아오고, 인덱싱을 통해 분류한 다음
텍스트 정보만 get_text 메서드로 받아오는 방법을 택했다.
(코드는 하단 깃허브 링크 참고)

그렇게하면 수치 정보와 상태 정보가 한 번에 이어져 받아진다.
그래서 단위 기호를 기준으로 인덱스 슬라이싱하여 각각 변수에 넣어주었다.


그렇게 다 됐다고 생각하고 리뷰까지 적었을 무렵...다른 문제가 발생하는데...

위치, 시간에 따라 네이버 날씨 정보에 미세먼지, 초미세먼지, 오존 값이 나타나지 않을 때가 있는 것이었다...............
= 개발자 도구를 확인해보니
이럴 경우, 미세먼지,초미세먼지,오존 값을 가지고 있는 상위 태그, 클래스 자체가 존재하지 않아서 기존 코드대로 data3 값을 받아오려고하면 에러가 난다.

기존의 코드는
data3 = data1.findAll('dd') 하여 
data1 안에서 dd 태그를 가진 모든 값을 data3에 받아오고,

data3[0] => 미세먼지 정보(수치 정보 + 상태 정보) 
data3[1] => 초미세먼지 정보(수치 정보 + 상태 정보)
data3[2] => 오존 정보(수치 정보 + 상태 정보)

이런 식으로 인덱싱하여 값을 나눠 넣은 뒤,
슬라이싱해서 각 수치 정보와 상태 정보를 분리, 변수에 넣는 방식이다.

이럴 경우 발생하는 문제는 크게 아래 두가지이다. (코드 바꾸면서 자잘한 에러들은 덤..!짜릿..!)

1) data3에는 미세먼지, 초미세먼지, 오존 관련 정보 외에도 다른 값들이 존재한다. (아래는 pprint(data3)의 결과값이다.)

  • 미/초/오 정보가 존재할 때는 이 값이 맨 위에 존재하지만 ([0],[1],[2] 위치에 각각 존재),
  • 문제 상황 캡쳐 이미지처럼 해당 값이 존재하지 않을 때에는 다른 값이 해당 인덱스 위치에 존재한다.

2) 미/초/오 값이 매번 다 있거나 다 없거나 한 것이 아니라 특정 값만 없기도 한다. (미세먼지, 초미세먼지 값은 있는데 오존 값이 없다거나...)


🐛 디버깅

그래서 추가적으로 '예외를 처리하는 디버깅'을 실시했다.

먼저, 1)번 문제를 해결하기 위해 다음 내용으로 코드를 수정했다.

1.1) data3 값에 미/초/오 정보만 담기도록 find
(아래는 수정 후 print(data3)의 결과값이다. 미/초/오 정보만 담겨있는 것을 확인할 수 있다!)

1.2) 미/초/오 정보가 없을 경우, data3이 비어있을 것이기 때문에 data3을 인덱싱해서 변수에 할당하기 전에 if(data3) 구문으로 data3 값 유무 확인


위의 두 방법으로 수정 후에도 2)번 문제는 해결할 수가 없었다.
그래서 추가적으로 아래 2.1, 2.2 수정을 진행했다.

2.1) data3 전체 값이 null인지 아닌지를 볼 것이 아니라 각 미/초/오 값이 있어야 할 자리가 null인지 확인해야했기에
if문을 if(data3[n])으로 수정, 인덱싱하기에 앞서 [0],[1],[2] 자리에 값이 있는지 각각 확인

  • if(data3[n]) 가 true이면, 해당 정보를 슬라이싱해서 각각 수치 정보 변수, 상태 정보 변수에 할당
  • 그렇지 않은 경우 예외 상황(exception)이 발생했다는 의미로 excN (N=1,2,3) 변수에 숫자 1 저장

2.2) 미/초/오 변수를 포함한 문장을 출력할 때, 어떤 변수 하나가 비어있으면 오류가 발생하기 때문에 모든 변수 값이 제 값으로 차있을 때에만 문장을 출력하는 조건 설정

  • 날씨 정보를 크롤링하여 출력하는 def weather() 함수 내
    미/초/오 출력 부분에는 excN (N=1,2,3)이 단 하나도 1이 아닌 경우에만 출력이라는 조건 설정
    = exc1 + exc2 + exc3 = 0
  • 위 조건문을 사용할 수 있도록 if(data3[n]) 가 true 일 때 excN (N=1,2,3) 변수에 숫자 0 저장

이렇게 하면,
네이버 날씨 html에 미/초/오 정보가 모두 존재하지 않거나,
이 중 특정값만 존재하지 않을 때에도
weather() 함수가 에러 뜨지 않고 작동한다 🔥🔥🔥
(해당 상황에서는 미/초/오 값 없음 => 출력 안함)


📄 코드

내가 구현한 부분 코드는 크게 두 부분으로 나뉜다.

웹 크롤링 부분

: 웹 정보 find => 변수에 할당

함수 생성 부분

: 웹 정보를 담고 있는 변수들을 잘 엮어서 출력하는 함수 생성

처음에는 날씨 정보 출력 구현만 생각했었는데,
크롤링해서 받아온 변수를 다양하게 사용해보면 좋을 것 같아서 함수를 몇 개 더 만들었다.

  • weather() : 날씨 정보(위치와 기온, 오전/오후 강수 확률, 미/초/오) 출력
  • rain() : 현시간 확인 후, 이후 시간 강수 확률에 따른 알림
    (비가 올 것 같으니 우산을 챙기라는 알림)
    - 현재 오전이면 => 오전/오후 중 강수 확률이 50 이상일 때 알림
    - 현재 오후이면 => 오후 중 강수 확률이 50 이상일 때 알림
  • dress() : 기온별 옷차림 추천 출력

rain() 함수와 dress() 함수는 weather() 함수 내부에 포함되어 함께 실행됨

자세한 코드 내용은 깃허브로!


💬 리뷰

한 줄 요약 : "될 듯 말 듯 아리까리의 늪"이 성장의 핵심 동력이다?

미니 프로젝트나 토이 프로젝트를 꼭 진행해보고 싶었는데,
어떻게 해야하는지 모르겠어서 섣불리 시작하지 못했었다.

인터넷에 검색해서 나오는 프로젝트 주제들은 내가 배운 것과 거리가 먼 모듈에 대한 것들도 많았고, 난이도도 제각각이었다.

너무 어려운 프로젝트에 참여하면 좌절감을 느끼고 지쳐버리기마련인데
이 정도 난이도가 아주 적당했던 것 같다.

될 듯 말 듯한 부분을 잡고 오래 고민하고, 찾아보고하면서
예전에 학부 시절 프로젝트를 진행하다 막히면 아무것도 하지 못하던 나에서 많이 성장했구나 느낄 수 있었다.
(스킬이 많이 성장했다기보단..마인드가..? 스킬도 성장하긴 했을거야..그럼)

아쉬운 점

1) 네이버 위치 추적 정확도가 너무 떨어진다..
나는 인천에 있는데 자꾸 김포에 있다하고..;; 가끔은 경기도 광주(??!)에 있다고하기도 함ㅋㅋㅌ큐ㅠ

다음 기회에는 사용자로부터 위치 정보를 받아서 검색한 뒤 결과를 출력하는 코드를 짜봐야겠다.

2) 미/초/오 관련 디버깅에서 값이 다 있지 않더라도 있는 것만 받아서 출력하도록 코드를 짜는 것이 더 나았을까? 하는 생각이 든다.

사실 그렇게 되면 경우의 수가 2^3=8가지나 되고...
미/초/오 값이 없는 경우보다 있는 경우가 훠얼씬 더 많기 때문에
디버깅의 포커스를 값이 있는 변수만 출력 보다 값이 없는 변수가 있어도 에러 없이 동작에 두었다.

진행중인 미니 프로젝트에서 챗봇의 기능이 날씨 출력 밖에 없었다면 이 부분을 더 세밀하게 만들었겠지만, 여러 기능을 합쳐 동작하게 해야하는만큼 하나 하나 세밀하게 작동하는 것보다
여러 기능을 합쳐 실행해도 에러없이 잘 작동하게 하는 것이 더 중요하다고 생각했다.

이 부분에서 많이 헤매다가 지쳐버렸다는 것이 학계 정설...
(에러가 처음 발생했을 때는 어떤 문제 때문인지 찾는 데에만도 많은 시간이 걸렸다ㅜ)

이번 프로젝트의 weather()는 이렇게 마무리하고, 다음에 크롤링이 중심이 되는 프로젝트를 통해 세밀한 크롤링, 조건 설정을 학습해야겠다.

profile
사부작 사부작

0개의 댓글