ai-creator

OpenAPI 활용 - 일정 도우미 프로젝트 본문

오늘 배워 오늘 쓰는 OpenAPI/프로젝트

OpenAPI 활용 - 일정 도우미 프로젝트

ai-creator 2020. 2. 22. 19:50
반응형

늘 약속 시간만 되면, 무엇을 먹을지 몰라 고민 되시나요? OpenAPI를 사용하면 여러분의 고민을 덜 수 있습니다.

<< 목표 >>

  • 구글 캘린더에서 식사 일정을 조회해서 주변 맛집을 찾아주는 일정 도우미 프로그램 만들기

<< 사전 준비 >>

1. 구글 / 네이버 / 카카오 인증 키 발급

 

사용할 API : 캘린더(구글), 카카오톡(카카오), 검색-지역(네이버)

언어 & 환경(IDE) : Python3.6 & Jupter notebook

예제파일 :

1. 일정 도우미.zip
0.01MB

<< 구현 순서 >>

 

Step 0 구글 캘린더에 일정 등록
Step 1 OpenAPI 인증 (구글 + 네이버 + 카카오)
Step 2 구글 캘린더 일정 가져오기
Step 3 일정 데이터 정제하기
Step 4 맛집 검색
Step 5 '나에게 카카오톡' 보내기
Step 6 전체 코드

 

Step 0) 구글 캘린더에 일정 등록

일정을 등록할 때, 제목 특정 형식으로 지정합니다. 약속된 형식에서 "카테고리" 와 "장소" 정보를 추출해서 검색을 하는 용도입니다.

 

[카테고리-장소] 일정 제목

 

- 카테고리 :  "식사" 로 설정되어 있다면, 맛집 검색이 필요하다고 인식합니다.

- 장소 : 장소(ex."강남역", "국민대")를 키워드로 네이버 검색을 합니다.  

Step 1) OpenAPI 인증

Step 1-1) 구글 인증

# 1. 구글 API 인증
from google_auth_oauthlib.flow import InstalledAppFlow

# 구글 OAuth 클라이언트 ID Json 파일
gcreds_filename = 'auth/gcredentials.json'
# 캘린더에서 사용할 권한
SCOPES = ['https://www.googleapis.com/auth/calendar']

# 새로운 창에서 로그인 하시면 인증 정보를 얻게 됩니다.
flow = InstalledAppFlow.from_client_secrets_file(gcreds_filename, SCOPES)
gcreds = flow.run_local_server(port=0)

프로그램이 수행되면 승인을 허용해주자.

Step 1-2) 카카오 인증

# 2. 카카오톡 인증
# https://developers.kakao.com/docs/restapi/tool
# 해당 사이트에서 로그인 후 'Access token'을 얻어오세요
kcreds = {
    "access_token" : "<Access token>"
}
kheaders = {
    "Authorization": "Bearer " + kcreds.get('access_token')
}

Step 1-3) 네이버 인증

# 3. 네이버 인증
# https://developers.naver.com/apps
# 해당 사이트에서 로그인 후 "Cliend ID"와 "Client Secret"을 얻어오세요
ncreds = {
    "client_id": "<Client ID>",      
    "client_secret" : "<Client Secret>"
}
nheaders = {
    "X-Naver-Client-Id" : ncreds.get('client_id'),
    "X-Naver-Client-Secret" : ncreds.get('client_secret')
}

 

Step 2) 구글 캘린더 일정 가져오기

일정을 등록할 때, 제목특정 형식으로 지정합니다. 약속된 형식에서 "카테고리" 와 "장소" 정보를 추출해서 검색을 하는 용도입니다.

 

[카테고리-장소] 일정 제목

 

- 카테고리 :  "식사" 로 설정되어 있다면, 맛집 검색이 필요하다고 인식합니다.

- 장소 : 장소(ex."강남역", "국민대")를 키워드로 네이버 검색을 합니다.  

 

 

# 사용할 기본 라이브러리 import
import datetime
import requests
import urllib
import json

