IT/언어

[python] Requests를 이용해 Web API 호출하기(데이터 획득, 조작)

개발자 두더지 2022. 5. 28. 00:05
728x90

일본의 한 포스트를 번역한 글입니다. 오역 및 직역, 의역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 이번 포스팅에서는 Python의 HTTP 라이브러리인 Requests를 사용하여 REST 스타일의 Web API를 호출해 데이터를 얻어내거나 조작하는 방법에 대해서 설명하고자 한다. Qiita API (일본의 기술 포스팅 사이트)를 예로 HTTP 메소드를 이용해 Web API를 조작해 볼 것이다. 

 여기서 설명할 내용은 다음과 같다.

  • HTTP 메소드
  • Qiita API의 액세스 토큰을 취득
  • POST(포스트 작성) : requests.post()
  • GET(포스트 획득) : requests.get()
  • PATCH(포스트 갱신) : requests.path()
  • DELETE(포스트 삭제) : requests.delete()
  • PUT(태그 팔로우) : requests.put()

 

 

HTTP 메소드


 목적에 따른 HTTP 메소드를 이용해 Web API를 호출한다. Web API를 조작하기 위해 주로 사용되는 HTTP 메소드는 아래와 같다.

  • POST : 리소스의 작성
  • GET : 리소스의 획득
  • PUT : 리소스의 갱신 (모두 바꾼다)
  • PATCH : 리소스의 갱신 (일부를 바꾼다)
  • DELETE : 리소스의 삭제

 뒤에서 설명하겠지만, Requests에서는 HTTP메소드의 이름이 그대로 메소드 이름이 된다.

 

 

Qiita API의 액세스 토큰 획득


 사이트에 공개되어 있는 정보를 획들할 GET 메소드에는 액세스 토큰이 필요하진 않지만, 포스트를 작성하거나 삭제하거나 할 때에는 액세스 토큰이 필요하다.

 로그인한 상태에서 계정의 어플리케이션에서 개인용 액세스 토큰의 "새로운 토큰을 발행하기" 를 클릭한 뒤에, 스코프의 "read_qiita"와 "write_qiita"를 체크한 뒤 발행한다. 액세스 토큰은 한 번만 표시되므로 복사/붙여넣기 해두길 바란다. 잊어버린 다면 삭제한 뒤 다시 재발행받으면 된다.

 여기서는 아래와 같이 텍스트 파일에 기입한 액세스 토큰을 읽어들이고 있지만, headers로 사용하는 {'Authorization': 'Bearer <access_token>'} 이라는 사전형을 만든다면 어떠한 방법을 사용해도 OK이다.

import requests
import pprint

with open('data/temp/qiita_access_token.txt') as f:
    qiita_access_token = f.read().strip()

headers = {'Authorization': 'Bearer {}'.format(qiita_access_token)}

 또한, pprint는 보기 쉽게 출력해주기 때문에 import했다.

 

 

POST(포스트 작성) : requests.post()


 POST 메소드로 포스트를 신규작성한다. requests.post()의 인수 json에 작성한 포스트의 내용을 사전형으로 지정한다. 설정 항목은 API에 따라 다르므로 그 API의 공식 문서를 참고하길 바란다. qiita의 api의 경우 tags에 무조건 하나 이상을 작성해야하므로 주의하자.

url_items = 'https://qiita.com/api/v2/items'

item_data = {
    'title': '테스트 포스트',
    'body': '테스트',
    'private': True,
    'tags': [{'name': 'test'}],
    'coediting': False,
    'gist': False,
    'tweet': False
}

r_post = requests.post(url_items, headers=headers, json=item_data)

 정상적으로 포스트가 작성된 경우 상태 코드로 201이 반환된다. 그리고 작성된 포스트의 정보가 JSON형태로 돌아온다.

print(r_post.status_code)
# 201

pprint.pprint(r_post.json())
# {'body': '테스트\n',
#  'coediting': False,
#  'comments_count': 0,
#  'created_at': '2018-07-12T22:05:19+09:00',
#  'group': None,
#  'id': '93ead2568150009de5f1',
#  'likes_count': 0,
#  'page_views_count': None,
#  'private': True,
#  'reactions_count': 0,
#  'rendered_body': '<p>테스트</p>\n',
#  'tags': [{'name': 'test', 'versions': []}],
#  'title': '테스트 포스트',
#  'updated_at': '2018-07-12T22:05:19+09:00',
#  'url': 'https://qiita.com/nkmk/private/93ead2568150009de5f1',
#  'user': {'description': '',
#           'facebook_id': '',
#           'followees_count': 0,
#           'followers_count': 0,
#           'github_login_name': None,
#           'id': 'nkmk',
#           'items_count': 0,
#           'linkedin_id': '',
#           'location': '',
#           'name': '',
#           'organization': '',
#           'permanent_id': 53096,
#           'profile_image_url': 'https://qiita-image-store.s3.amazonaws.com/0/53096/profile-images/1473692950',
#           'twitter_screen_name': None,
#           'website_url': ''}}

 예에서는 private를 True로 하였으므로 작성한 포스트는 마이 페이지에서만 확인할 수 있다. post()의 인수 json는 Requests의 버전 2.4.2에서 부터 추가됐다. json으로 지정하는 것으로 사전형이 자동적으로 문자열로 변환되어, 리퀘스트 헤더의 Content-Type도 application/json으로 변환된다.

 인수 data를 사용하는 경우는 표준 라이브러리의 json 모듈을 import한 상태에서 json.dumps(item_data)로 사전형을 문자열로 변환하도록 지정한다(data=json.dumps(item_data)). 이 경우, 리퀘스트 헤더 headers의 Content-Type를 application/json으로 지정해 둘 필요가 있다.

 

 

