데이터 과학 기반의 파이썬 빅데이터 분석 Chapter05 파이썬 크롤링-API 이용

2023. 1. 5. 23:46Python/데이터 과학 기반의 파이썬 빅데이터 분석(한빛 아카데미)

 

01. 네이버 API를 이용한 크롤링

[CODE 0] 먼저, 전체 작업 스토리를 설계한다.

def main():
    node = 'news'   #크롤링할 대상
    srcText = input('검색어를 입력하세요: ')
    cnt = 0
    jsonResult = []

    jsonResponse = getNaverSearch(node, srcText, 1, 100)    #[CODE 2]
    total = jsonResponse['total']

    while ((jsonResponse != None) and (jsonResponse['display'] != 0)):
          for post in jsonResponse['items']:
              cnt += 1
              getPostData(post, jsonResult, cnt)  #[CODE 3]

          start = jsonResponse['start'] + jsonResponse['display']
          jsonResponse = getNaverSearch(node, srcText, start, 100)    #[CODE 2]

    print('전체 검색: %d 건' %total)

    with open('%s_naver_%s.json' % (srcText, node), 'w', encoding = 'utf8') as outfile:
          jsonFile = json.dumps(jsonResult, indent = 4, sort_keys = True, 
                                ensure_ascii = False)
        
          outfile.write(jsonFile)
    
    print("가져온 데이터 : %d 건" %(cnt))
    print('%s_naver_%s.json SAVED' % (srcText, node))

[CODE 1] url 접속을 요청하고, 응답을 받아서 반환하는 부분을 작성한다.

def getResquestUrl(url):
    req = urllib.request.Request(url)
    req.add_header("X-Naver-Client-Id", client_id)
    req.add_header("X-Naver-Client-Secret", client_secret)

    try:
        response = urllib.request.urlopen(req)
        if response.getcode() == 200:
            print("[%s] Url Request Success" % datetime.datetime.now())
            return response.read().decode('utf-8')
    except Exception as e:
        print(e)
        print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
        return None

[CODE2] 네이버 뉴스 검색 url을 만들고, [CODE1]의 getRequestURL(url)을 호출하여 반환받은 응답 데이터를 파이썬 json 형식으로 반환하는 부분이다.

def getNaverSearch(node, srcText, start, display):
    base = "https://openapi.naver.com/v1/search"
    node = "/%s.json" % node
    parameters = "?query=%s&start=%s&display=%s" % (urllib.parse.quote(srcText), start, display)

    url = base + node + parameters
    responseDecode = getResquestUrl(url) #[CODE 1]

    if (responseDecode == None):
        return None
    else:
        return json.loads(responseDecode)

[CODE3] JSON 형식의 응답 데이터를 필요한 항목만 정리하여 딕셔너리 리스트인 jsonResult를 구성하고, 반환하도록 작성한다.

def getPostData(post, jsonResult, cnt):
    title = post['title']
    description = post['description']
    org_link = post['originallink']
    link = post['link']

    pDate = datetime.datetime.strptime(post['pubDate'], '%a, %d %b %Y %H:%M:%S +0900')
    pDate = pDate.strftime('%Y-%m-%d %H:%M:%S')

    jsonResult.append({'cnt':cnt, 'title':title, 'description': description, 'org_link': org_link, 'link': org_link, 'pDate': pDate})
    return

nvCrawler.py(전체 프로그램)

import os
import sys
import urllib.request
import datetime
import time
import json

client_id = '7gEpliLXF0aVmrsq7ffv'
client_secret = 'ScyStQ2AXP'

#[CODE1]
def getResquestUrl(url):
    req = urllib.request.Request(url)
    req.add_header("X-Naver-Client-Id", client_id)
    req.add_header("X-Naver-Client-Secret", client_secret)

    try:
        response = urllib.request.urlopen(req)
        if response.getcode() == 200:
            print("[%s] Url Request Success" % datetime.datetime.now())
            return response.read().decode('utf-8')
    except Exception as e:
        print(e)
        print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
        return None

