어제 오후에 이어 오늘 15시까지 프로젝트를 기획하고 구현하였다.
우리 조의 주제는 재난 문자를 활용한 지역별 자연재해 발생 빈도를 확인하는 코드를 작성하였다. 하지만, 코드를 작성하면서 생각해 보니 발생 빈도를 분석해도 대비하기도 어렵고, 활용 방안이 마땅히 떠오르지 않았다. 그렇기 때문에 오늘(25일) 다시 주제를 선정했다. 바뀐 주제는 긴급 재난 문자와, 실종 안내 문자의 여론을 비교하여 여론이 좋지 않은 긴급 재난 문자의 개선점을 제안하는 것이다.
그렇기 때문에 변경한 주제로 다시 프로젝트를 기획하기 시작했다. 다른 조에 비해 도중에 주제가 바뀐 우리 조에게는 절대적인 시간이 많은 편은 아니었다. 원래라면 내가 코드의 전반적인 부분을 작성하고 팀원들에게 마무리를 부탁했을 텐데, 이번 경우에는 시간이 없어 업무를 분담하여 처음부터 진행할 수 밖에 없었다. 다행히도 재난 문자 크롤링에 대한 코드는 대부분 작성하여서, 해당 코드를 공유하고, 수업시간에 배운 코드들로 모든 기능들을 구현하였다. 다음은 기능별 코드에 대해 살펴보자.
1. 재난 문자 크롤링 코드
재난 문자를 크롤링하기 앞서 재난 문자를 제공해주는 사이트의 url을 분석할 필요가 있었다.
해당 웹사이트에서 1년치 재난 문자에 대한 데이터를 수집할 수 있었다. 여기서 유심히 봐야하는 부분은 currentPage이다. currentPage의 값을 변경한다면 다음 페이지로 넘어갈 수 있기 때문이다. 1년치 데이터를 제공해주다보니 수많은 페이지들로 구성되어 있었다. 이 currentPage의 값을 1씩 증가시켜주면 결국 마지막에 도달할 수 있다.
코드를 살펴보자.
크롤링에 필요한 라이브러리들을 호출한다.
크롬에 접속하기 위한 설정을 해주고, 이제부터 본격적인 코드를 살펴보자.
아래의 코드는 재난 문자의 모든 데이터를 수집하기 위한 함수이며, 같은 조인 최정훈님께서 작성하신 코드이다.
def m_collector(currentPage):
# 기본 셋팅 - 1페이지부터 시작
url = f'https://www.safetydata.go.kr/disaster-data/disasterNotification?searchStartDttm=&searchEndDttm=&keyword=&orderBy=¤tPage={currentPage}&cntPerPage=10&pageSize=10'
driver.get(url)
html_source = driver.page_source
soup = BeautifulSoup(html_source,'html.parser')
pre_total = soup.find('p', {'class': 'board-count'}).text
total = int(re.sub('[^0-9]','', pre_total))
if total % 10 == 0:
last_page = total // 10
else:
last_page = (total // 10) + 1
data_pages = []
print('재난문자 수집 시작...')
for i in range( 1, last_page + 1 ):
SCROLL_PAUSE_TIME = 3
currentPage = str(i)
new_url = f'https://www.safetydata.go.kr/disaster-data/disasterNotification?searchStartDttm=&searchEndDttm=&keyword=&orderBy=¤tPage={currentPage}&cntPerPage=10&pageSize=10'
driver.get(new_url) # url 띄우는거
time.sleep(3) # 3정도 쉬기
# 사람인 척 하기 - > 동적 이벤트 주기 - > 스크롤 내리기(자바스크립트 명령어)
driver.execute_script('window.scrollTo(0,800)')
time.sleep(3)
# 데이터 수집 시작
html_source = driver.page_source # 열려있는 html소스 받기
soup = BeautifulSoup(html_source,'html.parser')
# 제목 추출
titles = soup.find_all('a', {'class':'tableHover'})
title_list = [title.text for title in titles] # for문은 리스트안에서 실행
# 등록일 추출
crol_dates = soup.find_all('td', {'class':'cell-date'})
crol_list = [ crol_date.text for crol_date in crol_dates ]
#데이터 프레임 저장
df = pd.DataFrame({'제목':title_list, '등록일':crol_list})
print(f'{i}페이지 자료 수집 완료')
data_pages.append(df)
data = pd.concat(data_pages, ignore_index=True)
data.to_csv(f'재난문자_.csv', encoding='utf-8-sig')
print('문자 수집 완료')
해당 코드를 자세히 분석해보자.
# 기본 셋팅 - 1페이지부터 시작
url = f'https://www.safetydata.go.kr/disaster-data/disasterNotification?searchStartDttm=&searchEndDttm=&keyword=&orderBy=¤tPage={currentPage}&cntPerPage=10&pageSize=10'
driver.get(url)
html_source = driver.page_source
soup = BeautifulSoup(html_source,'html.parser')
pre_total = soup.find('p', {'class': 'board-count'}).text # 전체 재난 문자의 개수가 포함된 태그를 추출
total = int(re.sub('[^0-9]','', pre_total)) # 추출된 태그에서 전체 재난 문자의 개수만 추출
if total % 10 == 0:
last_page = total // 10
else:
last_page = (total // 10) + 1
# 전체 게시물로 전체 페이지 수를 계산
data_pages = []
print('재난문자 수집 시작...')
첫 화면인 1페이지를 설정해준다. 1페이지에서 얻어야 하는 정보는 전체 재난 문자의 수 이다. 이것을 구해야 하는 이유는 1페이지에 10개의 게시물이 표시되고, 전체의 게시물로 전체 페이지 수를 가늠한다. 한 페이지에 10개의 게시물이 표시되는데 10으로 나누어 떨어지면 페이지가 넘어가지 않지만, 만약 나누어 떨어지지 않는다면 전체 페이지에서 1개의 페이지를 더 추가해야한다.
for i in range( 1, last_page + 1 ):
SCROLL_PAUSE_TIME = 3
currentPage = str(i)
new_url = f'https://www.safetydata.go.kr/disaster-data/disasterNotification?searchStartDttm=&searchEndDttm=&keyword=&orderBy=¤tPage={currentPage}&cntPerPage=10&pageSize=10'
driver.get(new_url) # url 띄우는거
time.sleep(3) # 3정도 쉬기
# 사람인 척 하기 - > 동적 이벤트 주기 - > 스크롤 내리기(자바스크립트 명령어)
driver.execute_script('window.scrollTo(0,800)')
time.sleep(3)
# 데이터 수집 시작
html_source = driver.page_source # 열려있는 html소스 받기
soup = BeautifulSoup(html_source,'html.parser')
# 제목 추출
titles = soup.find_all('a', {'class':'tableHover'})
title_list = [title.text for title in titles] # for문은 리스트안에서 실행
# 등록일 추출
crol_dates = soup.find_all('td', {'class':'cell-date'})
crol_list = [ crol_date.text for crol_date in crol_dates ]
이전 단계에서 전체 게시물 수에서 전체 페이지 수를 계산했다면 해당 페이지까지 도달할 때까지 반복해주어야 한다.
이번 코드에서 주의깊게 봐야하는 부분은 제목 추출과 등록일 추출이다.
제목은 A태그의 문자 값이고, tableHover라는 클래스를 가지고 있다. 그렇기 때문에 BeautifulSoup 라이브러리에서 제공하는 find_all 함수를 사용하여 태그는 A이고, 클래스가 tableHover인 html객체를 가져온다. 가져온 html객체의 텍스트 값만 추출하여 리스트에 추가한다. 이때 사용하는 방법이 리스트 조건 제시법(List Comprehension)이다. 다시 한번 짚어보자면, 리스트 조건 제시법은 리스트에 직접 요소를 추가하는 것이 아닌 리스트에 조건을 추가하여 조건에 해당하는 요소들을 집어넣는 방법을 이야기한다.
제목을 추출하였으니 등록일도 추출하면, 등록일은 테이블 태그의 요소 중 하나이다. 또 cell-date 클래스 속성을 가지고 있기 때문에 쉽게 찾을 수 있다. 제목을 추출한 방법과 같은 방법으로 추출한 뒤 리스트에 동일하게 추가한다.
#데이터 프레임 저장
df = pd.DataFrame({'제목':title_list, '등록일':crol_list})
print(f'{i}페이지 자료 수집 완료')
data_pages.append(df)
data = pd.concat(data_pages, ignore_index=True)
data.to_csv(f'재난문자_.csv', encoding='utf-8-sig')
print('문자 수집 완료')
마지막으로 데이터프레임으로 만들어주고, 값을 추가해준다. 여기서 주의할 점은 들여쓰기다. 위의 세 줄은 들여쓰기가 되어 있는 상태이고, 아래의 세 줄은 들여쓰기가 안되어있다. 위의 세 줄은 제목과 등록일을 가져온 for문에 속한 코드이기 때문에 들여쓰기를 꼭 해주어야 한다. 데이터프레임이 완성되면 csv파일로 저장해준다. 한글이 포함되어 있는 csv파일이기 때문에 인코딩 방식을 지정해주어야 한다.
m_collector(1)
driver.close()
마지막으로, 선언한 함수를 호출해주고, 작업이 끝나게 되면 드라이버를 중지 시키면 모든 작업이 종료된다.
2. 재난 문자와 실종 문자에 대한 감성 분석
재난 문자와 실종 문자에 대한 유튜브 영상들을 불러와 댓글들을 단어 단위로 분리하여 시각화하기도 하였다. 해당 방법은 지난 게시물에서 모두 설명 했기 때문에 링크를 참고하자
13일차 게시물을 참고하면 워드클라우드의 마스킹에 대한 이야기도 나오니 해당 부분을 참고하자.
해당 그림은 실종 문자에 대한 선한 영향력에 사람들이 좋은 댓글을 달았다는 것을 확인할 수 있고,
해당 그림으로 긴급 재난 문자에 대한 부정적인 여론을 확인할 수 있다.
그렇다면 긍정적인 여론과 부정적인 여론을 판단할 수 있는 기준이 있을까?
위의 사진은 14800여개의 단어들에 가중치를 부여하여 해당 단어가 좋은 의미로 사용되는지, 부정적으로 사용되는지 알 수 있게 해준다. sentiword_info라는 json파일을 읽어와 데이터 프레임으로 만들면 위의 사진과 같이 나오게 된다. 해당 프로젝트에서 사용하는 감성사전은 군산대학교 Data Intelligence 랩실에서 정의한 감성 사전을 이용하여 감성 분석을 진행하였다.
여론을 조사하기 위해 유튜브 댓글을 크롤링하였고, 해당 내용은 중복되는 내용이기 때문에 생략하도록 한다. 크롤링한 댓글 데이터프레임은 csv 파일로 저장하고, 저장된 csv파일을 불러와 데이터 프레임으로 다시 만들게 된다.
얼추 보이는 댓글만 보더라도 긴급 재난 문자에 대한 여론이 안좋은 것을 확인할 수 있다.
다음은 불용어 처리를 한다. 불용어 처리란, 중요하지 않은 단어를 모두 삭제하는 과정을 말한다. 이러한 과정을 거치면서 의미 없는 데이터들을 삭제하고, 유의미한 데이터만을 취급할 수 있게 된다.
불용어 처리까지 완료한 단어들은 감성 분석에 들어가게 된다. 위에서 불러온 감성 사전을 기반으로 댓글들의 가중치를 계산하게 된다. 부정적이면 음수의 값으로, 긍정적이면 양수의 값을 가진다. 이렇게 감성 분석을 진행하게 되는데, 만약 결측값이 있게 되면 빈 문자열로 대체하게 된다. 그 결과 데이터프레임을 확인해보면 댓글 별 감성 점수 총합을 확인할 수 있게 된다.
이런 감성분석을 통해 위에서 확인했던 워드 클라우드를 만들 수 있게 되었다.
3. 발표
내가 발표를 맡게 되었다.
모든 조가 발표를 끝내고, 미니 프로젝트에 대한 시상식이 이어졌다. 시상식을 할 때 교육담당자님과 계속 눈이 마주쳤다. 그래서 내심 기대했는데, 결국 우수 프로젝트로 선정됐다. 너무 뿌듯했고, 조원들을 많이 챙겨주지 못한 것 같은 아쉬움이 계속 남았었다.
상금은 회식비 10만원이었다. 상금은 고생한 우리 조원들과 함께 맛있는 회식으로 사용될 예정이고, 이번 미니 프로젝트를 시작으로 앞으로의 프로젝트에서 모두 성공할 수 있게 노력할 것이다.
'ABC부트캠프 데이터 탐험가 과정' 카테고리의 다른 글
[18일차] ABC 부트캠프 Numpy를 이용한 데이터 가공 (0) | 2024.07.30 |
---|---|
[17일차] ABC 부트캠프 취업 역량 강화 활동 (0) | 2024.07.28 |
[15일차] ABC 부트캠프 이미지 크롤링 및 프로젝트 주제 선정 (0) | 2024.07.24 |
[14일차] ABC 부트캠프 동적 크롤링을 이용한 노래 가사 분석 (11) | 2024.07.24 |
[13일차] ABC 부트캠프 동적 크롤링 및 시각화 (1) | 2024.07.23 |