데이터분석/Python

어느 정도 노하우가 쌓인 xml 공공데이터 parsing과 CSV 저장

창조적생각 2021. 8. 21. 23:29

<목차>

* 필요한 라이브러리

 

1. 파싱하기 전에 반드시 해야 할것

 1)미리보기를 통한 데이터형태 파악하기

 2)참고문서 다운받기

 

2. 파싱시작하기

 1) 라이브러리 불러오기

 2) 요청 명세를 보고 url 잘게자르기

 3) Beautifulsoup와 lxml을 이용하여 데이터 파싱하여 다시 형태파악

 4) 파싱할 함수 짜기 (예외처리 반드시 넣기)

 5) pandas에 사용할 수 있는 표 형태로 저장하기

 

3. 다음에 빠르게 불러 올 수 있게 csv 파일로 저장하기

 

4. 전체 코드 바로가기

 

*이 글은 pandas를 편하게 사용하기 위해 jupyter notebook을 사용했습니다.

 

**이 글을 위해 사용한 공공데이터는 "공공데이터활용지원센터_보건복지부 코로나19 감염 현황" 입니다.

 

***공공데이터 xml을 파싱하기 위해서 제가 사용한 라이브러리는

Beautifulsoup, lxml, requests, 그리고 pandas를 사용했습니다.

Beautifulsoup는 태그 안의 텍스트를 가져오기 위해 사용하고 lxml은 xml을 파싱하기 위해 beautifulsoup의 엔진으로 사용했고, requests는 url을 통해 불러 오기 위해 사용했으며, pandas는 표형태로 쉽게 자료를 가공하기 위해 사용했습니다.

모두 pip install을 통해 설치 할 수 있습니다.

 

****xml 파싱을 하느라 두달 넘게 고생을 하다보니 어느정도 노하우가 생긴 것 같습니다. 그래서 이번에는 xml을 파싱하면서 제가 행했던 실수와 오류들을 적고 그보다 나은 방법을 제시함으로써 이 글을 보시는 분들께서는 그러한 실수를 하지 않고 보다 간편하게 파싱하시기를 바라는 마음에 이 글을 작성합니다.

 

1. 파싱하기 전에 반드시 해야 할것

공공데이터를 사용하기 위해 사용 신청을 하셨다면 이제 그 데이터를 받으셔야 겠지요.

그러기 위해서는 우선 '계발정보 상세보기' 를 먼저 보셔야 합니다.

그리고 그 페이지에서 보셔야 할 것은 참고문서, 요청변수 미리보기,  인증키(Encording)입니다.

1) 참고문서에 있는 활용가이드를 다운받으십시오. 나중에 요청할 데이터들의 태그명들이 적혀있습니다.

 

각 공공 데이터마다 다 다른 참고문서가 있습니다. 사용할 데이터마다 다운받아주시기를 바랍니다.

 

2) 그리고 요청변수 미리보기를 통해서 데이터의 형태를 파악하십시오

 

 

여기에서 검색할 생성일 범위와 한장에 표시될 줄의 수를 선택하시고 미리보기를 눌러주시면 됩니다.

 

미리보기를 누르면 이러한 화면이 뜹니다. 여기서 태그들 안에 데이터들이 들어 있음을 확인할 수 있습니다.

이 것이 미리보기를 통해 봐야 하는 이유입니다. 공공데이터는 나중에도 말씀드리겠지만 한 항목항목이 item이라는 태그 안에 감싸져 있습니다. 저는 분명 한장에 항목을 10개만 보겠다고 했는데 너무 길어서 item을 검색해본 결과 364개 즉 182개의 아이템이 있었습니다.

즉 위의 numOfRows 항목은 아무 의미가 없이 한장에 전체 데이터가 들어있다는 뜻입니다. 그렇다면 for 문을 사용해서 굳이 페이지를 넘겨가며 파싱할 필요가 없다는 뜻입니다.

 

3)Open API 활용가이드를 이용해 요청메세지 및 태그들을 확인하십시오.

요청/응답 메시지 예제를 통해 url의 형태를 확인해주세요.

requests에 들어가야할 url은 요청메세지에 형식대로 작성하면됩니다.

그리고 응답 메시지 명세를 통해 데이터가 들어있는 태그를 확인해 줍니다.

2. 파싱 시작하기

준비는 끝났습니다. 이제 파싱을 시작해보기로 합니다.

먼저 라이브러리를 불러옵니다.

requests bs4 pandas를 불러옵니다.

 

그리고 활용가이드에 요청 메세지 예제를 보기삼아 요청 url을 작성합니다.

다른 데이터를 불러올 때 사용했던 url입니다. 나쁜 예시를 보여드리기 위해 가져왔습니다.

url에 인증키를 넣어서 그대로 길게 사용하셔도 되는데, 각 항목별로 잘게 잘라서 다시 requests에 넣을 때 다시 붙여서 사용하시는 것을 추천드립니다. 

나중에 필요해서 다시 사용할 때 구간이나 시작점 끝점 같은 항목들을 체크하기 쉽게하기 위해서 입니다.

