AI 기본 과정

Python을 이용한 데이터 분석 - API를 이용한 크롤링 (1)

넝구리 2022. 5. 14. 21:29

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 파일이 생성됨

 

결과 확인

봄_naver_news.json 확인
네이버 웹 페이지에서 "봄"을 검색한 결과