46.3 웹 페이지의 HTML을 가져와서 파일로 저장하기

이제 기상청 웹 사이트에서 도시별 현재날씨 페이지의 HTML을 가져와보겠습니다. 웹 브라우저를 실행하고 다음 주소로 이동합니다.

  • 도시별 현재날씨 > 지상관측자료 > 관측자료 > 날씨 > 기상청
    http://www.kma.go.kr/weather/observation/currentweather.jsp
그림 46-15 기상청 도시별 현재날씨

웹 페이지를 보면 도시별 기상 데이터가 나옵니다. 많은 데이터가 표시되지만 우리는 이 웹 페이지에서 지점, 기온(현재기온), 습도만 가져오겠습니다.

참고로 기상청 웹 사이트는 시간이 지나면 개편을 하므로 웹 페이지의 HTML 구조도 바뀌게 됩니다. 특히 파이썬 코딩 도장 책이 나온 뒤에 기상청 웹 사이트가 개편되었을 경우에는 책 내용대로 실습을 할 수 없게 됩니다. 따라서 이번 유닛에서는 원활한 실습을 위해 도시별 현재날씨 페이지를 복사해서 올려놓은 Bitbucket 주소를 사용하겠습니다.

46.3.1  가져올 HTML 확인하기

그럼 웹 브라우저에서 다음 주소로 이동하고, F12를 눌러서 개발자 도구를 표시합니다(여기서는 크롬을 사용하겠습니다). 그리고 왼쪽 아래 커서 버튼을 클릭(Ctrl+Shift+C)한 뒤 현재날씨 표에서 지점을 클릭합니다.

  • Bitbucket 도시별 현재날씨 페이지
    https://pythondojang.bitbucket.io/weather/observation/currentweather.html
그림 46-16 개발자 도구에서 지점 선택
참고 | 웹 페이지가 열리지 않는다면?

만약 위 웹 페이지가 열리지 않는다면 HTML을 가져오는 과정은 생략하고, GitHub 저장소의 Unit 46/weather.txt 파일을 이용하여 '46.4 데이터로 그래프 그리기' 실습을 진행하기 바랍니다.

이제 지점에 해당하는 HTML 코드가 표시됩니다. 여기서 스크롤을 위쪽으로 조금 올린 뒤 <table class="table_develop3" 부분을 클릭합니다. 그러면 표 전체가 선택되는데 이 표가 우리가 가져올 부분입니다.

그림 46-17 개발자 도구에서 표 선택

46.3.2  주피터 노트북 만들기

가져올 HTML을 확인했으면 주피터 노트북에 프로젝트 폴더(C:\project)를 지정해서 실행합니다(여기서는 반드시 아나콘다의 python.exe를 사용해야 합니다. 기존 파이썬 인터프리터를 사용하면 안 됩니다).

C:\Users\dojang>C:\Users\dojang\Anaconda3\python.exe -m notebook --notebook-dir C:\project

주피터 노트북에서 새 노트북을 만든 뒤 코드 셀에 다음 내용을 입력합니다.

weather.ipynb

import requests                  # 웹 페이지의 HTML을 가져오는 모듈
from bs4 import BeautifulSoup    # HTML을 파싱하는 모듈
 
# 웹 페이지를 가져온 뒤 BeautifulSoup 객체로 만듦
response = requests.get('https://pythondojang.bitbucket.io/weather/observation/currentweather.html')
soup = BeautifulSoup(response.content, 'html.parser')
 
table = soup.find('table', { 'class': 'table_develop3' })    # <table class="table_develop3">을 찾음
data = []                            # 데이터를 저장할 리스트 생성
for tr in table.find_all('tr'):      # 모든 <tr> 태그를 찾아서 반복(각 지점의 데이터를 가져옴)
    tds = list(tr.find_all('td'))    # 모든 <td> 태그를 찾아서 리스트로 만듦
                                     # (각 날씨 값을 리스트로 만듦)
    for td in tds:                   # <td> 태그 리스트 반복(각 날씨 값을 가져옴)
        if td.find('a'):             # <td> 안에 <a> 태그가 있으면(지점인지 확인)
            point = td.find('a').text    # <a> 태그 안에서 지점을 가져옴
            temperature = tds[5].text    # <td> 태그 리스트의 여섯 번째(인덱스 5)에서 기온을 가져옴
            humidity = tds[9].text       # <td> 태그 리스트의 열 번째(인덱스 9)에서 습도를 가져옴
            data.append([point, temperature, humidity])    # data 리스트에 지점, 기온, 습도를 추가
 
data    # data 표시. 주피터 노트북에서는 print를 사용하지 않아도 변수의 값이 표시됨

실행을 해보면 Out [1]: 부분에 data의 값이 표시됩니다. 여기서 Out [1]:In [1]:의 출력이라는 뜻입니다.

