본문 바로가기

Programming/Python

[Python] 파이썬 크롤링으로 일출 일몰 데이터 가져오기

반응형

우리 대학원 연구실에서는 태양광에 관한 연구를 장기간 진행하고 있기 때문에 하루 중 태양의 노출시간 등이 상당히 중요하다. 그래서 특정 일의 특정 위치에 대한 일출 일몰 시간을 알려주는 프로그램을 작성해야 했는데 이를 알고리즘으로 구현하기보다는 이미 서비스중인 웹페이지를 스크래핑하는 방식을 생각해냈다.

요즘 크롤링(좀더 정확한 의미로는 스크래핑이 맞다고 한다. 그러나 편의상 본 포스팅에서는 크롤링이라는 단어로 사용하고자 한다.) 하면 파이썬으로 이루어진 경우가 많다. 그만큼 크롤링을 할 수 있도록 도와주는 파이썬의 다양한 패키지들이 강력하다는 뜻이다. 본 포스팅에서는 파이썬으로 일출 일몰 데이터를 가져오는 방법을 소개할까 한다.

1. 대상 분석

 

국내에 있는 가장 정확한 일출일몰 시간을 구할 수 있는 페이지는 한국 천문연구원에 있다.

https://astro.kasi.re.kr:444/life/pageView/9

2019.12.11 주소가 바뀌었다.

https://astro.kasi.re.kr/life/pageView/9

 

일출일몰시각계산 | 생활천문관 | 천문우주지식정보

지금까지 역서가 발행된 연도의 역서자료를 바탕으로 월별, 지역별 해/달 출몰시각을 검색할 수 있습니다. 검색결과 날 짜2017-03-22 지 역서울특별시 위 치동경 / 북위 낮의 길이 시민박명(아침/저녁) 항해박명(아침/저녁) 천문박명(아침/저녁) 유의사항 본 계산식에서 나온 값들은 어떠한 법적효력도 가지고 있지 않습니다. 참고하시기 바랍니다. 월별 해/달 출몰시각 페이지는 지금까지 역서가 발행된 연도의 역서자료를 바탕으로 월별, 지역별 해/달 출몰시각을

astro.kasi.re.kr

 

한국천문연구원은 구체적인 지역의 위경도 값을 반영하여 정확한 일출일몰 시간을 산출하여 제공한다.

위 페이지에 들어가면 큼지막한 input form이 나온다.

 

 

각각의 input에 날짜와 주소지를 입력하면 해당 날짜 값과 위치 값이 파라미터로 넘어가면서

 

https://astro.kasi.re.kr/life/pageView/9?date=2017-08-26&address=xxxx+xxxx+xxxx

 

위와 같은 url로 변경된다.

이렇게 넘어간 페이지에는 일출시각, 남중시각(해가 중천에 떠있는 시각), 일몰시각이 나타난다.

 

 

 

그렇다면, 우리는 base url인 https://astro.kasi.re.kr/life/pageView/9에 원하는 날짜 값과 원하는 주소지를 조합한 url에 GET method를 request 하여 얻어낸 웹 페이지 데이터에서 일출일몰시각 정보만 가져오면 된다.

일출시각과 일몰시각을 포함하는 태그를 찾기 위해 해당 페이지를 개발자도구로 분석해보면 다음과 같다.

 

 

 

 

위에서 확인할 수 있듯이 일출일몰시각 데이터는 span 태그로 싸여있고 'sunrise', 'sunset'이라는 클래스 명을 각각 가지고 있음을 알 수 있다.

 

 

2. 도구 사용하기

 

파이썬에는 HTTP 프로토콜을 지원하는 다양한 패키지가 있는데 여기서는 requests라는 모듈을 소개하려고 한다.

파이썬에는 기본적으로 인터넷 자원에 접근할 수 있도록 돕는 urllib라는 패키지가 있다. 많이 사용되는 패키지이지만 requests는 사용법이 urllib보다 간단하다. requests 모듈은 urllib와 다르게 파이썬에 내장되어있지 않으므로 추가로 모듈을 설치하여야 한다.

 

필자는 pycharm을 사용하여 개발하고 있으므로 설정창의 인터프리터 항목에서 모듈을 추가할 것이다.