# 구글 캘린더 API 서비스 객체 생성
from googleapiclient.discovery import build

service = build('calendar', 'v3', credentials=gcreds)

# 조회에 사용될 요청 변수 지정
calendar_id = 'primary'                   # 사용할 캘린더 ID
today = datetime.date.today().isoformat()
time_min = today + 'T00:00:00+09:00'      # 일정을 조회할 최소 날짜
time_max = today + 'T23:59:59+09:00'      # 일정을 조회할 최대 날짜
max_results = 1                           # 일정을 조회할 최대 개수
is_single_events = True                   # 반복 일정의 여부
orderby = 'startTime'                     # 일정 정렬

# 오늘 일정 가져오기
events_result = service.events().list(calendarId = calendar_id,
                                      timeMin = time_min,
                                      timeMax = time_max,
                                      maxResults = max_results,
                                      singleEvents = is_single_events,
                                      orderBy = orderby).execute()
events_result.get('items', [])
# events_result.get('items', [])
# [{'kind': 'calendar#event',
#   'etag': '"3161909706058000"',
#   'id': '71r7508o4t98naqhb41323lhpq',
#   'status': 'confirmed',
#   'htmlLink': 'https://www.google.com/calendar/event?eid=NzFyNzUwOG80dDk4bmFxaGI4123xdWxocHEgaWFtZXhhbXBsZTJAbQ',
#   'created': '2020-02-05T09:38:09.000Z',
#   'updated': '2020-02-06T02:07:33.029Z',
#   'summary': '[식사-국민대] 친구 만나기',
#   'description': '국민대에서 친구와 식사하기',
#   'location': '국민대학교, 대한민국 서울특별시 성북구 정릉동 정릉로 77',
#   'creator': {'email': 'example@gmail.com', 'self': True},
#   'organizer': {'email': 'example@gmail.com', 'self': True},
#   'start': {'dateTime': '2020-02-06T19:00:00+09:00'},
#   'end': {'dateTime': '2020-02-06T20:00:00+09:00'},
#   'iCalUID': '71r7508o4t98naqhb47rqulhpq@google.com',
#   'sequence': 1,
#   'reminders': {'useDefault': True}}]	

가져온 일정(events_result)에서 사용할 주요 정보는 다음과 같습니다.

- summary : 일정 제목

- description : 일정 상세 설명

- location : 일정 주소 (구글 주소)

- start : 일정 시작 날짜

- end : 일정 종료 날짜

- htmlLink : 해당 일정에 접속하는 링크

- reminders : 일정을 알려주는 설정 정보입니다. useDefault : True 인 것은 해당 캘린더 전체에 적용된 기본 알림으로 하겠다는 뜻입니다.

 

Step 3) 일정 데이터 정제하기

 

# 테스트를 위해 오늘 일정에서 한 개만 가져오겠습니다.
items = events_result.get('items')
item = items[0]

# 일정 제목
gsummary = item.get('summary')

# 일정 제목에서 [식사-국민대]에서 카테고리와 장소만 빼내옵니다.
gcategory, glocation = gsummary[gsummary.index('[')+1 : gsummary.index(']')].split('-')

# 해당 구글 캘린더 일정 연결 링크
gevent_url = item.get('htmlLink')

gaddress = item.get('location')
# 구글 주소를 정제합니다.
# ex) "국민대학교, 대한민국 서울특별시 성북구 정릉동 정릉로 77" => "대한민국 서울특별시 성북구 정릉동 정릉로 77"
if ',' in gaddress:
	gaddress = gaddress.split(',')[1].strip()


Step 4) 일정 위치 주변 맛집 검색

# '식사' 카테고리만 처리하기 원한다면 if문 안으로 코드를 넣으세요.
# if '식사' in category:

# 인코딩된 맛집 검색어
enc_location = urllib.parse.quote(glocation + "맛집")

# 검색에 사용될 정보
# sort : "comment"을 넣으면 블로그 리뷰순으로 정렬
# query : 검색어
# display : 검색 결과 출력 건수 지정 (기본값 : 1, 최대 : 5)
nparams = "sort=comment&query=" + enc_location + "&display=" + '5'

