[파이썬 챗봇] 3. 웹 크롤링 대규모 수정

지수·2021년 7월 15일
0

플레이데이터

목록 보기
21/50

오늘은 나눠서 구현 중인 코드를 모아서 main.py에 연결하고 실행하면서 다같이 디버깅, 코드 리뷰 & 디벨롭, 기술서 작성을 했다.

우리 팀이 만든 👾파이썬 챗봇👾의 최종.ver 기능은 다음과 같다.
(이 챗봇은 플레이데이터 교육 수강자들을 사용대상으로 기획한 것이라 관련 기능을 포함한다.)

  • 알람) 입퇴실 알림+QR코드, 오늘의 운세 알림
  • 날씨) 네이버 날씨 웹 크롤링 후 출력, 강수확률 알림, 기온별 옷차림 추천
  • 스케쥴) 교육 일수, 교육 과목 및 세부 주제 출력, 수료 D-DAY count
  • 점심) 점심 메뉴 고르기(+랜덤기능), 점심 값 내기 사다리타기
  • 미니게임) 기억력 테스트, 용어 복습 애나그램, 용어 복습 행맨
  • 코로나) 네이버 코로나 웹 크롤링, 오늘 확진자와 총 확진자 크롤링 후 출력
  • 뉴스) 네이버 뉴스 웹 크롤링, 키워드 입력 받아 뉴스 검색, 관련 최신 뉴스 헤드라인 5개 출력


🔔 수정사항

오늘 팀원분들과 서로 코드 설명, 코드 리뷰를 하면서 각자의 특성이 묻어나는 다양한 코드를 볼 수 있었다.
비슷한 반복문, 조건문을 만들 때에도 각자 자기만의 스타일로 코드를 구성했음을 알 수 있었다.

다른 분들이 작성한 코드를 보니, 내 코드에서 부족한 점이 더 잘 보였다.
그래서 다음 3가지를 수정하기로 결정했다.


1. 포맷팅(.format())

다른 팀원분들의 코드를 보면서 다른 팀원분들 코드에는 다 있고, 내 코드에만 없는 것을 찾았다. 바로 포멧팅!

내가 만든 weather() 함수의 경우 크롤링으로 가져온 값을 모두 출력하기 때문에 출력문이 되게 많은데, 포멧팅 없이 문자열과 변수를 +로 연결하여 작성해두었다.

포멧팅을 처음 접한 것은 아니지만 습관이 안되어서인지 잘 사용하게 되지 않았는데,
확실히 한 print문 안에 여러 변수와 문자열을 출력할 때는 훨씬 편했다.
포멧팅을 사용하는 것이 코드 가독성도 높여주는 것 같아서 문자열과 변수를 같이 출력하는 부분을 다 포맷팅으로 수정했다!

2. 크롤링(.select())

(1번 수정 정도만 있었으면 대규모 수정이라하지 않았겠지..ㅎ?)

파이썬 챗봇에 탑재된 기능 중 웹 크롤링 과정을 포함하는 기능은 총 세 가지이다.
네이버 날씨 웹을 크롤링하는 weather.py
네이버 코로나 웹을 크롤링하는 corona.py
네이버 뉴스 웹을 크롤링하는 news.py

이 중 weather.pycorona.py 는 내가 구현했고,
news.py 는 다른 팀원분께서 뉴스 기사 크롤링 유튜브 영상을 참고하여 구현해주셨다.


[ 1편 네이버 날씨 웹 크롤링 리뷰 ]에 보면 코드 구현 초기에 아래와 같은 어려움을 겪었다고 말했었는데,

뉴스 크롤링을 하신 팀원분은 나와 다른 방법으로 값을 크롤링 해오신 것을 확인했다..!
(나는 find(), findAll()을 사용했는데, 그 분께서는 select_one(), select()를 사용하셨다.)

find()를 사용한 기존 코드보다 select()를 이용한 코드가 가진 장점

  • 훨씬 간결하고 가독성도 높았으며,
  • 중간 중간 데이터를 data1 data2 등으로 쪼갤 필요없이,
    select 조건을 태그.상위 클래스명 > 태그.하위 클래스명으로 자세히 설정할 수 있음!