pycharm에서 ctrl + alt + s 를 누르면 설정창이 나타난다.

 

 

좌측의 Project interpreter 를 클릭하면 현재 프로젝트에 잡혀있는 인터프리터에 설치된 패키지 목록이 나타나는데 우측의 + 버튼을 누르자.

 

 

외부 패키지를 검색할 수 있는 창이 나타나면 requests를 검색하여 하단의 install package 버튼을 누르고 잠시 기다리자.

설치가 완료되면 검색창을 나가고 설정창은 ok를 누르자.

그럼 이제부터 requests 모듈을 사용할 수 있게 되었다.

 

2018년 2월 20일 공주대학교 천안캠퍼스의 일출 일몰 시간을 요청하기 위해 아래와 같은 코드를 작성해보자.

 

import requests

url_r = 'https://astro.kasi.re.kr/life/pageView/9'
datetime = '2018-02-20'
address = '충남+천안시+서북구+천안대로+1223-24'

url = url_r + '?datetime=' + datetime + '&address=' + address
print(url)

response = requests.get(url=url)
print(response.content)

 

requests.get()은 인자로 전달받은 url의 웹페이지 내용을 반환한다. 엄밀히 말하면 해당 url에 GET method 요청(request)에 대한 응답(response)을 반환한다. 더욱 다양한 기능이 있지만 우리는 GET method만 필요하므로 이것만 알면 된다.

코드를 실행하면 출력 결과는 다음과 같을 수 있다.

 

<!DOCTYPE html>
<!--[if gt ie 8]>
	<html lang="ko" class="no-js">
<!--<![endif]-->
<!--[if lte IE 8]> 
	<html lang="ko" id="ie8" class="no-js">
<![endif]-->
 <!-- 태그 라이브러리 설정 -->
 <!-- 태그 라이브러리 설정 --> 
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="description" content="">
<meta name="author" content="">
<meta name="keyword" content="">
<link rel="shortcut icon" href="/resources/images/favicon.ico">

<title>




				일출일몰시각계산 | 생활천문관 | 천문우주지식정보





</title>

<link href="/resources/css/bootstrap.css" rel="stylesheet">
<link href="/resources/css/datepicker.css" rel="stylesheet">
<link href="/resources/css/common.css" rel="stylesheet">
<link href="/resources/css/font-awesome.min.css" rel="stylesheet">
<link href="/resources/css/print.css" rel="stylesheet">
...

 

해당 페이지의 내용을 잘 가져올 수 있었다. 이제 이 html 소스에서 sunrise, sunset 클래스 명을 가진 span 태그를 찾으면 된다.

 

파이썬에는 beautifulsoup라는 유명한 html 파싱 패키지가 있다. 파이썬으로 크롤링하는 이유가 'beautifulsoup가 있어서' 라고 말할 정도이다.

beautifulsoup도 외부 패키지 설치 후 사용하여야 한다. 설치는 requests를 설치하는 방법과 동일하다.

 

설치가 끝났다면 위의 파이썬 코드를 아래와 같이 수정하자.

 

import requests
from bs4 import BeautifulSoup

url_r = 'https://astro.kasi.re.kr/life/pageView/9'
date = '2018-02-20'
address = '충남+천안시+서북구+천안대로+1223-24'

url = url_r + '?date=' + date + '&address=' + address
print(url)

response = requests.get(url=url)

soup = BeautifulSoup(response.text, 'html.parser')

sunrise = soup.find_all('span', {'class': 'sunrise'})
sunset = soup.find_all('span', {'class': 'sunset'})

print(sunrise[0].string)
print(sunset[0].string)

 

아까 가져왔던 html 소스인 response.text를 html parser로 파싱을 하는 BeautifulSoup 객체 soup를 생성하였다.

BeautifulSoup 클래스에는 태그를 찾을 수 있는 메소드가 내장되어있다. find_all()은 첫번째 인자로 찾고싶은 태그 종류를 string으로 받고, 두번째 인자로는 태그의 attribute를 dictionary type으로 받는다. 우리가 찾고자 하는 태그는 class라는 attrubute를 가지며 그 value는 각각 sunrise, sunset인 태그이다.