# 네이버 지역 검색 주소 및 검색할 정보
naver_local_url = "https://openapi.naver.com/v1/search/local.json?" + nparams

 

맛집 검색을 위해 필요한 정보들을 입력합니다.

 

# 네이버에 일정 장소 주변 맛집을 검색합니다.
res = requests.get(naver_local_url, headers=nheaders)

# 검색 결과로 받아온 10개의 결과
places = res.json().get('items')

 

places[:2]

# places[:2]
[{'title': '바람난오리궁뎅이',
  'link': '',
  'category': '한식>오리요리',
  'description': '',
  'telephone': '02-918-5999',
  'address': '서울특별시 성북구 정릉동 767-4',
  'roadAddress': '서울특별시 성북구 보국문로29길 15',
  'mapx': '312191',
  'mapy': '557556'},
 {'title': '기차순대국',
  'link': '',
  'category': '한식>순대,순댓국',
  'description': '',
  'telephone': '02-914-9316',
  'address': '서울특별시 성북구 정릉동 398-9',
  'roadAddress': '서울특별시 성북구 보국문로11길 18-6',
  'mapx': '312705',
  'mapy': '556768'}]


Step 5) '나에게 카카오톡' 보내기

Step 5-1) 리스트 템플릿 형식 맞추기

받아온 결과를 이제 카카오톡 리스트 템플릿의 형식에 맞춰줍니다.

# 카카오톡 리스트 템플릿은 최대 3개를 표현하기 때문에 3개만 가져오겠습니다.
contents = []
for place in places[:3]:
    ntitle = place.get('title')  # 장소 이름
    ncategory = place.get('category')  # 장소 카테고리
    ntelephone = place.get('telephone')  # 장소 전화번호
    nlocation = place.get('address')  # 장소 지번 주소

    # 각 장소를 클릭할 때 네이버 검색으로 연결해주기 위해 작성된 코드
    enc_location = urllib.parse.quote(nlocation + ' ' + ntitle)
    query = "query=" + enc_location

    # 장소 카테고리가 카페이면 카페 이미지
    # 이외에는 음식 이미지
    if '카페' in ncategory:
        image_url = "https://freesvg.org/img/pitr_Coffee_cup_icon.png"
    else:
        image_url = "https://freesvg.org/img/bentolunch.png?w=150&h=150&fit=fill"

    # 전화번호가 있다면 제목과 함께 넣어줍니다.
    if ntelephone:
        ntitle = ntitle + "\ntel) " + ntelephone

    # 카카오톡 리스트 템플릿 형식에 맞춰줍니다.
    content = {
        "title": "[" + ncategory + "] " + ntitle,
        "description": ' '.join(nlocation.split()[1:]),
        "image_url": image_url,
        "image_width": 50, "image_height": 50,
        "link": {
            "web_url": "https://search.naver.com/search.naver?" + query,
            "mobile_web_url": "https://search.naver.com/search.naver?" + query
        }
    }
    contents.append(content)
contents[0]

# contents[0]
# {'title': '[한식>오리요리] 바람난오리궁뎅이\ntel) 02-918-5999',
# 'description': '성북구 정릉동 767-4',
# 'image_url': 'https://freesvg.org/img/bentolunch.png?w=150&h=150&fit=fill',
# 'image_width': 50,
# 'image_height': 50,
# 'link': {'web_url': 'https://search.naver.com/search.naver?query=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C%20%EC%84%B1%EB%B6%81%EA%B5%AC%20%EC%A0%95%EB%A6%89%EB%8F%99%20767-4%20%EB%B0%94%EB%9E%8C%EB%82%9C%EC%98%A4%EB%A6%AC%EA%B6%81%EB%8E%85%EC%9D%B4',
# 'mobile_web_url': 'https://search.naver.com/search.naver?query=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C%20%EC%84%B1%EB%B6%81%EA%B5%AC%20%EC%A0%95%EB%A6%89%EB%8F%99%20767-4%20%EB%B0%94%EB%9E%8C%EB%82%9C%EC%98%A4%EB%A6%AC%EA%B6%81%EB%8E%85%EC%9D%B4'}
# }

