YOGYUI

Python::BeautifulSoup - 기상청 '도시별 현재날씨' 크롤링 본문

Data Analysis/Data Engineering

Python::BeautifulSoup - 기상청 '도시별 현재날씨' 크롤링

요겨 2021. 2. 7. 16:33
반응형

[ Web Crawling (Python) ]

기상청 날씨누리 사이트의 '도시별 현재날씨' 정보를 pandas DataFrame 객체로 저장해보자

www.weather.go.kr/weather/observation/currentweather.jsp

 

도시별 현재날씨 > 지상관측자료 > 관측자료 > 날씨 > 기상청

홈 > 관측자료 > 지상관측자료 > 도시별 현재날씨 |날씨|관측자료|지상관측자료|도시별 현재날씨 기상실황표2021.02.07.16:00 기상실황표 강릉   6.6     7.1 1.9 4.7     70 북서 1018.9 강진군   4.0     12

www.weather.go.kr

1. HTML GET

requests 라이브러리를 사용해 해당 url의 html을 가져온다

import requests
from bs4 import BeautifulSoup

url = "https://www.weather.go.kr/weather/observation/currentweather.jsp"
html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")
print(soup)

html parser를 이용해 파싱한 soup 객체를 print해보면 다음과 같은 도입부를 확인할 수 있다

<!DOCTYPE html>

<html lang="ko">
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
        <title>도시별 현재날씨 &gt; 지상관측자료 &gt; 관측자료 &gt; 날씨 &gt; 기상청 </title>
        <link rel="shortcut icon" href="https://www.kma.go.kr/iphone-shortcut.png" />
        <link rel='apple-touch-icon-precomposed' href='https://www.kma.go.kr/iphone-shortcut.png'/> 
        <meta http-equiv="Content-Type" content="text/html; charset=euc-kr" />
        <link rel="stylesheet" type="text/css" href="/share/css/base.css?ver=20210128" />
        <link rel="stylesheet" type="text/css" href="/share/css/common.css?ver=20210128" />
        <link rel="stylesheet" type="text/css" href="/share/css/weather-layout.css?ver=20210128" />
        <link rel="stylesheet" type="text/css" href="/share/css/component.css?ver=20210128" />
        <link rel="stylesheet" type="text/css" href="/share/css/add_2018.css?ver=20210128" />
<!-- 후략 -->

우리과 관심있는 것은 현재 날씨 정보가 담긴 테이블이다

가져와야 할 테이블

테이블은 caption tag가 "기상실황표"인 <table>을 찾으면 된다

(class 이름이 "table_develop3"이다...웹개발자들이 개발 완료 후에 딱히 코드 정리는 안하는듯? ㅎㅎ)

<table class="table_develop3" summary="기상실황표로 지점, 날씨, 기온, 강수, 바람, 기압등을 안내한 표입니다.">
    <caption>기상실황표</caption>
    <colgroup>
        <col style="width:14%"/>
        <col style="width:12%"/>
        <col style="width:7%"/>
        <col style="width:5%"/>
        <col style="width:8%"/>
        <col style="width:5%"/>
        <col style="width:6%"/>
        <col style="width:5%"/>
        <col style="width:8%"/>
        <col style="width:5%"/>
        <col style="width:5%"/>
        <col style="width:8%"/>
        <col style="width:6%"/>
        <col />
    </colgroup>
    <thead>
    <tr id="table_header1" class="table_header">
        <th scope="col" rowspan="2" class="top_line">지점</th>
        <th scope="col" colspan="4" class="top_line" id="headers-weather">날씨</th>
        <th scope="col" colspan="3" class="top_line" id="headers-temp">기온(℃)</th>
        <th scope="col" colspan="3" class="top_line" id="headers-rain">강수</th>
        <th scope="col" colspan="2" class="top_line" id="headers-wind">바람</th>
        <th scope="col" class="top_line" id="headers-press">기압(hPa)</th>
    </tr>
    <tr id="table_header2" class="table_header">
        <th scope="col" class="nm" headers="headers-weather">현재일기&nbsp;</th>
        <th scope="col" class="nm" headers="headers-weather">시정<br/>km</th>
        <th scope="col" class="nm" headers="headers-weather">운량<br/>1/10</th>
        <th scope="col" class="nm" headers="headers-weather">중하운량</th>
        <th scope="col" class="nm" headers="headers-temp">현재<br/>기온</th>
        <th scope="col" class="nm" headers="headers-temp">이슬점<br/>온도</th>
        <th scope="col" class="nm" headers="headers-temp">체감<br/>온도</th>
        <th scope="col" class="nm" headers="headers-rain">일강수<br/>mm</th>
        <th scope="col" class="nm" headers="headers-rain">적설<br/>cm</th>
        <th scope="col" class="nm" headers="headers-rain">습도<br/>%</th>
        <th scope="col" class="nm" headers="headers-wind">풍향&nbsp;</th>
        <th scope="col" class="nm" headers="headers-wind">풍속<br/><script>writeWindSpeedUnit();</script></th>
        <th scope="col" class="nm" headers="headers-press">해면<br/>기압</th>
    </tr>
    </thead>
    <tbody>
