트위터 크롤링이 너무 귀찮아서 “일단 정형데이터부터 써야지ㅎㅎ” 하는 마음으로 시작했는데 생각보다 귀찮은 작업이었습니다.
KOPIS는 공연예술통합전산망으로 작년 말?즈음에 페이스북을 통해 웨비나 소식으로 처음 접하게 되었습니다.
웨비나에서 KOPIS 활용 공모전 우수사례 발표가 있었는데, 결과물들을 보고 나중에 한 번 써먹어봐야지 하다가 드디어 쓰게 되었습니다.
공연시설별 통계 목록 조회
KOPIS Open API 탭으로 가면 개발자 가이드와 공통코드가 제공되고 있는데, 안타깝게도 링크 첨부가 되지 않네요.
가장 먼저 해야 할 일은 API Key를 발급받는 일입니다.
Key를 발급받으면 이메일로 개별 키를 전송해주는데, 잊어버리지 말고 잘 적어두세요.
1차 놀람+분노 : API를 통해 출력되는 모든 데이터는 xml 형식입니다.
정량데이터 분석을 위해 2019년 6월 이후의 쇼노트 공연의 관객수 및 판매금액을 조회하고 싶었지만, 해당 요청에서는 금액을 조회할 수 없었습니다.
api_key = 'API_Key' # 메일로 전달받은 API를 입력하면 된다.
start_date = '20220101'
end_date = '20220121'
city_code = '11' # 11 == 서울
theater = 'LG아트센터'
url = f"http://www.kopis.or.kr/openApi/restful/prfstsPrfByFct?service={api_key}&cpage=1&rows=10&stdate={start_date}&eddate={end_date}&sharea={city_code}&shprfnmfct={theater}"
요청을 전달하기 위한 url에서 필요로 하는 데이터는 날짜, 도시코드, 극장입니다.
2차 놀람+불편 : 극장명은 한글로 적는다. 띄어쓰기도 한다.
<prfsts>
<prfst>
<seatcnt>1103</seatcnt>
<prfprocnt>0</prfprocnt>
<totnmrs>12074</totnmrs>
<prfnmplc>LG아트센터</prfnmplc>
<prfdtcnt>23</prfdtcnt>
<prfnmfct>LG아트센터</prfnmfct>
</prfst>
</prfsts>
3차 놀람+분노 : 일별 데이터를 제공하지 않고 기간 총합 데이터를 출력한다…
어제 너무 짜증나서 때려친 덕분에 오늘 아침에 시작했는데, 덕분에 분노로 잠이 달아났습니다.
어쨋든 저는 일별 데이터를 엑셀 파일로 저장하고 싶고, 어차피 개발 프로젝트도 아니고 누구 보여줄 것도 아니었던지라 그냥 하드코딩했어요.
service = 'api_key'
stdate = datetime.date(2020, 7, 4)
eddate = datetime.date(2020, 9, 12)
place = 'LG아트센터'
# get xml from kopis
check_date = stdate.strftime('%Y%m%d')
url = f'http://www.kopis.or.kr/openApi/restful/prfstsPrfByFct?service={service}&cpage=1&rows=10&stdate={check_date}&eddate={check_date}&sharea=11&shprfnmfct={place}'
여러 극장에서 공연별로 거의 100일씩 조회해야하는데 url 손으로 하나씩 쓰고있을 수는 없으니 반복을 돌릴 예정입니다.
timedelta로 하루씩 늘리며 반복할 예정이라 시작/종료 날짜는 datetime.date로 설정하고, url에 넣기 전에 strftime으로 변환해줍니다.
참고로 위 예시는 2020년 쇼노트에서 제작한 제이미 관객 데이터입니다.
while stdate <= eddate:
check_date = stdate.strftime('%Y%m%d')
--parse--
stdate += datetime.timedelta(days=1)
반복 틀은 이렇게 잡았습니다.
시작 날짜부터 종료 날짜까지 반복하고, 파싱이 끝나면 timedelta를 이용해 다음날로 이동
# get xml from kopis
url = f'http://www.kopis.or.kr/openApi/restful/prfstsPrfByFct?service={service}&cpage=1&rows=10&stdate={check_date}&eddate={check_date}&sharea=11&shprfnmfct={place}'
response = requests.get(url)
contents = response.text
requests로 url에서 xml을 텍스트로 받아오는 코드입니다.
# 데이터 결과값 예쁘게 출력해주는 코드
pp = pprint.PrettyPrinter(indent=4)
print(pp.pprint(contents))
('<?xml version="1.0" encoding="UTF-8"?>\n'
'<prfsts>\n'
' <prfst>\n'
' <seatcnt>702</seatcnt>\n'
' <prfprocnt>1</prfprocnt>\n'
' <totnmrs>623</totnmrs>\n'
' <prfnmplc>대극장</prfnmplc>\n'
' <prfdtcnt>1</prfdtcnt>\n'
' <prfnmfct>홍익대 대학로 아트센터</prfnmfct>\n'
' </prfst>\n'
' <prfst>\n'
' <seatcnt>150</seatcnt>\n'
' <prfprocnt>0</prfprocnt>\n'
' <totnmrs>0</totnmrs>\n'
' <prfnmplc>소극장</prfnmplc>\n'
' <prfdtcnt>0</prfdtcnt>\n'
' <prfnmfct>소극장</prfnmfct>\n'
' </prfst>\n'
'</prfsts>\n')
이건 제이미는 아니고 2019년 헤드윅 데이터인데, 예쁘게 프린트해서 보면 이런식으로 데이터를 불러온 상태입니다.
# xml parsing
tree = elemTree.fromstring(contents)
prfst = tree.findall("./prfst")
prfnmplc = [x.findtext("prfnmplc") for x in prfst] # 공연장명
prfdtcnt = [x.findtext("prfdtcnt") for x in prfst] # 상영 횟수
totnmrs = [x.findtext("totnmrs") for x in prfst] # 총 관객수
이제 xml을 파싱할건데, 이게 공연 회차별로 데이터를 불러다 주는 게 아니라서 주말이나 마티네처럼 하루에 2회 공연이 진행되는 날은 2회 공연의 합계만 출력합니다.
그리고 이게 데이터를 훑어보니 잘못된 내용도 있어서 주의해야 할 것 같아요.
작년 12월에 광림 젠가에 일 4회 공연이 찍혀있는데 나는 그런 적이 없어ㅇㅅㅇ
하여튼 2회차 공연은 어쩔 수 없이 총 관객수를 반토막 내서 사용해야 할 것 같습니다.
개발자 가이드에 서술된 태그를 참고해서 공연장명, 상연횟수, 총관객수를 추출합니다.
아까 위에 헤드윅 공연을 보면 홍아센은 대극장과 소극장이 있는데, 헤드윅은 대극장에서 진행됐기 때문에 대극장 관객 수만 확인하기 위해 공연장 명이 필요합니다.
for plc, cnt, tot in zip(prfnmplc, prfdtcnt, totnmrs):
re = {'date':stdate, 'plc':plc, 'cnt':cnt, 'tot':tot}
print(re)
result = result.append(re, ignore_index=True)
놀랍게도 이중 반복문을 돌렸습니다..ㅎ
진짜 그냥 나 편하려고 막 작성한 코드라서…
앞서 이야기했듯이 공연시설마다 여러개의 홀을 가지고 있는 극장이 있으므로 데이터가 여러개 생성됩니다.
이걸 하나씩 dictionary에 넣어서 df에 추가해줍니다.
참고로 result는 pd.DataFrame입니다.
최종코드
import requests
import pandas as pd
import datetime
import xml.etree.ElementTree as elemTree
"""
service : 서비스키
stdate : 검색시작기간 (YYYYMMDD)
eddate : 종료검색기간 (YYYYMMDD)
sharea : 지역(시도)코드 - 서울 : 11
shprfnmfct : 공연시설명
"""
service = 'api_key'
stdate = datetime.date(2020, 7, 4)
eddate = datetime.date(2020, 9, 12)
place = 'LG아트센터'
result = pd.DataFrame()
while stdate <= eddate:
check_date = stdate.strftime('%Y%m%d')
# get xml from kopis
url = f'http://www.kopis.or.kr/openApi/restful/prfstsPrfByFct?service={service}&cpage=1&rows=10&stdate={check_date}&eddate={check_date}&sharea=11&shprfnmfct={place}'
response = requests.get(url)
contents = response.text
# xml parsing
tree = elemTree.fromstring(contents)
prfst = tree.findall("./prfst")
prfnmplc = [x.findtext("prfnmplc") for x in prfst] # 공연장명
prfdtcnt = [x.findtext("prfdtcnt") for x in prfst] # 상영 횟수
totnmrs = [x.findtext("totnmrs") for x in prfst] # 총 관객수
for plc, cnt, tot in zip(prfnmplc, prfdtcnt, totnmrs):
re = {'date':stdate, 'plc':plc, 'cnt':cnt, 'tot':tot}
print(re)
result = result.append(re, ignore_index=True)
stdate += datetime.timedelta(days=1)
result.to_csv("/content/drive/MyDrive/GMB7/2020jamie.csv")
그렇게 조악하게 완성한 조회 코드..ㅎ
상위 5개 항목을 조회해보면 이렇게 출력됩니다.
깔끔하게 보려면 csv로 저장할 때 인덱스를 버리거나 해야겠어요.
엘아센은 홀이 하나밖에 없어서 plc가 왜 필요한가 싶긴 하겠지만, 광림이나 충무같은 경우는 홀이 여러개라서 일자별로 정보가 여러개 출력됩니다.
이것도 원하는 홀만 골라서 데이터 쌓으면 예쁘긴 하겠지만, 차라리 엑셀로 빼는게 편해요..ㅎ
하여튼 이제 정량데이터는 추출 다 했으니 나중에 대충 그래프만 그리면 될 것 같고, 트위터 크롤링 마저 하러 가야겠습니다.