#[CODE2]
def getNaverSearch(node, srcText, start, display):
    base = "https://openapi.naver.com/v1/search"
    node = "/%s.json" % node
    parameters = "?query=%s&start=%s&display=%s" % (urllib.parse.quote(srcText), start, display)

    url = base + node + parameters
    responseDecode = getResquestUrl(url) #[CODE 1]

    if (responseDecode == None):
        return None
    else:
        return json.loads(responseDecode)

#[CODE3]
def getPostData(post, jsonResult, cnt):
    title = post['title']
    description = post['description']
    org_link = post['originallink']
    link = post['link']

    pDate = datetime.datetime.strptime(post['pubDate'], '%a, %d %b %Y %H:%M:%S +0900')
    pDate = pDate.strftime('%Y-%m-%d %H:%M:%S')

    jsonResult.append({'cnt':cnt, 'title':title, 'description': description, 'org_link': org_link, 'link': org_link, 'pDate': pDate})
    return

#[CODE0]
def main():
    node = 'news'   #크롤링할 대상
    srcText = input('검색어를 입력하세요: ')
    cnt = 0
    jsonResult = []

    jsonResponse = getNaverSearch(node, srcText, 1, 100)    #[CODE 2]
    total = jsonResponse['total']

    while ((jsonResponse != None) and (jsonResponse['display'] != 0)):
          for post in jsonResponse['items']:
              cnt += 1
              getPostData(post, jsonResult, cnt)  #[CODE 3]

          start = jsonResponse['start'] + jsonResponse['display']
          jsonResponse = getNaverSearch(node, srcText, start, 100)    #[CODE 2]

    print('전체 검색: %d 건' %total)

    with open('%s_naver_%s.json' % (srcText, node), 'w', encoding = 'utf8') as outfile:
          jsonFile = json.dumps(jsonResult, indent = 4, sort_keys = True, 
                                ensure_ascii = False)
        
          outfile.write(jsonFile)
    
    print("가져온 데이터 : %d 건" %(cnt))
    print('%s_naver_%s.json SAVED' % (srcText, node))

if __name__ == '__main__':
    main()
검색어를 입력하세요: 월드컵
[2023-01-05 06:31:05.483106] Url Request Success
[2023-01-05 06:31:06.711095] Url Request Success
[2023-01-05 06:31:07.900064] Url Request Success
[2023-01-05 06:31:09.116239] Url Request Success
[2023-01-05 06:31:10.326584] Url Request Success
[2023-01-05 06:31:11.557006] Url Request Success
[2023-01-05 06:31:12.774514] Url Request Success
[2023-01-05 06:31:14.057419] Url Request Success
[2023-01-05 06:31:15.289108] Url Request Success
[2023-01-05 06:31:16.534831] Url Request Success
HTTP Error 400: Bad Request
[2023-01-05 06:31:17.438939] Error for URL : https://openapi.naver.com/v1/search/news.json?query=%EC%9B%94%EB%93%9C%EC%BB%B5&start=1001&display=100
전체 검색: 2769589 건
가져온 데이터 : 1000 건
월드컵_naver_news.json SAVED

02. 공공데이터 API 기반 크롤링

[CODE0] 전체 작업 스토리를 구성한다.