<!-- 후략 -->

각 지점들의 기상 정보에 해당하는 테이블 행 태그 <tr>들은 다음과 같이 구성된다

<tr>
    <td>
        <a href="/weather/observation/currentweather.jsp?tm=2021.2.7.14:00&amp;type=t99&amp;mode=0&amp;reg=100&amp;auto_man=m&amp;stn=105" >
            강릉
        </a>
    </td>
    <td>&nbsp;</td>	
    <td>9.9</td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
    <td>9.1</td>
    <td>2.1</td>
    <td>7.4</td>
    <td>&nbsp;</td>	
    <td>&nbsp;</td>
    <td>62</td>
    <td>북동</td>
    <td><script>writeWindSpeed('3.0', false, '', '', 1);</script></td>
    <td>1017.9</td>
</tr>

<tr>
    <td>
        <a href="/weather/observation/currentweather.jsp?tm=2021.2.7.14:00&amp;type=t99&amp;mode=0&amp;reg=100&amp;auto_man=m&amp;stn=259" >
            강진군
        </a>
    </td>
    <td>&nbsp;</td	
    <td>7.2</td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
    <td>13.8</td>
    <td>0.4</td>
    <td>12.8</td>
    <td>&nbsp;</td>	
    <td>&nbsp;</td>
    <td>40</td>
    <td>북서</td>
    <td><script>writeWindSpeed('3.7', false, '', '', 1);</script></td>
    <td>1020.2</td>
</tr>
<!-- 후략 -->

데이터가 없는 (빈) 항목의 태그값은 &nbsp (non-breaking space)로 공백으로 구성되어 있다

2. Make DataFrame

위 정보를 토대로, 테이블 내 모든 <tr> 태그들에 대해 <td>들의 텍스트 리스트를 생성해 데이터프레임을 만들 수 있다

import requests
import pandas as pd
from bs4 import BeautifulSoup

url = "https://www.weather.go.kr/weather/observation/currentweather.jsp"
html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")
table = soup("table", "table_develop3")[0]

# 테이블 내 모든 <tr> 태그 리스트업
table_rows = table.find_all("tr")
# 최초 두 태그는 테이블 헤더로 사용됨, 이후는 모두 데이터 항목
table_headers = table_rows[:2]
table_data = table_rows[2:]
# 모든 데이터 행에 대해 <td> 항목 추출
table_data_elements = [x.find_all("td") for x in table_data]
# 리스트 만든 후 DataFrame 생성
data = []
for elem in table_data_elements:
    if len(elem) > 0:
        data.append([x.text for x in elem])
df = pd.DataFrame(data)
# 헤더 데이터 추출
header_text0 = [x.text for x in table_headers[0].find_all("th")]
header_text1 = [x.text for x in table_headers[1].find_all("th")]
header = [header_text0[0].replace('\r\n\t\t', '')] + header_text1
df.columns = header

df.head(5)

[결과]

    지점 현재일기    시정km  ...  풍향                    풍속writeWindSpeedUnit();    해면기압
0   강릉          6.6  ...   북서  writeWindSpeed('3.5', false, '', '', 1);  1018.9
1  강진군          4.0  ...   북서  writeWindSpeed('3.3', false, '', '', 1);  1020.4
2   강화    맑음  20 이상  ...  서남서  writeWindSpeed('2.4', false, '', '', 1);  1022.5
3   거제          5.6  ...  북북서  writeWindSpeed('1.5', false, '', '', 1);  1018.8
4   거창          9.3  ...   북서  writeWindSpeed('3.4', false, '', '', 1);  1018.1

[5 rows x 14 columns]

풍속의 경우 javascript 코드가 들어있는데 (writeWindSpeedUnit, writeWindSpeed), 해당 스크립트는 기상청 서버 내부에 정보가 들어있는 것 같아 대체할 수가 없다

<script src="/share/js/weather-common.js?ver=20210128"></script>

지점에 원하는 지역 이름을 매칭하여 필터링하면 해당 지역의 기상 정보를 가져올 수 있다

df[df['지점'] == '수원'].values

>> array([['수원', '연무', '8.2', '0', '0', '5.8', '0.0', '2.2', '\xa0', '\xa0',
          '66', '서', "writeWindSpeed('5.2', false, '', '', 1);", '1021.5']],
         dtype=object)

끝~!

반응형