카카오톡 리스트 템플릿

# 일정 주소 네이버 연결 링크
enc_gaddress = urllib.parse.quote(gaddress)
query = "query=" + enc_gaddress
gaddr_url = "https://search.naver.com/search.naver?" + query
# 카카오톡 리스트 템플릿을 작성해봅니다.
ktemplate = {
    "object_type" : "list",
    "header_title" : "'%s' - 맛집 추천" % gsummary,
    "header_link" : {
        "web_url": gevent_url,
        "mobile_web_url" : gevent_url
    },
    "contents" : contents,
    "buttons" : [
        {
            "title" : "일정 자세히 보기",
            "link" : {
                "web_url": gevent_url,
                "mobile_web_url" : gevent_url
            }
        },
        {
            "title" : "일정 장소 보기",
            "link": {
                "web_url": gaddr_url ,
                "mobile_web_url": gaddr_url
            }
        }
    ],
}

# json 형식의 string을 보내야하기 때문에 json.dumps로 dict => string(json)으로 바꿔줍니다.
kpayload = {
    "template_object" : json.dumps(ktemplate)
}

template에 사용된 데이터

- header_title : 리스트 템플릿의 제목 (gsummary : 구글 캘린더에서 가져온 일정 제목)

- header_link : 리스트 템플릿의 제목 링크 (gevent_url : 구글 캘린더에서 가져온 일정 제목)

- - web_url : pc버전 카카오톡에서 사용하는 웹 링크 URL.
- - mobile_web_url : 모바일 카카오톡에서 사용하는 웹 링크 URL.

- contents : 리스트 템플릿에 표출될 내용 하나하나. (위에서 만든 맛집 정보를 담은 리스트)
- buttons
- - link : 버튼을 누르면 연결되는 페이지. 도메인 부분은 개발자 사이트에 등록된 도메인과 일치해야 합니다. (gaddr_url : 네이버에서 검색된 장소 정보)

Step 5-2) 도메인 등록하기

위에서 버튼을 클릭하면 이동하는 링크의 도메인을 내 앱 페이지에 등록해주지 않으면, 보안의 이유로 해당 링크로 이동시켜주지 않습니다.

> 순서 : 카카오 개발자 사이트 > 내 애플리케이션 > 플랫폼 > 사이트 도메인 수정 버튼 클릭 > https://search.naver.comhttps://www.google.com 추가

 

Step 5-3) 카카오톡 보내기

위에 리스트 템플릿 형식에 맞춰진 데이터(kpayload)와 카카오톡 인증(kheaders) 그리고 템플릿 주소로 요청을 보내어 카카오톡을 보내면 결과를 확인할 수 있습니다!

# 기본 템플릿(피드, 리스트, 위치, 커머스, 텍스트) : https://kapi.kakao.com/v2/api/talk/memo/default/send
# 스크랩 템플릿 : https://kapi.kakao.com/v2/api/talk/memo/scrap/send
# 커스텀 템플릿 : https://kapi.kakao.com/v2/api/talk/memo/send
kakaotalk_template_url = "https://kapi.kakao.com/v2/api/talk/memo/default/send"

res = requests.post(kakaotalk_template_url, data=kpayload, headers=kheaders)

res.json()
# {'result_code': 0}

 


Step 6) 전체 코드

##################################################
# 1. 인증
##################################################

# 1-1. 구글 API 인증
from google_auth_oauthlib.flow import InstalledAppFlow

# 구글 OAuth 클라이언트 ID Json 파일
gcreds_filename = 'auth/gcredentials.json'

# 캘린더에서 사용할 권한
SCOPES = ['https://www.googleapis.com/auth/calendar']

# 새로운 창에서 로그인 하시면 인증 정보를 얻게 됩니다.
flow = InstalledAppFlow.from_client_secrets_file(gcreds_filename, SCOPES)
gcreds = flow.run_local_server(port=0)