그림 46-18 웹 페이지에서 가져온 데이터 표시

46.3.3  HTML의 데이터를 가져오는 방식 알아보기

그럼 지점, 기온(현재기온), 습도 값을 어떻게 가져오는지 알아보겠습니다. 웹 브라우저의 개발자 모드에서 왼쪽 아래 커서 버튼을 클릭(Ctrl+Shift+C)한 뒤 서울을 클릭합니다. 그러면 서울에 해당하는 태그들이 출력됩니다. 그다음에 서울의 현재기온 25.6과 습도 30도 클릭해봅니다. 이런 방식으로 HTML 코드와 웹 페이지 화면을 보면서 어떤 태그가 원하는 값인지 찾습니다.

그림 46-19 웹 페이지에서 원하는 값 찾기

이제 HTML 코드를 살펴보겠습니다. <table>에서 도시(지점)별 데이터는 <tr> 태그로 묶여 있고, 세부 값은 <td> 태그에 들어있습니다. 여기서는 <td> 태그 안에 <a> 태그가 있으면 지점이라 판단하고, 지점 값을 가져옵니다. 그다음에 기온은 여섯 번째(인덱스 5) <td>의 값을 가져오고, 습도는 열 번째(인덱스 9) <td>의 값을 가져오면 됩니다.

<table class="table_develop3" summary="기상실황표로 지점, 날씨, 기온, 강수, 바람, 기압등을 안내한 표입니다.">
...생략...
         <tr>
                  <td><a href="/weather/observation/currentweather.jsp?tm=2017.5.17.14:00&amp;type=t99&amp;mode=0&amp;auto_man=m&amp;stn=108" >서울</a></td> 
                  <td>맑음</td> 
                  
                  <td>18.9</td> 
                  <td>1</td> 
                  <td>1</td> 
                  <td>25.6</td> 
                  <td>6.7</td> 
                  <td>70</td> 
                  <td>&nbsp;</td> 
                  
                  <td>30</td> 
                  <td>서남서</td> 
                  <td>2.1</td> 
                  <td>1010.1</td> 
         </tr>
...생략...

다시 파이썬에서 HTML을 가져오는 부분입니다. 여기서는 requests 모듈로 웹 페이지의 HTML을 가져오고, bs4 모듈로 HTML을 파싱합니다(HTML 파싱은 텍스트 형태의 HTML 코드를 분석해서 객체로 만든 뒤 검색하거나 편집할 수 있도록 만드는 작업입니다. 그리고 bs4BeautifulSoup 라이브러리이고 HTML 코드를 파싱하는데 사용합니다).

import requests                  # 웹 페이지의 HTML을 가져오는 모듈
from bs4 import BeautifulSoup    # HTML을 파싱하는 모듈
 
# 웹 페이지를 가져온 뒤 BeautifulSoup 객체로 만듦
response = requests.get('https://pythondojang.bitbucket.io/weather/observation/currentweather.html')
soup = BeautifulSoup(response.content, 'html.parser')

request.get에 URL을 넣으면 응답(Response) 객체가 나옵니다. 그리고 BeautifulSoup 클래스에 응답 객체의 content 속성과 'html.parser'를 넣습니다. content 속성에는 텍스트 형태의 HTML이 들어있으며, 파이썬의 html.parser 모듈을 사용해서 파싱하도록 설정합니다.

참고 | 아나콘다를 사용하지 않는다면?

requestsbs4는 기본적으로 파이썬에 포함되어 있지 않습니다. 따라서 pip install requests, pip install bs4로 패키지를 설치해줍니다.

이제 BeautifulSoup 클래스로 만든 soup 객체로 태그를 찾습니다. 먼저 soup.find('table', { 'class': 'table_develop3' })과 같이 HTML의 class 속성(attribute)이 table_develop3<table> 태그를 찾습니다(HTML의 class는 태그의 스타일을 지정할 때 사용하는 속성이며 파이썬의 클래스와는 다릅니다).

table = soup.find('table', { 'class': 'table_develop3' })    # <table class="table_develop3">을 찾음

그다음에는 데이터를 저장할 리스트 data를 만듭니다. 그리고 for tr in table.find_all('tr'):과 같이 table에서 모든 <tr> 태그를 찾아서 반복합니다. 즉, 이 <tr> 태그에 서울, 백령도, 인천 등 지점별 데이터가 들어있으므로 반복할 때마다 서울, 백령도, 인천 등 각 지점의 데이터를 가져옵니다.

data = []                            # 데이터를 저장할 리스트 생성
for tr in table.find_all('tr'):      # 모든 <tr> 태그를 찾아서 반복(각 지점의 데이터를 가져옴)