find_all() 메소드는 인자 값에 해당하는 태그를 모두 찾아 Tag라는 객체의 리스트 타입으로 반환한다. html 소스중에 우리가 찾고자 하는 태그는 단 한개뿐이다. 따라서 sunrise, sunset 리스트의 0번 인덱스에 해당하는 값이 우리가 원하는 내용일 것이다. 

sunrise[0]은 Tag 객체이다. 이를 print하면 <span>태그를 그대로 포함한다. 따라서 태그의 내용을 가져오기 위해 string 멤버를 사용한다.

 

 

 

 

이 코드를 실행하면 잘 가져올 수 있을 줄 알았는데 None이 뜬다. 왜그럴까 생각을 해보았다. 아무래도 페이지를 좀더 면밀히 분석해 봐야할 것 같다.

 

 

개발자도구에서는 문제 없이 뜬다. 자 그럼 파이썬으로 가져온 html을 보자.

 

...
<!-- Timebox -->
<div class="Timebox">
    <div class="blcok">
        <div class="watch" id="sunrise-clock"></div>
        <p class="tit">
            <strong>해뜨는 시각(일출)</strong>
            <span class="sunrise"></span>
        </p>
    </div>
    <div class="blcok">
        <div class="watch" id="culmination-clock"></div>
        <p class="tit">
            <strong>한낮의 시각(남중)</strong>
            <span class="culmination"></span>
        </p>
    </div>
    <div class="blcok">
        <div class="watch" id="sunset-clock"></div>
        <p class="tit">
            <strong>해지는 시각(일몰)</strong>
            <span class="sunset"></span>
        </p>
    </div>
    <div class="clearfix">
    </div>
</div>
<!--// Timebox -->
...

 

이상하게도 파이썬으로 가져온 html에는 시간 정보가 다 비어있다. 무엇이 문제일까? 데이터가 깨져서 못가져오는걸까?

 

그런데, html 상단에서 이것을 발견하였다.

 

...
<script type="text/javascript">
    ...
    $(function () {
        ...
        var sun_m_0_hr = hr2hr(sun_m_0 * 24.0 + timeoffset); // 남중
        var sun_m_1_hr = hr2hr(sun_m_1); // 일출
        var sun_m_2_hr = hr2hr(sun_m_2); // 일몰
        ...
    });
...

 

천문연구원 사이트의 일출일몰 계산 페이지는 jQuery로 작성된 이 함수를 통해 일출일몰시각을 산출하는 것으로 보인다. 

 

$(function() { ... } ); 구문은 jQuery에서 $(document).ready(function() { ... }); 의 줄임 형태이다. 이 jQuery 구문은 쉽게 말하면 웹페이지의 모든 dom이 사용가능한 상태일 때 한번 호출된다. 초기화 작업 정도로 생각하면 되는데, 문제는 요청에대한 응답으로 html이 먼저 클라이언트로 전달되고 이후 조금 늦게 자바스크립트가 로드된다는 점이다.

 

이처럼 자바스크립트가 동적으로 html을 변경하는 경우에는 위와 같은 방법으로는 가져올 수 없다(위와같은 방법을 '정적 웹 크롤링'이라고 한단다).

 

그렇다면 동적 웹 크롤링은 어떻게 하여야 할까? 이 역시 파이썬에서 제공하고 있는 패키지를 사용하면 된다.

 

 

4. 동적 웹 크롤링 도구 Selenium

Selenium은 사실 웹 애플리케이션을 테스트하는 프레임워크이다. 개발된 웹 애플리케이션을 일일이 수동으로 찾지 않고 자동으로 테스트해주는 역할을 한다.

Selenium은 클라이언트에 존재하는 웹드라이버(webdriver) API를 이용하여 대상 웹페이지에 접속하고 그 내용을 가져온다.

우리는 그 중 chrome browser의 webdriver api룰 이용할 것이다.

 

Selenium이라는 패키지를 설치하자. 설치는 이제 껌이다.

그 다음 여기서 chrome webdriver를 다운받자. 자신에 맞는 운영체제로 골라서 받아야한다.

 

이제 준비는 끝났으니 위의 코드를 수정한다.

 

from bs4 import BeautifulSoup
from selenium import webdriver

chrome_driver_path = 'C:/_misc/chromedriver.exe' # chrome driver의 경로
driver = webdriver.Chrome(chrome_driver_path)

