OpenAPI를 이용한 현재 기상(기온, 강수량, 바람성분, 습도, 강수형태, 풍향, 풍속) 정보 얻기

공공데이터포털(data.go.kr)에서 “동네예보”로 검색하면 아래와 같은 결과 중 기상청_동네예보 조회서비스에 대한 활용신청 버튼을 클릭하여 OpenAPI 사용 신청을 합니다.

저 같은 경우 고맙게도 사용 신청 후 즉시 사용할 수 있었는데요. 마이페이지에서 오픈API의 개발계정을 클릭하면 앞서 신청한 서비스가 표시됩니다. 표시되는 서비스 중 앞서 신청한 서비스의 제목을 클릭하면 이 중 초단기실황조회의 확인 버튼을 클릭하면 아래와 같은 화면이 표시됩니다.

현재의 기상 정보를 얻을 목적이므로 위의 서비스 중에서 초단기실황조회에 대한 미리보기의 확인 버튼을 클릭하면 다음과 같이 테스트해 볼 수 있는 UI가 표시됩니다.

서비스 키와 날짜, 시간, 위치값(위의 경우 nx와 ny로 지정하며 광진구 화양동)을 입력하고 미리보기 버튼을 클릭하면 아래의 결과가 표시됩니다.



   
00 NORMAL_SERVICE
XML 20210526 1200 PTY 61 126 0 20210526 1200 REH 61 126 37 20210526 1200 RN1 61 126 0 20210526 1200 T1H 61 126 22.6 20210526 1200 UUU 61 126 1.9 20210526 1200 VEC 61 126 210 20210526 1200 VVV 61 126 3.3 20210526 1200 WSD 61 126 3.8 10 1 8

총 8개의 정보가 표시됩니다. category 태그를 통해 어떤 종류인지 확인할 수 있으며 T1H(기온 ℃), RN1(1시간 강수량 mm), UUU(동서바람성분 m/s), VVV(남북바람성분 m/s), REH(습도 %), PTY(강수형태), VEC(풍향 deg), WSD(풍속 m/s)입니다. 값이 900이상 -900이하일 경우 누락된 값으로 판단해야 합니다. 아울러 정보 중 강수형태는 코드값으로 값에 대한 의미는 없음(0), 비(1), 비/눈(2), 눈(3), 소나기(4), 빗방울(5), 빗방울/눈날림(6), 눈날림(7)입니다.

초단기실행 정보는 10분 단위로 업데이트 된다고 합니다. 그리고 관측지점은 nx와 ny로 지정하는데 이 값은 일반적인 경위도값이 아닌 이 서비스를 위한 기준 격자 번호입니다. 해당 서비스에 대한 엑셀 문서를 통해서 각 행정구역에 대한 nx와 ny를 파악할 수 있도 있고 경위도를 통해 변환할 수 있는 C언어 코드를 문서를 통해 제공하고 있습니다.

이 글은 현재 기상 정보를 파악하기 위해 필요한 최소한의 내용을 설명하고 있으므로 전체적인 내용 파악이 필요할 경우 해당 서비스에 대한 활용가이드 문서를 참고하시기 바랍니다.

아래의 코드는 위의 서비스를 실제 시스템에 반영할 때 발생하는 CORS 문제를 피하기 위한 프록시(Proxy) 서버를 사용해 호출하는 코드입니다.

let proxy = "http://111.111.111.111/Xr";
let urlHeader = "http://apis.data.go.kr/1360000/VilageFcstInfoService/getUltraSrtNcst";
let queryString = "serviceKey=__ENCODED_KEY__&pageNo=1&numOfRows=10&dataType=XML&base_date=20210526&base_time=0600&nx=18&ny=1";
queryString = encodeURI(queryString);
url = proxy + "?reqPrx|" + urlHeader + "|" + queryString;

const xhr = new XMLHttpRequest();
xhr.open("GET", url);
        
xhr.onreadystatechange = function (evt) {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(xhr.responseText);
        } else {
            throw new Error("Error");
        }
    }
};

xhr.send(null);

GLSL를 이용한 그래픽 효과

