Python을 이용한 데이터 분석 - API를 이용한 크롤링 (1)
ICT이노베이션스퀘어 AI기본과정(CNU) 교육을 듣고 정리한 내용입니다.
AI기본과정(CNU) 교육 자료를 참고하였습니다.
API를 이용한 데이터 수집
네이버 API를 이용한 크롤링
1. 크롤링이란?
(1) 크롤링
- 웹에서 데이터를 수집하는 작업
- 크롤러 또는 스파이더라는 프로그램으로 웹사이트에서 데이터 추출
(2) 웹 API
- 일반적으로 HTTP 통신을 이용할 때 사용
- 지도, 검색, 주가, 환율 등 다양한 정보를 가지고 있는 웹 사이트의 기능을 외부에서 쉽게 사용할 수 있도록 사용 절차와 규약을 정의한 것
2. 네이버 개발자 가입
(1) 네이버 개발자 센터(https://developers.naver.com/main/) 접속
(2) Products > 서비스 API > 데이터랩 > 오픈 API 이용 신청
(3) 애플리케이션 등록
(4) 애플리케이션 정보 확인
(5) Documents > 서비스 API > 검색 > API 호출 예제, API 기본 정보, 요청 변수 확인
(6) API 호출 예제 - Python
# 네이버 검색 API예제는 블로그를 비롯 전문자료까지 호출방법이 동일하므로 blog검색만 대표로 예제를 올렸습니다.
# 네이버 검색 Open API 예제 - 블로그 검색
import os
import sys
import urllib.request
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
encText = urllib.parse.quote("검색할 단어")
url = "https://openapi.naver.com/v1/search/blog?query=" + encText # json 결과
# url = "https://openapi.naver.com/v1/search/blog.xml?query=" + encText # xml 결과
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
rescode = response.getcode()
if(rescode==200):
response_body = response.read()
print(response_body.decode('utf-8'))
else:
print("Error Code:" + rescode)
3. 네이버 뉴스 크롤링
(1) 전체 작업 설계
작업 설계 | 사용할 코드 |
1. 검색어 지정하기 | srcText = "금리" |
2. 네이버 뉴스 검색하기 | getNaverSearch() |
2.1 url 구성하기 | url = base + node + scrText |
2.2 url 접속과 검색 요청하기 | urllib.request.urlopen() |
2.3 요청 결과를 응답 JSON으로 받기 | json.load() |
3. 응답 데이터를 정리하여 리스트에 저장하기 | getPostData() |
4. 리스트를 JSON 파일로 저장하기 | json.dumps() |
(2) 프로그램 구성 설계
(3) 함수 설계
[CODE 0] 전체 작업 스토리 설계
import
import os
import sys
import urllib.request
import datetime
import time
import json
지역 변수
node : 네이버 검색 API에서 검색할 대상 노드
srcText : 사용자 입력으로 받은 검색어 저장
cnt : 검색 결과 카운트
jsonResult : 검색 결과를 정리하여 저장할 리스트 객체
jsonResponse : 네이버 뉴스 검색에 대한 응답을 저장하는 객체
total : 전체 검색 결과 개수
post : 응답받은 검색 결과 중에서 한 개를 저장한 객체
items : 전체 응답 검색 결과로 내부 항목은 title, originallink, link, description, pubDate
jsonFile : JSON 파일에 저장할 데이터를 담은 객체
메서드
input("검색어를 입력하세요:") : 사용자로부터 입력받음
getNaverSearch(node, srcText, 1, 100) : 1부터 100개의 검색 결과 처리([CODE 2])
getPostData() : 검색 결과 한 개를 처리([CODE 2])
json.dumps() : 객체를 JSON 형식으로 변환
1 def main():
2 node = 'news' #크롤링할 대상
3 srcText = input('검색어를 입력하세요: ')
4 cnt = 0
5 jsonResult = []
6
7 jsonResponse = getNaverSearch(node, srcText, 1, 100) #[CODE 2]
8 total = jsonResponse['total']
9
10 while ((jsonResponse != None) and (jsonResponse['display'] != 0)):
11 for post in jsonResponse['items']:
12 cnt += 1
13 getPostData(post, jsonResult, cnt) #[CODE 3]
14
15 start = jsonResponse['start'] + jsonResponse['display']
16 jsonResponse = getNaverSearch(node, srcText, start, 100) #[CODE 2]
17
18 print('전체 검색 : %d 건' %total)
19
20 with open('%s_naver_%s.json' % (srcText, node), 'w', encoding='utf8') as outfile:
21 jsonFile = json.dumps(jsonResult, indent=4, sort_keys=True, ensure_ascii=False)
22
23 outfile.write(jsonFile)
24
25 print("가져온 데이터 : %d 건" %(cnt))
26 print ('%s_naver_%s.json SAVED' % (srcText, node))
2행 : 네이버 뉴스 검색을 위해 검색 API 대상을 'news'로 설정
3행 : 파이썬 셀 창에서 검색어를 입력받아 srcText에 저장
7행 : getNaverSearch() 함수를 호출하여 start = 1, display = 100에 대한 검색 결과를 반환받아 jsonResponse에 저장
10 ~ 16행 : 검색 결과(jsonResponse)에 데이터가 있는 동안 for문(11~13행)으로 검색 결과를 한 개씩 처리하는 작업(getPostData())을 반복, 반복 작업이 끝나면 다음 검색 결과 100개를 가져오기 위해 start 위치를 변경
16행 : getNaverSearch() 함수를 호출하여 새로운 검색 결과를 jsonResponse에 저장하고 for문 11~13행을 다시 반복
20 ~ 23행 : 파일 객체를 생성하여 정리된 데이터를 JSON 파일에 저장
네이버 검색 API 개발자 가이드
URL | 뉴스 | https://openapi.naver.com/v1/search/news.json |
블로그 | https://openapi.naver.com/v1/search/blog.json | |
카페 | https://openapi.naver.com/v1/search/cafearticle.json | |
영화 | https://openapi.naver.com/v1/search/movie.json | |
쇼핑 | https://openapi.naver.com/v1/search/shop.json |
요청 변수 | query | 검색을 원하는 문자열, UTF-8로 인코딩 |
start | 검색 시작 위치, 최대 1000까지 가능, 1(기본값) ~ 1000(최대값) | |
display | 검색 결과 출력 건수를 지정, 10(기본값) ~ 100(최대값) |
응답 변수 | items | 검색 결과로 title, originallink, link, description, pubDate를 포함 |
title | 검색 결과 문서의 제목 | |
link | 검색 결과 문서를 제공하는 네이버의 하이퍼텍스트 link | |
originallink | 검색 결과 문서를 제공하는 언론사의 하이퍼텍스트 link | |
description | 검색 결과 문서의 내용을 요약한 정보 | |
pubDate | 검색 결과 문서가 네이버에 제공된 시간 |
[CODE 1] url 접속을 요청하고 응답을 받아서 반환하는 부분을 작성
매개변수
url : 네이버 뉴스 검색("금리")에 대한 url
지역 변수
req : url 접속 요청(request) 객체
app_id : 네이버 개발자로 등록하고 받은 Client ID
app_secret : 네이버 개발자로 등록하고 받은 Client Secret
response : 네이버 서버에서 받은 응답을 저장하는 객체
메서드
urllib.request.Request() : urllib 패키지의 request 모듈에 있는 Request() 함수로 네이버 서버에 보낼 요청(request) 객체를 생성
Request.add_header() : 서버에 보내는 요청 객체에 헤더 정보 추가
urllib.request.urlopen() : 서버에서 받은 응답을 변수에 저장하기 위해 메모리로 가져오는 urllib 패키지의 request 모듈에 있는 함수
response.getcode() : 요청 처리에 대한 응답 상태를 확인하는 response 객체의 멤버 함수로, 상태 코드가 200이면 요청 처리 성공을 나타냄
datetime.datetime.now() : 현재 시간을 구하는 함수
response.read().decode("utf-8") : utf-8 형식으로 문자열을 디코딩
1 def getRequestUrl(url):
2 req = urllib.request.Request(url)
3 req.add_header("X-Naver-Client-Id", client_id)
4 req.add_header("X-Naver-Client-Secret", client_secret)
5
6 try:
7 response = urllib.request.urlopen(req)
8 if response.getcode() == 200:
9 print("[%s] Url Request Success" % datetime.datetime.now())
10 return response.read().decode('utf-8')
11 except Exception as e:
12 print(e)
13 print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
14 return None
2행 : 매개변수로 받은 url에 대한 요청을 보낼 객체 생성
3 ~ 4행 : API를 사용하기 위한 Client ID와 Client Secret 코드를 요청 객체 헤드에 추가
7행 : 요청 객체를 보내고 그에 대한 응답을 받아 response 객체에 저장
8 ~ 10행 : getcode()로 response 객체에 저장된 코드를 확인, 200이면 요청이 정상 처리된 것이므로 성공 메시지를 파이썬 셀 창에 출력하고 응답을 utf-8 형식으로 디코딩하여 반환
11 ~ 14행 : 요청이 처리되지 않은 예외 사항(exception)이 발생하면 에러 메시지를 파이썬 셀 창에 출력
[CODE 2] 네이버 뉴스 검색 url을 만들고 [CODE 1]의 getRequestUrl(url)을 호출하여 반환받은 응답 데이터를 파이썬 json 형식으로 반환하는 부분
매개변수
node : 네이버 검색 API를 이용하여 검색할 대상 노드(news, blog, cafearticle, movie, shop 등)
srcText : 검색어
page_start : 검색 시작 위치(1 ~ 1000)
display : 출력 건수(10 ~ 100)
지역 변수
base : 검색 url의 기본 주소
node : 검색 대상에 따른 json 파일 이름
parameter : url에 추가할 검색어와 검색 시작 위치, 출력 건수 등의 매개변수
responseDecode : getRequestUrl(url)을 호출하여 반환받은 응답 객체(utf-8로 디코드)
메서드
getRequest(url) : [CODE 1]을 호출하여 url 요청에 대한 응답을 받음
json.loads(responseDecode) : 응답 객체를 파이썬이 처리할 수 있는 JSON 형식으로 변환
1 def getNaverSearch(node, srcText, page_start, display):
2 base = "https://openapi.naver.com/v1/search"
3 node = "/%s.json" % node
4 parameters = "?query=%s&start=%s&display=%s" % (urllib.parse.quote(srcText), page_start, display)
5
6 url = base + node + parameters
7 responseDecode = getRequestUrl(url) #[CODE 1]
8
9 if (responseDecode == None):
10 return None
11 else:
12 return json.loads(responseDecode)
2 ~ 6행 : 네이버 검색 API 정보에 따라 요청 URL 구성
7행 : 완성한 url을 이용하여 getRequestUrl() 함수를 호출하여 받은 utf-8 디코드 응답을 responseDecode에 저장
12행 : 서버에서 받은 JSON 형태의 응답 객체를 파이썬 객체로 로드하여 반환
[CODE 3] JSON 형식의 응답 데이터를 필요한 항목만 정리하여 딕셔너리 리스트인 jsonResult를 구성하고 반환하도록 작성
매개변수
post : 응답으로 받은 검색 결과 데이터 중에서 결과 한 개를 저장한 객체
jsonResult : 필요한 부분만 저장하여 반환할 리스트 객체
cnt : 현재 작업 중인 검색 결과의 번호
지역 변수
post["title"] : post 객체의 title 항목에 저장된 값
post["description"] : post 객체의 description 항목에 저장된 값
post["originallink"] : post 객체의 originallink 항목에 저장된 값
post["link"] : post 객체의 link 항목에 저장된 값
메서드
datetime.datetime.strptime() : 문자열을 날짜 객체 형식으로 변환
pDate.strftime() : 날짜 객체의 표시 형식을 지정
jsonResult.append() : 리스트 객체인 jsonResult에 원소 추가
1 def getPostData(post, jsonResult, cnt):
2 title = post['title']
3 description = post['description']
4 org_link = post['originallink']
5 link = post['link']
6
7 pDate = datetime.datetime.strptime(post['pubDate'], '%a, %d %b %Y %H:%M:%S +0900')
8 pDate = pDate.strftime('%Y-%m-%d %H:%M:%S')
9
10 jsonResult.append({'cnt':cnt, 'title':title, 'description': description, 'org_link':org_link, 'link': org_link, 'pDate':pDate})
11 return
2 ~ 5행 : 검색 결과가 들어있는 post 객체에서 필요한 데이터 항목을 추출하여 변수에 저장
7행 : 네이버에서 제공하는 시간인 pubDate는 문자열 형태이므로 날짜 객체로 변환, pubDate는 그리니치 평균시 형식을 사용하는데 한국 표준시보다 9시간 느리므로 +0900을 사용해 한국 표준시로 맞춤
8행 : 수정된 날짜를 "연-월-일 시:분:초" 형식으로 나타냄
10행 : 2 ~ 5행에서 저장한 데이터를 딕셔너리 형태인 {"키" : 값}으로 구성하여 리스트 객체인 jsonResult에 추가
(4) 전체 프로그램 작성
전체 코드
import os
import sys
import urllib.request
import datetime
import time
import json
client_id = "Client ID"
client_secret = "Client Secret"
# CODE 1
def getRequestUrl(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
# CODE 2
def getNaverSearch(node, srcText, page_start, display):
base = "https://openapi.naver.com/v1/search"
node = "/%s.json" % node
parameters = "?query=%s&start=%s&display=%s" % (urllib.parse.quote(srcText), page_start, display)
url = base + node + parameters
responseDecode = getRequestUrl(url)
if (responseDecode == None):
return None
else:
return json.loads(responseDecode)
# CODE 3
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
# CODE 0
def main():
node = 'news'
srcText = input('검색어를 입력하세요: ')
cnt = 0
jsonResult = []
jsonResponse = getNaverSearch(node, srcText, 1, 100)
total = jsonResponse['total']
while ((jsonResponse != None) and (jsonResponse['display'] != 0)):
for post in jsonResponse['items']:
cnt += 1
getPostData(post, jsonResult, cnt)
start = jsonResponse['start'] + jsonResponse['display']
jsonResponse = getNaverSearch(node, srcText, start, 100)
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()
# "봄"을 입력하면 JSON 파일이 생성됨
결과 확인