👩‍💻 bs4 find(), select() 참고 자료


select() 메서드를 쓰는데 익숙하지 않아서 수정이 정말 오래 걸렸다.

그치만, select() 메서드를 사용하면 아래와 같이 개발자 도구를 사용해서 확인할 수 있는 값을 바로 인자로 넣어 해당 위치 값을 가져올 수 있어 사실상 더 편하다!

(참고했던 여러 자료 중에 "정신건강을 위해 그냥 select()문을 쓰세요"라는 말이 있었다..)

3. 반복되는 부분 수정

기존 코드에서는 미세먼지, 초미세먼지, 오존 정보를 받아와서 각각 수치 값과 상태 값으로 넣어주는 코드를 다 따로 작성했었다.
+) find()를 썼었기 때문에 data3 값을 따로 받아와야했고,
+) 예외처리를 위해 if-else문도 있고, excN (N=1,2,3) 값 할당도 해야했다.

<< 기존의 장황한 코드..>>

data3 = data1.find('div', {'class': 'detail_box'}).findAll('dd')

# 미세먼지
if (data3[0]):
    f_data = data3[0].get_text()
    idf = f_data.find('㎥')
    fine_dust = f_data[:idf+1]
    f_state = f_data[idf+1:]
    exc1 = 0
else:
    exc1 = 1

# 초미세먼지
if(data3[1]):
    fu_data = data3[1].get_text()
    idf = fu_data.find('㎥')
    fine_ultra_dust = fu_data[:idf+1]
    fu_state = fu_data[idf+1:]
    exc2 = 0
else:
    exc2 = 1

# 오존
if(data3[2]):
    o_data = data3[2].get_text()
    idf = o_data.find('m')
    ozone = o_data[:idf+1]
    o_state = o_data[idf+1:]
    exc3 = 0
else:
    exc3 = 1

그런데 select() 함수로 바꾸고 나니

  • data3 값을 따로 받아올 필요도 없었고
  • 미세먼지, 초미세먼지, 오존 값을 받아올 때도 인덱스 부분 숫자만 바꿔주면 됐다.

그래서 반복되는 부분을 for문으로 묶었다..!
저 길었던 코드가 12줄..!로 수정되었다 짝짝짝

< for문 사용 방식 >

  • for문 안에서 한번 반복될 때마다 미세먼지, 초미세먼지, 오존 값을 받아오고 슬라이싱해서 리스트에 넣어지도록 했다.
  • 미세먼지/초미세먼지/오존 정보의 수치값은 n_list에 담고, 상태값은 s_list에 담았다.
  • 중간에 수치값과 상태값을 슬라이신하는데 필요한 구분자도 미리 리스트에 넣어 반복문 진행시 하나씩 빼내 쓸 수 있도록 했다.

< 미/초/오 값이 없을 때 >
for문을 돌기 이전에 미/초/오 값이 다 있는지(간혹 네이버 웹에 해당 값이 없기도 함) 확인하는 조건문을 두었다!
if len(soup.select('dl.indicator > dd') == 3: 으로 값이 모두 있을 때만 아래 반복을 진행하도록 설정했다.

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



💬 리뷰

한 줄 요약 : 수정은 힘들어도 더 나은 결과를 가져온다!

처음에 select()문으로 바꾸면서는 자꾸 에러가 뜨고 제대로 작동이 안되길래 구현 잘되던 코드를 괜히 바꾸는 삽질을 하고 있는걸까?하는 생각이 들었는데 코드 수정을 마치고 보니 수정하길 정말 잘했다는 생각이 든다.

지금은 그래도 만드는 코드 자체가 엄창 복잡하지도 않고, 양도 많지 않아서
처음 코드를 만드는데도, 디버깅을 하는데도, 전면 수정을 하는데도 시간이 정말 많이 걸리는 것은 아니지만 앞으로는 긴 수정을 앞두고 진지하게 고민하게되는 시점이 많이 올 것이라는 것을 안다.

그럴 때마다 지금 이 때를 생각해야겠다.
처음에는 막막하더라도 다 해놓고보면 하길 정말 잘했다고 생각하는 이 순간!

profile
사부작 사부작

0개의 댓글