url_r = 'https://astro.kasi.re.kr/life/pageView/9'
date = '2018-02-20'
address = '충남+천안시+서북구+천안대로+1223-24'
url = url_r + '?date=' + date + '&address=' + address

driver.get(url)
soup = BeautifulSoup(driver.page_source, 'html.parser')

sunrise = soup.find_all('span', {'class': 'sunrise'})
sunset = soup.find_all('span', {'class': 'sunset'})

print(sunrise[0].string)
print(sunset[0].string)

 

Selenium의 사용법은 requests 만큼은 아니지만 어렵지 않다. 먼저 webdriver의 chrome API 객체를 초기화할 때 chrome driver 의 로컬 경로를 지정해주고 이렇게 생성한 객체의 .get() 메소드를 호출 후 .page_source로 페이지 내용을 가져온다.

이렇게 얻어온 페이지를 html 파싱 하는 것은 이전과 동일.

 

이제 실행해보자! (아까도 말했지만 필자는 윈도우 환경에서 PyCharm을 사용하고 있으므로 사용환경이 조금 다를수 있다.)

그런데 이번에는 갑자기 크롬브라우저가 뜬다.

 

 

'자동화된 테스트 소프트웨어에 의해 제어되고 있습니다' 문구를 보니 chrome webdriver가 호출되면 발생하는 것 같다.

아래와 같이 우리가 원하는 결과는 잘 나온다.

 

 

뭐 결과만 나온다면 그냥 사용해도 되지만 크롬창이 뜨는 것이 불편한 사람들을 위한 내용까지 소개하고 마친다.

webdriver 옵션에 '--headless' 라는 옵션을 사용하면 크롬 창이 뜨는 것을 막을 수 있다.

 

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_driver_path = 'C:/_misc/chromedriver.exe'
driver = webdriver.Chrome(chrome_options=chrome_options, executable_path=chrome_driver_path)

url_r = 'https://astro.kasi.re.kr/life/pageView/9'
date = '2018-02-20'
address = '충남+천안시+서북구+천안대로+1223-24'
url = url_r + '?date=' + date + '&address=' + address

driver.get(url)
soup = BeautifulSoup(driver.page_source, 'html.parser')

sunrise = soup.find_all('span', {'class': 'sunrise'})
sunset = soup.find_all('span', {'class': 'sunset'})

print(sunrise[0].string)
print(sunset[0].string)

 

위처럼 코드를 수정하여 실행하면 크롬창이 뜨지 않고 결과를 잘 얻을 수 있는 것을 확인할 수 있다.

 

========================================================

 

19.12.11 추가

한국천문연구원 사이트 일부 기능 개편으로 인해 변동 사항이 발생했는데

일출일몰시각계산 페이지는 쿼리파라미터가 '위도, 경도, 날짜, 주소'로 변경되었다.

 

이를 반영하여 마지막 코드를 변형하면 다음과 같다.

 

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_driver_path = 'C:/_misc/chromedriver.exe'
driver = webdriver.Chrome(chrome_options=chrome_options, executable_path=chrome_driver_path)

url_r = 'https://astro.kasi.re.kr/life/pageView/9'
lat = '36.85'
lng = '127.15'
date = '2018-02-20'
address = '충남+천안시+서북구+천안대로+1223-24'
url = url_r + '?lat=' + lat + '&lng=' + lng + '&date=' + date + '&address=' + address

driver.get(url)
soup = BeautifulSoup(driver.page_source, 'html.parser')

sunrise = soup.find_all('span', {'class': 'sunrise'})
sunset = soup.find_all('span', {'class': 'sunset'})

print(sunrise[0].string)
print(sunset[0].string)

 

위경도값을 주지 않으면 아무 값도 가져오지 못하게 되므로 원하는 지점의 위경도를 미리 입력해놓고 사용하면 된다.

그런데, 위경도 값을 이렇게 지정해서 주기보다는 주소에 따라서 위경도 값을 변화하여 가져올 수 있으면 좋을것 같다.

주소도 사용자가 입력해서 사용할 수 있도록 구현하면 좋을 것 같다.

 

이러한 기능들은 다음 포스팅에서 만들어보고자 한다.

 

To be continued...

 

 

 

반응형