3차원 그래픽에서 특수 효과는 쉐이더를 통해 대부분 구현됩니다. 이 글은 간단한 GLSL 쉐이더 코드를 통해 물과 불에 대한 효과를 소개합니다.

먼저 불에 대해 구현하고자 하는 모습은 다음과 같습니다.

다음은 물에 대한 결과입니다.

전체 소스코드는 아래 링크를 통해 다운로드 받으실 수 있습니다. 웹기반에서 구현된 코드이므로 js와 css, html 파일로 구성되어 있으며 WebGL 2.0으로 쉐이더 코드가 실행됩니다.

chart.js 코드 정리

MIT 라이선스인 chart.js를 예제를 통해 간단하게 정리해 봅니다. 구현하고자 하는 모습은 아래의 영상과 같습니다.

꺽은선 차트이며, 2개의 데이터 축을 가지고 1초마다 데이터가 변경됩니다.

먼저 DOM 구조입니다.

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div>
    <canvas id="chart"></canvas>
</div>

CSS는 다음과 같구요.

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap');

body {
    margin: 0;
    padding: 0;
}

div {
    width: 600px;
    height: 400px;
    padding: 20px;
}

이제 JS 코드에 대한 내용인데, 먼저 차트를 위한 기본 골격은 다음과 같습니다.

Chart.defaults.font.family = 'Noto Sans KR';

const ctx = document.getElementById('chart');

const config = {
    // CORE PART !!
};

const chart = new Chart(ctx, config);

위의 config에 속성값을 지정함으로써 차트를 정의하는 세세한 내용을 결정할 수 있습니다. config에 대한 속성값은 다음과 같이 정의했습니다.

const config = {
    type: 'line',
    data: {
        labels: ['빨강', 'Blue', 'Yellow', '초록', 'Purple', '오렌지'],
        datasets: [
            {
                label: '변곡점1',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: 'yellow',
                borderColor: 'black',
                borderWidth: 1
            },
            {
                label: '변곡점2',
                data: [10, 16, 7, 6, 4, 2],
                backgroundColor: 'white',
                borderColor: 'gray',
                borderWidth: 1
            }
        ]
    },
    options: {
        maintainAspectRatio: false,
        plugins: {
            title: {
                display: true,
                text: '차트, 그것이 알고 싶다.'
            }
        },
        scales: {
            x: {
                title: {
                    display: true,
                    text: '색상'
                }
            },
            y: {
                title: {
                    display: true,
                    text: '변곡량'
                }
            }
        },

    }
}

앞서 실행 화면을 보면 1초마다 차트의 값이 변경되는 것을 볼 수 있습니다. 이에 대한 코드는 다음과 같습니다.

setInterval(() => {
    const datasets = config.data.datasets;

    for (let iDataset = 0; iDataset < datasets.length; iDataset++) {
        const data = datasets[iDataset].data;
        for (let iValue = 0; iValue < data.length; iValue++) {
            data[iValue] += Math.random() * 2.0 - 1.0;
        }
    }

    chart.update();
}, 1000);

동적 웹 페이지에서 데이터 크롤링

이 글은 동적 웹 페이지에서 주소 데이터를 취득하기 위한 파이썬 코드를 설명합니다.

동적 페이지라함은 웹 페이지에서 사용자의 클릭 등과 같은 조작을 통해 AJAX 호출이 발생하여 그 결과가 페이지의 일부분에 반영되어 변경되는 것을 의미합니다. 예를 들어 아래의 커피빈 페이지에서 매장 정보를 확인하기 위해 사용자는 다음과 같은 절차를 통해 매장의 이름과 주소 그리고 전화번호를 확인할 수 있습니다.

위의 그림을 글로 설명하면, 먼저 사용자는 정보를 파악할 매장에 대한 “자세히 보기” 버튼을 클릭하면 웹브라우저가 연결된 javascript 코드를 실행하여 해당 매장의 상세 정보가 동일한 페이지에 동적으로 표시됩니다.