다만, pageNo는 그대로 그냥 사용해줬습니다. format(i)를 이용하여 페이지를 넘길때 필요해서 입니다.

 

잘게 자른 모습입니다.

 

이제 requests를 이용하여 xml 파일을 불러들이고 뷰티풀스프를 사용하여 파싱해보겠습니다.

result라는 변수를 선언해줍니다.

그리고 request.get(url)을 result에 저장해줍니다.

그리고 뷰티풀스프와 lxml-xml을 이용하여 항목별로 파싱해줍니다.

 

여기서 위에서 말씀드렸다시피 공공데이터는 <item></item>항목안에 데이터들이 태그로 저장되어있습니다. 따라서 가장먼저 item들을 다 찾아줍니다.

items = soup.find_all('item')을 통해 찾은 item들을 저장해줍니다.

이런식으로 item안에 항목들이 들어가 있습니다.

이제 거의 끝났습니다. 각각의 item들을 돌아다니면서 안의 태그들 안의 text들을 가져와야합니다.

text를 가져오기 위한 함수를 작성합니다.

 

바로 for문을 통해서 함수 없이 가져올 수 있으나, 데이터가 잘못되어 있을 경우 수시간을 공들여 파싱하던 도중에 파싱이 멈춰 극심한 괴로움을 느낄 수 있으니 예외처리를 하기 위해 함수를 작성해줍니다.

 

함수를 작성하기위해서는 먼저 items 안의 태그들의 상세 정보에 대해서 알아야 합니다.

그러기 위해서 앞에서 문서에서 보았던 응답메세지를 보셔야 합니다.

확진자수는 <DECIDE_CNT>태그, 등록 일시는 <CREATE_DT>태그에 들어있다고 되어있군요.

응답메세지 명세를 통해서 태그들 안의 텍스트를 가져오고, 또한 pandas로 프레임을 짜기 위해 dictionary형태로 저장하겠습니다. 그리고 예외 처리를 위해 try와 except도 함께 적용하겠습니다.

글씨가 작아서 죄송합니다. 아래에 코드표로 남겨드리겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def parse():
    try:
        SEQ = item.find("seq").get_text()
        STATE_DT = item.find("stateDt").get_text()
        STATE_TIME = item.find("stateTime").get_text()
        DECIDE_CNT = item.find("decideCnt").get_text()
        CLEAR_CNT = item.find("clearCnt").get_text()
        EXAM_CNT = item.find("examCnt").get_text()
        DEATH_CNT = item.find("deathCnt").get_text()
        CARE_CNT = item.find("careCnt").get_text()
        RESUTL_NEG_CNT = item.find("resutlNegCnt").get_text()
        ACC_EXAM_CNT = item.find("accExamCnt").get_text()
        ACC_EXAM_COMP_CNT = item.find("accExamCompCnt").get_text()
        ACC_DEF_RATE = item.find("accDefRate").get_text()
        CREATE_DT = item.find("createDt").get_text()
        UPDATE_DT = item.find("updateDt").get_text()
        return {
            "게시글번호":SEQ,
            "기준일":STATE_DT,
            "기준시간":STATE_TIME,
            "확진자 수":DECIDE_CNT,
            "격리해제 수":CLEAR_CNT,
            "검사진행수":EXAM_CNT,
            "사망자 수":DEATH_CNT,
            "치료중 환자수":CARE_CNT,
            "결과 음성 수":RESUTL_NEG_CNT,
            "누적 검사 수":ACC_EXAM_CNT,
            "누적 검사 완료 수":ACC_EXAM_COMP_CNT,
            "누적 확진률":ACC_DEF_RATE,
            "등록일시분초":CREATE_DT,
            "수정일시분초":UPDATE_DT
        }
    except AttributeError as e:
        return {
            "게시글번호":None,
            "기준일":None,
            "기준시간":None,
            "확진자 수":None,
            "격리해제 수":None,
            "검사진행수":None,
            "사망자 수":None,
            "치료중 환자수":None,
            "결과 음성 수":None,
            "누적 검사 수":None,
            "누적 검사 완료 수":None,
            "누적 확진률":None,
            "등록일시분초":None,
            "수정일시분초":None
        }
        
cs

 

*이상한 점을 느끼셨습니까? 응답메세지 안의 항목명과 실제 코드에 적혀진 항목명이 다릅니다.

응답메세지 안의 사망자 수는 DEATH_CNT인데 반해 코드의 사망자 수는 deathCnt입니다.

그렇습니다. 공공데이터의 사용설명서는 믿을 것이 못됩니다.

반드시 item을 먼저 파싱하시고 프린트해보셔서 그 안에 있는 항목명을 사용하셔야 합니다.

저도 항상 사용설명서에 있는 항목명 복붙하다가 당황하곤 합니다.

 

이제 본격적으로 파싱을 할 차례입니다.