def main():
    jsonResult = []
    result = []

    print("<< 국내 입국한 외국인의 통계 데이터를 수집합니다. >>")
    nat_cd = input('국가 코드를 입력하세요(중국: 112 / 일본: 130 / 미국: 275) :')
    nStartYear = int(input('데이터를 몇 년부터 수집할까요? : '))
    nEndYear = int(input('데이터를 몇 년까지 수집할까요? : '))
    ed_cd = "E"     #E : 방한외래관광객, D: 해외 출국)
    jsonResult, result, natName, ed, dataEND = getTourismStatsService(nat_cd, ed_cd, nStartYear, nEndYear)  #[CODE 3]

    #파일저장 1 : json 파일
    with open('./%s_%s_%d_%s.json' % (natName, ed, nStartYear, dataEND), 'w', encoding='utf8') as outfile:
        jsonFile = json.dumps(jsonResult, indent = 4, sort_keys = True, ensure_ascii = False)
        outfile.write(jsonFile)
    #파일저장 2 : CSV 파일
    columns = ["입국자국가", "국가코드", "입국연월", "입국자 수"]
    result_df = pd.DataFrame(result, columns = columns)
    result_df.to_csv('./%s_%s_%d_%s.csv' % (natName, ed, nStartYear, dataEND), index = False, encoding = 'cp949')

[CODE1] url 접속을 요청하고, 응답을 받아서 반환한다.

def getRequestUrl(url):
    req = urllib.request.Request(url)
    try:
        response = urllib.request.urlopen(req)
        if response.getcode() == 200:
            print("[%s] Url Resquest Success" % datetime.datetime.now())
            return response.read().decode('utf-8')
    except Exception as e:
        print(e)
        print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
        return None

[CODE2] 출입국관광통계서비스의 오픈 API를 사용하여 데이터 요청 url을 만들고, [CODE1]의 getRequestUrl(url)을 호출해서 받은 응답 데이터를 반환한다.

def getTourismStatsItem(yyyymm, nat_cd, ed_cd):
    service_url = "http://openapi.tour.go.kr/openapi/service/EdrcntTourismStatsService/getEdrcntTourismStatsList"
    parameters = "?_type=json&serviceKey=" + ServiceKey     #인증키
    parameters += "&YM=" + yyyymm
    parameters += "&NAT_CD=" + nat_cd
    parameters += "&ED_CD=" + ed_cd

    url = service_url + parameters
    print(url)  #액세스 거부 여부 확인용 출력
    responseDecode = getRequestUrl(url)     #[CODE1]

    if (responseDecode == None):
        return None
    else:
        return json.loads(responseDecode)

[CODE3] 수집 기간 동안 월 단위로 [CODE2]의 getTourismStatsItem()을 호출해 받은 데이터를 리스트로 묶어 반환한다.

def getTourismStatsService(nat_cd, ed_cd, nStartYear, nEndYear):
    jsonResult = []
    result = []
    natName = ''
    dataEND = "{0}{1:0>2}".format(str(nEndYear), str(12))
    isDataEnd = 0
    for year in range(nStartYear, nEndYear+1):
        for month in range(1, 13):
            if(isDataEnd == 1): break
            yyyymm = "{0}{1:0>2}".format(str(year), str(month))
            jsonData = getTourismStatsItem(yyyymm, nat_cd, ed_cd)   #[CODE2]
            if (jsonData['response']['header']['resultMsg'] == 'OK'):
                if jsonData['response']['body']['items'] == '':
                    isDataEnd = 1
                    dataEND = "{0}{1:0>2}".format(str(year), str(month-1))
                    print("데이터 없음... \n 제공되는 통계 데이터는 %s년 %s월까지입니다." %(str(year), str(month-1)))
                    break
                print(json.dumps(jsonData, indent = 4,
                      sort_keys = True, ensure_ascii = False))
                natName = jsonData['response']['body']['items']['item']['natKorNm']
                natName = natName.replace(' ', '')
                num = jsonData['response']['body']['items']['item']['num']
                ed = jsonData['response']['body']['items']['item']['ed']
                print('[ %s_%s : %s ]' %(natName, yyyymm, num))
                print('-------------------------------------------------')
                jsonResult.append({'nat_name': natName, 'nat_cd': nat_cd, 'yyyymm': yyyymm, 'visit_cnt': num})
                result.append([natName, nat_cd, yyyymm, num])
    return (jsonResult, result, natName, ed, dataEND)

openapi_tour.py(전체 프로그램)

import os
import sys
import urllib.request
import datetime
import time
import json
import pandas as pd