각 지점의 데이터를 가져왔으면 list(tr.find_all('td'))과 같이 tr에서 모든 <td> 태그를 찾아서 리스트로 만듭니다. 이렇게 하면 지점, 현재일기, 시정, 운량, 중하운량, 현재기온, 이슬점온도, 불쾌지수, 일강수, 습도, 풍향, 풍속, 해면기압 <td>가 리스트에 들어갑니다.

    tds = list(tr.find_all('td'))    # 모든 <td> 태그를 찾아서 리스트로 만듦
                                     # (각 날씨 값을 리스트로 만듦)

이제 <td> 태그 리스트 tds를 반복하면서 각 값을 가져옵니다. 먼저 if td.find('a'):와 같이 td<a> 태그가 있는지 확인합니다. <a> 태그가 있으면 td.find('a').text와 같이 <a> 태그의 text속성에서 지점을 가져옵니다(text 속성은 <태그>텍스트</태그>에서 태그 안에 들어있는 텍스트를 가져옵니다). 그리고 기온(현재기온)은 여섯 번째(인덱스 5), 습도는 열 번째(인덱스 9)에 있다는 것을 확인했으므로 tds[5].text에서 기온을 가져오고, tds[9].text에서 습도를 가져옵니다.

    for td in tds:                   # <td> 태그 리스트 반복(각 날씨 값을 가져옴)
        if td.find('a'):             # <td> 안에 <a> 태그가 있으면(지점인지 확인)
            point = td.find('a').text    # <a> 태그 안에서 지점을 가져옴
            temperature = tds[5].text    # <td> 태그 리스트의 여섯 번째(인덱스 5)에서 기온을 가져옴
            humidity = tds[9].text       # <td> 태그 리스트의 열 번째(인덱스 9)에서 습도를 가져옴
            data.append([point, temperature, humidity])    # data 리스트에 지점, 기온, 습도를 추가

필요한 값을 가져왔으면 data 리스트 안에 [point, temperature, humidity]처럼 값을 리스트 형태로 추가해줍니다.

            data.append([point, temperature, humidity])    # data 리스트에 지점, 기온, 습도를 추가
참고 | 웹 페이지 크롤링

지금까지 작성한 웹 페이지 크롤링 코드는 특정 태그가 있는지, 몇 번째에 위치한 태그를 가져온다든지 해서 생각보다 체계적이지 못한 느낌이 듭니다. 왜냐하면 우리가 가져오는 웹 페이지는 데이터를 화면에 보여주는게 목적일 뿐 데이터를 체계적으로 저장하는데는 적합하지 않기 때문입니다. 그래서 주어진 HTML 구성에 맞춰서 만들다 보니 코드가 깔끔하지 않습니다. 즉, 크롤링은 웹 페이지마다 전부 코드가 다르게 나오며 같은 웹 페이지라도 개편이 되면 크롤링 코드를 다시 만들어야 합니다.

46.3.4  데이터를 csv 파일에 저장하기

데이터가 완성되었으니 이 데이터를 파일에 저장해보겠습니다. 방금 data의 값을 출력한 뒤에 코드 셀이 하나 더 생겼을 겁니다. 이 코드 셀에서 다음 코드를 실행합니다(셸이 생기지 않았다면 메뉴의 Insert > Insert Cell Below 실행).

weather.ipynb

with open('weather.csv', 'w') as file:    # weather.csv 파일을 쓰기 모드로 열기
    file.write('point,temperature,humidity\n')                  # 컬럼 이름 추가
    for i in data:                                              # data를 반복하면서
        file.write('{0},{1},{2}\n'.format(i[0], i[1], i[2]))    # 지점,온도,습도를 줄 단위로 저장

코드를 실행하면 프로젝트 폴더(C:\project)에 weather.csv 파일이 생성됩니다. csv 파일은 Comma-separated values의 약자인데 각 컬럼을 ,(콤마)로 구분해서 표현한다고 해서 csv라고 부릅니다. 여기서는 file.write('point,temperature,humidity\n')처럼 맨 윗줄에 컬럼 이름을 추가하고 그 다음 줄부터는 data를 반복하면서 file.write('{0},{1},{2}\n'.format(i[0], i[1], i[2]))와 같이 지점, 온도, 습도를 줄단위로 저장합니다. 이때 콤마와 값 사이에는 공백을 넣지 않고 반드시 붙여줍니다.

weather.csv 파일을 메모장이나 기타 텍스트 편집기로 열어보면 다음과 같은 모양으로 지점, 기온, 습도 값이 저장된 것을 볼 수 있습니다.

weather.csv

point,temperature,humidity
서울,25.6,30
백령도,18.4,62
인천,20.8,54
수원,25.0,41
...생략...

특히 csv 파일을 저장할 때 컬럼 이름은 영어로 지정해줍니다. 영어로 지정하면 나중에 각 컬럼에 접근할 때 df.temperature처럼 속성으로 깔끔하게 사용할 수 있습니다.