이러한 사용자의 조작을 자동화하기 위해서는 Selenium 라이브러리를 사용합니다. 이 라이브러리는 웹 브라우저를 코드를 통해 제어하기 위한 라이브러리이며, 내부적으로는 Web Driver라는 프로그램을 사용합니다. 아울러 HTML 페이지를 해석하여 원하는 정보를 추출할 수 있는 BeautifulSoup 라이브러리도 필요합니다.

BeautifulSoup는 다음과 같은 pip 명령을 통해 설치할 수 있습니다.

pip install beautifulsoup4

Selenium은 다음과 같은 pip 명령으로 설치할 수 있습니다.

pip install selenium

언급했듯이 selenium은 Web Driver가 필요한데 Windows의 Chrome에 대한 드라이버는 현재 시점에서 다음 url을 통해 다운로드 받을 수 있습니다.

https://chromedriver.storage.googleapis.com/index.html?path=88.0.4324.96/

다운로드 받은 파일명은 chromedriver_win32.zip이며 압축을 풀면 chromedriver.exe가 생성됩니다.

이제 준비가 완료되었으므로 코드를 살펴보겠습니다. 먼저 필요한 라이브러리를 import 합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from bs4 import BeautifulSoup
import pandas as pd

pandas는 크롤링한 데이터를 csv 파일로 저장하기 위해 사용됩니다.

다음으로 Web Driver에 대한 객체를 생성합니다.

wd = webdriver.Chrome('./WebDriver/chromedriver.exe')

이제 다음의 코드로 사용자의 클릭을 통해 호출되는 자바스크립트 함수를 Web Driver를 통해 자동화합니다.

result = []
for i in range(1, 100):
    try:
        wd.get('https://www.coffeebeankorea.com/store/store.asp')
        wd.execute_script('storePop2(%d)' %i)

        element = WebDriverWait(wd, 2).until(
            expected_conditions.presence_of_element_located((By.CLASS_NAME, "store_table")))

        html = wd.page_source
        soup = BeautifulSoup(html, 'html.parser')

        store_name = soup.select('.store_txt > h2')[0].string

        store_info = soup.select('table.store_table > tbody > tr > td')

        store_address = list(store_info[2])[0]
        store_phone = store_info[3].string

        result.append([store_name, store_address,  store_phone])

        print(f'{i} : {store_name} {store_address} {store_phone}')
    except:
        print(f'{i} : not exist')
        continue

wd.quit()

df = pd.DataFrame(result, columns = ('name', 'address', 'phone'))
df.to_csv('./CoffeeBean.csv', encoding='utf-8', mode='w', index=False)

print('Completed..')

위의 코드는 1-99번까지의 인자값에 대한 자바스크립트 함수(storePop2)를 호출하며 호출된 결과인 html 문자열에서 필요한 정보를 추출하여 CSV 파일로 저장하는 코드입니다. 실행 결과로 저장되는 CSV 파일의 일부 내용은 다음과 같습니다.

name,address,phone
학동역 DT점,서울시 강남구 학동로 211 1층 ,02-3444-9973
수서점,서울시 강남구 광평로 280 수서동 724호 ,02-3412-2326
차병원점,서울시 강남구 논현로 566 강남차병원1층 ,02-538-7615
강남대로점,서울시 서초구 강남대로 369 1층 ,02-588-5778
메가박스점,서울 강남구 삼성동 159 코엑스몰 지하2층 ,02-6002-3320
.
.
.

이렇게 만들어진 파일을 통해 실제 공간 좌표로 변환하기 위한 지오코딩 툴은 Geocoder-Xr를 사용하시는 것을 추천드립니다.

끝으로 이 글은 “데이터 과학 기반의 파이썬 빅데이터 분석(저자 이지영)”이라는 서적을 원저자의 허락 하에 참조하여 작성하였습니다.

이 책은 빅데이터에 대한 깊이 있는 이해를 돕는 이론으로 시작해 빅데이터를 수집하는 구체적인 방법과 이렇게 수집된 데이터를 파이썬 언어를 이용하여 분석하고 분석 결과를 효과적으로 시각화하는 방법을 구체적으로 설명하고 있습니다. 크롤링에 대한 좋은 예제를 이 블로그를 통해 공개할 수 있도록 허락해주신 이 책의 저자이신 이지영님에게 감사드립니다.