ServiceKey = "KPgnUR%2FxcZK%2B9UjELd8AmigpgDzmRsOGqCtNRcINi8ZO0IZMjIAaPik7d6BeIIMd9pMmAwwFjKTHLkrjh321Dg%3D%3D"

#[CODE1]
def getRequestUrl(url):
    req = urllib.request.Request(url)
    try:
        response = urllib.request.urlopen(req)
        if response.getcode() == 200:
            print("[%s] Url Resquest Success" % datetime.datetime.now())
            return response.read().decode('utf-8')
    except Exception as e:
        print(e)
        print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
        return None

#[CODE2]
def getTourismStatsItem(yyyymm, nat_cd, ed_cd):
    service_url = "http://openapi.tour.go.kr/openapi/service/EdrcntTourismStatsService/getEdrcntTourismStatsList"
    parameters = "?_type=json&serviceKey=" + ServiceKey     #인증키
    parameters += "&YM=" + yyyymm
    parameters += "&NAT_CD=" + nat_cd
    parameters += "&ED_CD=" + ed_cd

    url = service_url + parameters
    print(url)  #액세스 거부 여부 확인용 출력
    responseDecode = getRequestUrl(url)     #[CODE1]

    if (responseDecode == None):
        return None
    else:
        return json.loads(responseDecode)

#[CODE3]
def getTourismStatsService(nat_cd, ed_cd, nStartYear, nEndYear):
    jsonResult = []
    result = []
    natName = ''
    dataEND = "{0}{1:0>2}".format(str(nEndYear), str(12))
    isDataEnd = 0
    for year in range(nStartYear, nEndYear+1):
        for month in range(1, 13):
            if(isDataEnd == 1): break
            yyyymm = "{0}{1:0>2}".format(str(year), str(month))
            jsonData = getTourismStatsItem(yyyymm, nat_cd, ed_cd)   #[CODE2]
            if (jsonData['response']['header']['resultMsg'] == 'OK'):
                if jsonData['response']['body']['items'] == '':
                    isDataEnd = 1
                    dataEND = "{0}{1:0>2}".format(str(year), str(month-1))
                    print("데이터 없음... \n 제공되는 통계 데이터는 %s년 %s월까지입니다." %(str(year), str(month-1)))
                    break
                print(json.dumps(jsonData, indent = 4,
                      sort_keys = True, ensure_ascii = False))
                natName = jsonData['response']['body']['items']['item']['natKorNm']
                natName = natName.replace(' ', '')
                num = jsonData['response']['body']['items']['item']['num']
                ed = jsonData['response']['body']['items']['item']['ed']
                print('[ %s_%s : %s ]' %(natName, yyyymm, num))
                print('-------------------------------------------------')
                jsonResult.append({'nat_name': natName, 'nat_cd': nat_cd, 'yyyymm': yyyymm, 'visit_cnt': num})
                result.append([natName, nat_cd, yyyymm, num])
    return (jsonResult, result, natName, ed, dataEND)

#[CODE0]
def main():
    jsonResult = []
    result = []

    print("<< 국내 입국한 외국인의 통계 데이터를 수집합니다. >>")
    nat_cd = input('국가 코드를 입력하세요(중국: 112 / 일본: 130 / 미국: 275) :')
    nStartYear = int(input('데이터를 몇 년부터 수집할까요? : '))
    nEndYear = int(input('데이터를 몇 년까지 수집할까요? : '))
    ed_cd = "E"     #E : 방한외래관광객, D: 해외 출국)
    jsonResult, result, natName, ed, dataEND = getTourismStatsService(nat_cd, ed_cd, nStartYear, nEndYear)  #[CODE 3]

    #파일저장 1 : json 파일
    with open('./%s_%s_%d_%s.json' % (natName, ed, nStartYear, dataEND), 'w', encoding='utf8') as outfile:
        jsonFile = json.dumps(jsonResult, indent = 4, sort_keys = True, ensure_ascii = False)
        outfile.write(jsonFile)
    #파일저장 2 : CSV 파일
    columns = ["입국자국가", "국가코드", "입국연월", "입국자 수"]
    result_df = pd.DataFrame(result, columns = columns)
    result_df.to_csv('./%s_%s_%d_%s.csv' % (natName, ed, nStartYear, dataEND), index = False, encoding = 'cp949')