for 문을 작성하여 각 item들을 돌아가며 항목들을 뽑아냅니다. 그리고 앞서 만들어 놓은 row 리스트에 dic형태로 저장된 항목들이 저장될 것입니다.

 

이제는 다 끝났습니다.

데이터를 만지기 위해 pandas의 데이터프레임 안으로 넣어주기만 하면 됩니다.

pandas에 예쁘게 잘 들어갔습니다.

3. 다음에 빠르게 불러 올 수 있게 csv 파일로 저장하기

다음에 필요한 일이 있어 다시 데이터를 보고 싶다고 또 이런 파싱을 계속할 수는 없는 노릇이겠지요.

이 데이터는 그나마 작아서 빨리 되었지만 큰데이터들은 파싱하고 불러오는데 어마어마한 시간이 듭니다.

그렇기에 엑셀로 접근할 수도 있고 빠르게 다시 불러올수 있는 csv파일로 저장하는 것이 최고일 것입니다.

csv파일로 저장하는 방법은 간단합니다.

df.to_csv("파일이름.csv",mode="v")를 이용하면 정말 간편하게 csv파일로 변환할 수 있습니다.

pd.read_csv()를 이용해 불러온 모습입니다. 잘 들어가있는것을 볼 수 있습니다.

 

*그냥 이렇게 불러오면 앞에 인덱스 열이 하나가 더 생깁니다. 그렇기에 불러올 때 index_col=0을 추가하거나, 불러온 후에 Unnamed:0 column을 삭제해주시면 됩니다.

index 하나가 사라진 모습입니다.
아니면 아예 Unnamed:0 을 삭제해도 됩니다.

 

이상으로 XML 자료 파싱하고 CSV로 저장하는 방법에 대한 글을 마치도록 하겠습니다.

긴 글 봐주셔서 감사합니다.

 

끝으로 전체 코드를 올려놓겠습니다.

 

[전체코드]

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#라이브러리 import
import requests
from bs4 import BeautifulSoup
import pandas as pd
 
#요청url 잘게 자르기
url = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson?"
serviceKey = "serviceKey=인증키(인코딩)"
numOfRows="&numOfRows=10"
StartCreateDt = "&startCreateDt=20210101"
endCreateDt = "&endCreateDt=20210630"
 
#항목 parsing 함수작성하기
def parse():
    try:
        SEQ = item.find("seq").get_text()
        STATE_DT = item.find("stateDt").get_text()
        STATE_TIME = item.find("stateTime").get_text()
        DECIDE_CNT = item.find("decideCnt").get_text()
        CLEAR_CNT = item.find("clearCnt").get_text()
        EXAM_CNT = item.find("examCnt").get_text()
        DEATH_CNT = item.find("deathCnt").get_text()
        CARE_CNT = item.find("careCnt").get_text()
        RESUTL_NEG_CNT = item.find("resutlNegCnt").get_text()
        ACC_EXAM_CNT = item.find("accExamCnt").get_text()
        ACC_EXAM_COMP_CNT = item.find("accExamCompCnt").get_text()
        ACC_DEF_RATE = item.find("accDefRate").get_text()
        CREATE_DT = item.find("createDt").get_text()
        UPDATE_DT = item.find("updateDt").get_text()
        return {
            "게시글번호":SEQ,
            "기준일":STATE_DT,
            "기준시간":STATE_TIME,
            "확진자 수":DECIDE_CNT,
            "격리해제 수":CLEAR_CNT,
            "검사진행수":EXAM_CNT,
            "사망자 수":DEATH_CNT,
            "치료중 환자수":CARE_CNT,
            "결과 음성 수":RESUTL_NEG_CNT,
            "누적 검사 수":ACC_EXAM_CNT,
            "누적 검사 완료 수":ACC_EXAM_COMP_CNT,
            "누적 확진률":ACC_DEF_RATE,
            "등록일시분초":CREATE_DT,
            "수정일시분초":UPDATE_DT
        }
    except AttributeError as e:
        return {
            "게시글번호":None,
            "기준일":None,
            "기준시간":None,
            "확진자 수":None,
            "격리해제 수":None,
            "검사진행수":None,
            "사망자 수":None,
            "치료중 환자수":None,
            "결과 음성 수":None,
            "누적 검사 수":None,
            "누적 검사 완료 수":None,
            "누적 확진률":None,
            "등록일시분초":None,
            "수정일시분초":None
        }
 
#parsing 하기
result = requests.get(url+serviceKey+"&pageNo=1"+numOfRows+StartCreateDt+endCreateDt)
soup = BeautifulSoup(result.text,'lxml-xml')
items = soup.find_all("item")
 
row = []
for item in items:
    row.append(parse())
 
#pandas 데이터프레임에 넣기
df = pd.DataFrame(row)
 
#csv 파일로 저장하기
df.to_csv("코로나20210101-20210630.csv",mode='w',)
 
#csv 파일 불러오기        
data = pd.read_csv("코로나20210101-20210630.csv",index_col=0)
df2 = pd.DataFrame(data)
cs

[목차로 돌아가기]

728x90