728x90
목표 : 최근 7일간 코로나19 신규 확진자 및 지역별 누적 확진자 수를 크롤링하고 데이터 분석하여 시각화하기.
기한 : 2021년 8월 26일 오전 9시 ~ 오후 4시
평가기준 : 얼마나 배운 것을 잘 활용하여 목표를 수행하였는지.
# 필요한 라이브러리 불러오기.
import re # 정규표현식 사용할 때 필요
import time # 크롤링할 때 등 대기 시간 줄 때 필요
import json # folium에 이용할 때 등 json 파일 로드할 때 필요
import folium # 지도를 이용한 시각화 툴
import pymysql # python에서 mysql 사용하기 위해 임폴트
import unicodedata # 유니코드 데이터베이스에 대한 엑세스 제공
import pandas as pd # 판다스, 데이터 분석 및 전처리 도구
import matplotlib.pyplot as plt # 데이터 시각화 툴
from matplotlib import font_manager, rc # 데이터 시각화할때 한글 안깨지게 하기 위한 도구
from selenium import webdriver # 웹드라이버(크롬 드라이버)를 이용한 크롤링 도구
from bs4 import BeautifulSoup # bs4 웹 크롤링 도구
from sqlalchemy import create_engine # SQL 데이터베이스로 데이터 밀어넣기 위한 도구
from branca.element import Template, MacroElement #
## 드라이브 설정 및 url 설정
driver = webdriver.Chrome('./chromedriver.exe')
url = "https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EC%BD%94%EB%A1%9C%EB%82%98+%ED%98%84%ED%99%A9"
driver.get(url)
# 크롬 드라이버를 이용해서 최근 7일간 신규확진자수 데이터 크롤링
## 날짜 클릭하는 함수 및 확진자 수 불러오는 함수 만들기
def select_day(driver):
time.sleep(1)
day = driver.find_element_by_css_selector("dd.value")
day.click()
time.sleep(2)
def get_content(driver):
html = driver.page_source
soup = BeautifulSoup(html, 'html.parser')
try:
day = "2021-0" + soup.select("dd.date._x_value")[0].text.replace(".", "-")
cnt = soup.select("dd.desc_em._total")[0].text.replace(",", "")
except:
cnt = " "
day = " "
return (day, cnt)
### 가져온 날짜 및 확진자 수를 담을 리스트 생성
weeks_cnt = []
### 반복문을 통해 가져온 데이터 리스트에 튜플 형식으로 담기
for _ in range(7):
select_day(driver)
weeks_cnt.append(get_content(driver))
### 리스트를 데이터프레임으로 바꾸기
newly = pd.DataFrame(weeks_cnt, columns = ['date', 'decide_cnt'])
newly['date'] = newly['date'].astype('datetime64')
newly['decide_cnt'] = newly['decide_cnt'].astype('int')
# 시각화 툴 폰트설정
font_path = './malgun.ttf'
font_name = font_manager.FontProperties(fname=font_path).get_name()
rc('font', family=font_name)
# plt를 이용한 라인 그래프
plt.plot(newly.date, newly.decide_cnt)
plt.title('최근 일주일간 신규확진자 추이')
plt.xlabel('날짜')
plt.ylabel('신규 확진자 수')
plt.show()
# 데이터프레임 엑셀로 저장
newly.to_excel('./newly.xlsx', index=False)
# 판다스 read_html을 이용해서 지역별 확진자 테이블 가져오기
table = pd.read_html(url)
### 3개로 나눠진 테이블 합쳐주기
regional = pd.concat([table[0], table[1], table[2]])
regional = regional.reset_index().drop('index', axis=1)
### 컬럼명 설정
regional.rename(columns={'지역':'location', '누적확진자':'acc_decide_cnt', '신규확진자':'regional_decide_cnt'}, inplace=True)
### 검열(해외입국) 행 제거해주기
regional = regional.drop(8).reset_index().drop('index', axis=1)
regional.to_excel('./regional.xlsx', index=False)
# pymysql을 이용해서 SQL문으로 데이터 저장하기
### pymysql 라이브러리 불러오기
### 데이터베이스에 연결 하고 커서 가져오기
conn = pymysql.connect(host='localhost', user='root', password = '1234', charset = 'utf8')
cur = conn.cursor()
### 데이터베이스(스키마) 만들기
cur.execute("CREATE SCHEMA IF NOT EXISTS first_mini_project")
### 만든 데이터베이스 연결하기
conn = pymysql.connect(host='localhost', user='root', password = '1234', db = 'first_mini_project', charset = 'utf8')
cur = conn.cursor()
### 테이블 만들기
cur.execute("CREATE TABLE IF NOT EXISTS newly (date char(4), decide_cnt int)")
cur.execute("CREATE TABLE IF NOT EXISTS regional (location char(4), acc_decide_cnt int, regional_decide_cnt int)")
#### 테이블 컬럼 형식 바꿔주기 ; 아래서 데이터 입력할때 충돌나서 수정해준것.)
cur.execute("ALTER TABLE newly MODIFY decide_cnt MEDIUMTEXT")
### 데이터 프레임을 sql로
db_connection_str = 'mysql+pymysql://root:1234@localhost/first_mini_project'
engine = create_engine(db_connection_str, echo=False)
newly.to_sql('newly', con=engine, if_exists='append', index=False)
regional.to_sql('regional', con=engine, if_exists='append', index=False)
### 데이터베이스에서 데이터 불러오기
newly_from_db = pd.read_sql_table('newly', con=engine)
newly_from_db
regional_from_db = pd.read_sql_table('regional', con=engine)
regional_from_db
# 데이터 분석
regional_sum = pd.DataFrame([['전체', regional['acc_decide_cnt'].sum(), regional['regional_decide_cnt'].sum()]], columns=['location', 'acc_decide_cnt', 'regional_decide_cnt'])
regional_sum = pd.concat([regional, regional_sum], ignore_index=True)
regional_sum
### 전체 누적 확진자의 지역별 비중
regional_sum['acc_percentage'] = (regional_sum['acc_decide_cnt']/regional_sum['acc_decide_cnt'].iloc[-1])*100
regional_sum
### 신규 확진자의 지역별 비중
regional_sum['daily_percentage'] = (regional_sum['regional_decide_cnt']/regional_sum['regional_decide_cnt'].iloc[-1])*100
regional_sum
### 확진자 지역별 비중 증감률
regional_sum['up/down'] = (regional_sum['daily_percentage'] - regional_sum['acc_percentage'])
regional_sum['rate_of_change'] = (regional_sum['daily_percentage'] / regional_sum['acc_percentage']) * 100 -100
regional_sum
## 확진자가 증가 중인 도시 확진자 관련 뉴스 알아보기 - 뉴스 크롤링
def get_news(driver, url):
title = []
driver.get(url)
html = driver.page_source
soup = BeautifulSoup(html, 'html.parser')
try:
for i in range(5):
title.append(soup.select('a.news_tit')[i].text)
except:
title = '실패'
return title
search_url = 'https://search.naver.com/search.naver?where=news&sm=tab_jum&query=확진자+{}'
for i in range(len(regional_sum)):
if regional_sum['rate_of_change'][i] > 0:
print(regional_sum['location'][i], " : ", get_news(driver, search_url.format(regional_sum['location'][i])))
# 데이터 시각화 (데이터 베이스 이후)
## 위도, 경도 데이터 전처리
geo_df = pd.read_excel('./행정_법정동 중심좌표.xlsx')
geo_df.head()
geo_df = geo_df.drop(columns=['코드', '시군구', '읍면동', '하위', '코드종류'])
geo_df['시도'] = geo_df['시도'].drop_duplicates()
geo_df = geo_df.dropna()
geo_df = geo_df.sort_values("시도", ignore_index=True)
geo_df
regional = regional.sort_values(by=['location'], axis=0).reset_index().drop('index', axis=1)
final_df = pd.concat([regional, geo_df], axis=1, join="inner")
final_df
## folium을 이용한 데이터 시각화 (1)
geojson_file_path = './korea_geo.json'
korea_geo = json.load(open(geojson_file_path, encoding='utf-8'))
korea_geo['features'][0]['properties']
covid_mean = final_df['acc_decide_cnt'].mean()
print(covid_mean)
covid_bubble = folium.Map(
location=[36.607996, 127.286277], tiles ='CartoDB positron', zoom_start=6)
def style_function(feature):
return {
'opacity': 0.1,
'weight': 1,
'color': 'white',
'fillOpacity':0,
'dashArray': '1, 1',
}
folium.GeoJson(
korea_geo,
style_function=style_function
).add_to(covid_bubble)
for idx in final_df.index:
lat = final_df.loc[idx, '위도']
lng = final_df.loc[idx, '경도']
count = final_df.loc[idx, 'acc_decide_cnt']
if count > covid_mean:
fillColor = '#de6464'
else:
fillColor = '#6499de'
folium.CircleMarker(
location=[lat, lng],
color='#ffffff',
fill_color=fillColor,
fill_opacity=1.0,
weight=0.1,
radius=count/1500,
).add_to(covid_bubble)
template = """
{% macro html(this, kwargs) %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#maplegend" ).draggable({
start: function (event, ui) {
$(this).css({
right: "auto",
top: "auto",
bottom: "auto"
});
}
});
});
</script>
</head>
<body>
<div id='maplegend' class='maplegend'
style='position: absolute; z-index:9999; border:2px solid grey; background-color:rgba(255, 255, 255, 0.8);
border-radius:6px; padding: 10px; font-size:14px; right: 20px; bottom: 20px;'>
<div class='legend-title'>우리나라 누적 확진자 분포</div>
<div class='legend-scale'>
<ul class='legend-labels'>
<li><span style='background:#de6464;'></span>전체 평균 이상</li>
<li><span style='background:#6499de;'></span>전체 평균 이하</li>
</ul>
</div>
</div>
</body>
</html>
<style type='text/css'>
.maplegend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
.maplegend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
.maplegend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 18px;
margin-bottom: 2px;
}
.maplegend ul.legend-labels li span {
display: block;
float: left;
height: 16px;
width: 30px;
margin-right: 5px;
margin-left: 0;
border: 1px solid #999;
}
.maplegend .legend-source {
font-size: 80%;
color: #777;
clear: both;
}
.maplegend a {
color: #777;
}
</style>
{% endmacro %}"""
macro = MacroElement()
macro._template = Template(template)
covid_bubble.get_root().add_child(macro)
covid_bubble
korea_geo_2 = json.load(open(geojson_file_path, encoding='utf-8'))
covid_choropleth = folium.Map(
location=[36.607996, 127.286277],
tiles='CartoDB positron',
zoom_start=6
)
folium.Choropleth(
geo_data=korea_geo_2,
data=final_df,
columns=['시도', 'acc_decide_cnt'],
fill_color = 'BuGn',
fill_opacity=1.5,
line_opacity=0.2,
key_on='feature.properties.CTP_KOR_NM',
legend_name="지역별 누적 확진자 수"
).add_to(covid_choropleth)
folium.LayerControl().add_to(covid_choropleth)
for idx in final_df.index:
lat = final_df.loc[idx, '위도']
lng = final_df.loc[idx, '경도']
count = final_df.loc[idx, 'regional_decide_cnt']
folium.CircleMarker(
location=[lat, lng],
color='#bd1919',
fill_color=fillColor,
fill_opacity=0.0,
weight=1.1,
radius=count/30,
).add_to(covid_choropleth)
covid_choropleth
'미니 프로젝트' 카테고리의 다른 글
AI 허브 - 피트니스 자세 이미지 데이터 읽기 (0) | 2022.01.07 |
---|---|
두 번째 미니 프로젝트 (1) | 2021.09.12 |
두 번째 미니 프로젝트 - 21.09.9(목) (0) | 2021.09.09 |
두 번째 미니 프로젝트 - 21.09.8(수) (0) | 2021.09.08 |
미니 프로젝트(1) (0) | 2021.08.20 |