if __name__ == '__main__':
    main()
<< 국내 입국한 외국인의 통계 데이터를 수집합니다. >>
국가 코드를 입력하세요(중국: 112 / 일본: 130 / 미국: 275) :112
데이터를 몇 년부터 수집할까요? : 2017
데이터를 몇 년까지 수집할까요? : 2021
http://openapi.tour.go.kr/openapi/service/EdrcntTourismStatsService/getEdrcntTourismStatsList?_type=json&serviceKey=KPgnUR%2FxcZK%2B9UjELd8AmigpgDzmRsOGqCtNRcINi8ZO0IZMjIAaPik7d6BeIIMd9pMmAwwFjKTHLkrjh321Dg%3D%3D&YM=201701&NAT_CD=112&ED_CD=E
[2023-01-05 08:10:31.469789] Url Resquest Success
{
    "response": {
        "body": {
            "items": {
                "item": {
                    "ed": "방한외래관광객",
                    "edCd": "E",
                    "natCd": 112,
                    "natKorNm": "중  국",
                    "num": 565243,
                    "rnum": 1,
                    "ym": 201701
                }
            },
            "numOfRows": 10,
            "pageNo": 1,
            "totalCount": 1
        },
        "header": {
            "resultCode": "0000",
            "resultMsg": "OK"
        }
    }
}
[ 중국_201701 : 565243 ]
-------------------------------------------------
http://openapi.tour.go.kr/openapi/service/EdrcntTourismStatsService/getEdrcntTourismStatsList?_type=json&serviceKey=KPgnUR%2FxcZK%2B9UjELd8AmigpgDzmRsOGqCtNRcINi8ZO0IZMjIAaPik7d6BeIIMd9pMmAwwFjKTHLkrjh321Dg%3D%3D&YM=201702&NAT_CD=112&ED_CD=E
[2023-01-05 08:10:32.022056] Url Resquest Success
{
    "response": {
        "body": {
            "items": {
                "item": {
                    "ed": "방한외래관광객",
                    "edCd": "E",
                    "natCd": 112,
                    "natKorNm": "중  국",
                    "num": 590790,
                    "rnum": 1,
                    "ym": 201702
                }
            },
            "numOfRows": 10,
            "pageNo": 1,
            "totalCount": 1
        },
        "header": {
            "resultCode": "0000",
            "resultMsg": "OK"
        }
    }
}
[ 중국_201702 : 590790 ]
-------------------------------------------------
.
.
.
-------------------------------------------------
http://openapi.tour.go.kr/openapi/service/EdrcntTourismStatsService/getEdrcntTourismStatsList?_type=json&serviceKey=KPgnUR%2FxcZK%2B9UjELd8AmigpgDzmRsOGqCtNRcINi8ZO0IZMjIAaPik7d6BeIIMd9pMmAwwFjKTHLkrjh321Dg%3D%3D&YM=202112&NAT_CD=112&ED_CD=E
[2023-01-05 08:11:04.334997] Url Resquest Success
{
    "response": {
        "body": {
            "items": {
                "item": {
                    "ed": "방한외래관광객",
                    "edCd": "E",
                    "natCd": 112,
                    "natKorNm": "중  국",
                    "num": 11691,
                    "rnum": 1,
                    "ym": 202112
                }
            },
            "numOfRows": 10,
            "pageNo": 1,
            "totalCount": 1
        },
        "header": {
            "resultCode": "0000",
            "resultMsg": "OK"
        }
    }
}
[ 중국_202112 : 11691 ]
-------------------------------------------------