# 1-2. 카카오톡 인증
# https://developers.kakao.com/docs/restapi/tool
# 해당 사이트에서 로그인 후 'Access token'을 얻어오세요
kcreds = {
    "access_token" : "<Access token>"
}
kheaders = {
    "Authorization": "Bearer " + kcreds.get('access_token')
}

# 1-3. 네이버 인증
# https://developers.naver.com/apps
# 해당 사이트에서 로그인 후 "Cliend ID"와 "Client Secret"을 얻어오세요
ncreds = {
    "client_id": "<Client ID>",
    "client_secret" : "<Client Secret>"
}
nheaders = {
    "X-Naver-Client-Id" : ncreds.get('client_id'),
    "X-Naver-Client-Secret" : ncreds.get('client_secret')
}

##################################################
# 2. 구글 캘린더 일정 가져오기
##################################################

# 기본 라이브러리 import
import datetime
import requests
import urllib
import json

# 구글 캘린더 API 서비스 객체 생성
from googleapiclient.discovery import build

service = build('calendar', 'v3', credentials=gcreds)

# 조회에 사용될 요청 변수 지정
calendar_id = 'primary'                   # 사용할 캘린더 ID
today = datetime.date.today().isoformat()
time_min = today + 'T00:00:00+09:00'      # 일정을 조회할 최소 날짜
time_max = today + 'T23:59:59+09:00'      # 일정을 조회할 최대 날짜
max_results = 1                           # 일정을 조회할 최대 개수
is_single_events = True                   # 반복 일정의 여부
orderby = 'startTime'                     # 일정 정렬

# 오늘 일정 가져오기
events_result = service.events().list(calendarId = calendar_id,
                                      timeMin = time_min,
                                      timeMax = time_max,
                                      maxResults = max_results,
                                      singleEvents = is_single_events,
                                      orderBy = orderby).execute()

events_result.get('items', [])

##################################################
# 3. 일정 데이터 정제하기
##################################################
# 테스트를 위해 오늘 일정에서 한 개만 가져오겠습니다.
items = events_result.get('items')
item = items[0]

# 일정 제목
gsummary = item.get('summary')

# 일정 제목에서 [식사-국민대]에서 카테고리와 장소만 빼내옵니다.
gcategory, glocation = gsummary[gsummary.index('[')+1 : gsummary.index(']')].split('-')

# 해당 구글 캘린더 일정 연결 링크
gevent_url = item.get('htmlLink')

gaddress = item.get('location')
# 구글 주소를 정제합니다.
# ex) "국민대학교, 대한민국 서울특별시 성북구 정릉동 정릉로 77" => "대한민국 서울특별시 성북구 정릉동 정릉로 77"
if ',' in gaddress:
    gaddress = gaddress.split(',')[1].strip()

##################################################
# 4. 일정 위치 주변 맛집 검색
##################################################

# '식사' 카테고리만 처리하기 원한다면 if문 안으로 코드를 넣으세요.
# if '식사' in category:

# 인코딩된 맛집 검색어
enc_location = urllib.parse.quote(glocation + "맛집")

# 검색에 사용될 정보
# sort : "comment"을 넣으면 블로그 리뷰순으로 정렬
# query : 검색어
# display : 검색 결과 출력 건수 지정 (기본값 : 1, 최대 : 5)
nparams = "sort=comment&query=" + enc_location + "&display=" + '5'

# 네이버 지역 검색 주소 및 검색할 정보
naver_local_url = "https://openapi.naver.com/v1/search/local.json?" + nparams

# 네이버에 일정 장소 주변 맛집을 검색합니다.
res = requests.get(naver_local_url, headers=nheaders)

# 검색 결과로 받아온 10개의 결과
places = res.json().get('items')

places[0]

##################################################
# 5. 맛집 정보 카카오톡으로 보내기
##################################################