GET (포스트 획득) : requests.get()


 POST의 Response로 작성한 포스트의 ID를 획득하고, GET 메소드로 해당 포스트의 정보를 획득한다. POST로 작성한 포스트의 내용을 얻을 수 있다.

item_id = r_post.json()['id']
print(item_id)
# 93ead2568150009de5f1

r_get = requests.get(url_items + '/' + item_id, headers=headers)

print(r_get.status_code)
# 200

pprint.pprint(r_get.json())
# {'body': '테스트\n',
#  'coediting': False,
#  'comments_count': 0,
#  'created_at': '2018-07-12T22:05:19+09:00',
#  'group': None,
#  'id': '93ead2568150009de5f1',
#  'likes_count': 0,
#  'page_views_count': 0,
#  'private': True,
#  'reactions_count': 0,
#  'rendered_body': '<p>테스트</p>\n',
#  'tags': [{'name': 'test', 'versions': []}],
#  'title': '테스트 포스트',
#  'updated_at': '2018-07-12T22:05:19+09:00',
#  'url': 'https://qiita.com/nkmk/private/93ead2568150009de5f1',
#  'user': {'description': '',
#           'facebook_id': '',
#           'followees_count': 0,
#           'followers_count': 0,
#           'github_login_name': None,
#           'id': 'nkmk',
#           'items_count': 0,
#           'linkedin_id': '',
#           'location': '',
#           'name': '',
#           'organization': '',
#           'permanent_id': 53096,
#           'profile_image_url': 'https://qiita-image-store.s3.amazonaws.com/0/53096/profile-images/1473692950',
#           'twitter_screen_name': None,
#           'website_url': ''}}

 얻어낸 json 데이터를 파일로 저장하고 싶은 경우는 표준 라이브러리의 json 모듈의 json.dump()를 사용한다.

 

 

PATCH (포스트 갱신) : requests.patch()


 PATCH 메소드로 포스트의 내용을 갱신할 수 있다. POST 메소드를 사용해 포스트 내용의 일부 변경하여 인수 json으로 지정하는 방법이다.

item_data_updated = item_data.copy()
item_data_updated['title'] = '테스트 포스트 갱신'
print(item_data_updated)
# {'title': '테스트 포스트 갱신', 'body': 'テスト', 'private': True, 'tags': [{'name': 'test'}], 'coediting': False, 'gist': False, 'tweet': False}

r_patch = requests.patch(url_items + '/' + item_id, headers=headers, json=item_data_updated)

print(r_patch.status_code)
# 200

 동일한 ID로 포스트의 내용(여기선 title)을 갱신하고 있다.

pprint.pprint(r_patch.json())
# {'body': '테스트\n',
#  'coediting': False,
#  'comments_count': 0,
#  'created_at': '2018-07-12T22:05:19+09:00',
#  'group': None,
#  'id': '93ead2568150009de5f1',
#  'likes_count': 0,
#  'page_views_count': None,
#  'private': True,
#  'reactions_count': 0,
#  'rendered_body': '<p>테스트</p>\n',
#  'tags': [{'name': 'test', 'versions': []}],
#  'title': '테스트 포스트 갱신',
#  'updated_at': '2018-07-12T22:05:19+09:00',
#  'url': 'https://qiita.com/nkmk/private/93ead2568150009de5f1',
#  'user': {'description': '',
#           'facebook_id': '',
#           'followees_count': 0,
#           'followers_count': 0,
#           'github_login_name': None,
#           'id': 'nkmk',
#           'items_count': 0,
#           'linkedin_id': '',
#           'location': '',
#           'name': '',
#           'organization': '',
#           'permanent_id': 53096,
#           'profile_image_url': 'https://qiita-image-store.s3.amazonaws.com/0/53096/profile-images/1473692950',
#           'twitter_screen_name': None,
#           'website_url': ''}}

 

 

DELETE(포스트 삭제) : requests.delete()


 DELETE 메소드로 포스트를 삭제해보자. 정상 삭제되면 상태코드 204가 반환된다.

r_delete = requests.delete(url_items + '/' + item_id, headers=headers)

print(r_delete.status_code)
# 204

 

 

PUT (태그 팔로우) : requests.put()


 PUT 메소드를 이용하여 특정 태그를 팔로우해보자. 문자열 메소드 format()을 사용해 api의 문서에 설명한대로 URL에 태그 ID를 입력한다.

 정상적으로 처리됐을 경우 상태 코드 204가 반환된다.

url_tag = 'https://qiita.com/api/v2/tags/{}/following'

tag = 'python'

r_put = requests.put(url_tag.format(tag), headers=headers)

print(r_put.status_code)
# 204

 팔로우를 취소하고 싶은 경우 DELETE 메소드를 사용하면 된다.

r_delete_tag = requests.delete(url_tag.format(tag), headers=headers)

print(r_delete_tag.status_code)
# 204

참고자료

https://note.nkmk.me/python-requests-web-api/

728x90