# 5-1. 리스트 템플릿 형식 맞추기
# 카카오톡 리스트 템플릿은 최대 3개를 표현하기 때문에 3개만 가져오겠습니다.
contents = []
for place in places[:3]:
    ntitle = place.get('title')  # 장소 이름
    ncategory = place.get('category')  # 장소 카테고리
    ntelephone = place.get('telephone')  # 장소 전화번호
    nlocation = place.get('address')  # 장소 지번 주소

    # 각 장소를 클릭할 때 네이버 검색으로 연결해주기 위해 작성된 코드
    enc_location = urllib.parse.quote(nlocation + ' ' + ntitle)
    query = "query=" + enc_location

    # 장소 카테고리가 카페이면 카페 이미지
    # 이외에는 음식 이미지
    if '카페' in ncategory:
        image_url = "https://freesvg.org/img/pitr_Coffee_cup_icon.png"
    else:
        image_url = "https://freesvg.org/img/bentolunch.png?w=150&h=150&fit=fill"

    # 전화번호가 있다면 제목과 함께 넣어줍니다.
    if ntelephone:
        ntitle = ntitle + "\ntel) " + ntelephone

    # 카카오톡 리스트 템플릿 형식에 맞춰줍니다.
    content = {
        "title": "[" + ncategory + "] " + ntitle,
        "description": ' '.join(nlocation.split()[1:]),
        "image_url": image_url,
        "image_width": 50, "image_height": 50,
        "link": {
            "web_url": "https://search.naver.com/search.naver?" + query,
            "mobile_web_url": "https://search.naver.com/search.naver?" + query
        }
    }
    contents.append(content)

# 일정 주소 네이버 연결 링크
enc_gaddress = urllib.parse.quote(gaddress)
query = "query=" + enc_gaddress
gaddr_url = "https://search.naver.com/search.naver?" + query

gaddr_url

# 카카오톡 리스트 템플릿을 작성해봅니다.
ktemplate = {
    "object_type" : "list",
    "header_title" : "'%s' - 맛집 추천" % gsummary,
    "header_link" : {
        "web_url": gevent_url,
        "mobile_web_url" : gevent_url
    },
    "contents" : contents,
    "buttons" : [
        {
            "title" : "일정 자세히 보기",
            "link" : {
                "web_url": gevent_url,
                "mobile_web_url" : gevent_url
            }
        },
        {
            "title" : "일정 장소 보기",
            "link": {
                "web_url": gaddr_url ,
                "mobile_web_url": gaddr_url
            }
        }
    ],
}

# json 형식의 string을 보내야하기 때문에 json.dumps로 dict => string(json)으로 바꿔줍니다.
kpayload = {
    "template_object" : json.dumps(ktemplate)
}

kpayload

# 5-2. 카카오톡 보내기

# 기본 템플릿(피드, 리스트, 위치, 커머스, 텍스트) : https://kapi.kakao.com/v2/api/talk/memo/default/send
# 스크랩 템플릿 : https://kapi.kakao.com/v2/api/talk/memo/scrap/send
# 커스텀 템플릿 : https://kapi.kakao.com/v2/api/talk/memo/send
kakaotalk_template_url = "https://kapi.kakao.com/v2/api/talk/memo/default/send"

res = requests.post(kakaotalk_template_url, data=kpayload, headers=kheaders)

res.json()
# {'result_code': 0}

 

<< 참고 자료 >>

 

<< Trouble Shooting >>

# python.exe exception 발생시

(만약 아나콘다를 사용하고 있다면)

더보기

$ conda update --all

$ conda update python

$ conda update anaconda

 

# 아래 함수 수행시 에러 발생시

events_result = service.events().list(calendarId = calendar_id,
                                      timeMin = time_min,
                                      timeMax = time_max,
                                      maxResults = max_results,
                                      singleEvents = is_single_events,
                                      orderBy = orderby).execute()

 

에러에 주어진 링크를 클릭하여, api 활성화를 시켜준다.


저작물의 저작권은 작성자에게 있습니다.

공유는 자유롭게 하시되 댓글 남겨주세요~

상업적 용도로는 무단 사용을 금지합니다.

끝까지 읽어주셔서 감사합니다^^

반응형
Comments