IT/Python

[Python] "Python API tutorial - An Introduction to using APIs" - 파이썬 API 튜토리얼 (번역)

wookiist 2018. 1. 10. 11:47
728x90

Python API tutorial - An Introduction to using APIs

API라고 불리는 Application Program Interfaces는 원격 웹 사이트에서 데이터를 검색해오는데 흔히 사용된다. Reddit이나 Twitter, Facebook은 모두 그들의 API를 활용해 특정 데이터를 제공한다. API를 사용하기 위해서는 원격 웹 서버에 요청하고, 필요한 데이터를 검색해야 한다.

 

하지만 왜 쉽게 다운로드할 수 있는 static dataset 대신에 API를 이용할까?

APIs는 다음과 같은 케이스에 유용하다.

 

  • 데이터가 빠르게 변화하는 경우이다. 주식 가격 데이터를 예로 들 수 있다. 생각해보면, 그 데이터 셋을 매분마다 새로 생성하고 새로 다운로드 받는다는 것은 상식적이지 못하다. 이러한 방식은 많은 대역폭을 사용하며, 또한 꽤나 느려질 것이다.
  • 꽤나 큰 데이터 셋에서 자그마한 일부분만 가져오고 싶은 경우이다. Reddit의 댓글을 예로 들 수 있다. 만약 Reddit에서 본인이 작성한 댓글만 당겨오고(pull)싶을 때는 어떻게 해야할까? 이를 위해서 Reddit 데이터베이스 전체를 다운로드하는 것 또한 본인의 댓글만 필터링하는 것보다 비상식적이다.
  • 반복적인 계산 작업이 포함되어 있는 경우이다. Spotify에는 음악의 장르를 알려주는 API가 있다. 본인이 이론적으로 본인만의 구분자(classifier)를 생성하고, 이를 이용해서 음악을 분류할 수도 있지만, 그것은 결코 Spotify가 가진 데이터보다 많을 수 없다는 문제가 있다.

위와 같은 경우, API는 올바른 선택이다. 이 블로그 포스트에서 우리는, ISS(국제 우주 정거장)에 관한 데이터를 가져오는 간단한 API를 질의(querying)할 것이다. 모든 것을 우리 스스로 계산하는 것보다, API를 사용하는 것은 우리의 시간과 노력을 줄여줄 것이다.

API Requests

API는 웹 서버에서 호스팅된다. www.google.com을 웹 브라우저의 주소창에 입력하면, 컴퓨터는 실제로 www.google.com의 서버에게 웹 브라우저에 띄울 웹 페이지를 요청한다.

웹 브라우저에서 웹 페이지를 요청하는 대신에 API는 프로그램에서 데이터를 요청한다는 것을 제외하면 웹 브라우저와 동일한 방식으로 작동한다. 이 데이터는 보통 JSON format으로 리턴된다. (더 많은 정보를 얻고 싶다면 working with JSON data를 참조하라.)

데이터를 얻어오기 위해, 웹 서버에게 request한다. 웹 서버는 데이터로 응답한다. Python에서 우리는 requests library를 사용할 것이다. 이 Python API tutorial에서 우리는 Python 3.4를 이용할 것이다.

Type of requests

requests의 종류는 다양하다. 가장 널리 사용되는 것은 데이터를 가져오는데 사용되는 GET request이다.

OpenNotify에서 정보를 가져오기 위해 간단한 GET request를 이용할 수 있다.

OpenNotify는 많은 API endpoints를 가진다. endpoint는 API로부터 각기 다른 데이터를 가져오기 위해 사용되는 server route이다. 예를 들어, Reddit API의 /comments endpoint는 댓글에 관한 정보를 가져올 것이다. 반면에 /users endpoint는 사용자에 관한 정보를 가져올 것이다. 이에 접근하기 위해, API의 base url 에 해당 endpoint를 추가해야 한다.

OpenNotify에서 우리가 가장 처음으로 보게 될 endpoint는 iss-now.json endpoint다. 이 endpoint는 ISS의 현재 위도와 경도를 가져온다. 이 데이터를 가저오는 것은 데이터 셋에 적합하지 않다. 이 데이터가 서버에서 이루어지는 계산을 통해 빠르게 변하기 때문이다.

여기를 통해 OpenNotify의 모든 endpoints를 볼 수 있다.

OpenNotify API를 위한 base urlhttp://api.open-notify.org이고, 이를 우리가 가진 endpoints 중 가장 첫번째 줄에 추가할 것이다.

# Make a get request to get the latest position of the international space station from the opennotify api.
response = requests.get("http://api.open-notify.org/iss-now.json")

# Print the status code of the response.
print(response.status_code)
200

Status codes

방금 요청에 대한 status code200이다. 웹 서버에 대한 모든 요청에는 Status codes가 반환된다. Status codes는 해당 요청에 대해 발생한 정보를 나타낸다. 다음은 GET requests와 연관이 있는 몇몇 코드다.

 

  • 200 -- 모든 것이 정상적이며, 결과값이 존재한다면 그 결과값이 정상적으로 반환되었다.
  • 301 -- 서버가 다른 endpoint로 리디렉션한다. 어떤 회사가 domain names를 변경하거나 endpoint 이름을 바꾼 경우 발생할 수 있다.
  • 401 -- 서버가 당신이 인증되지 않은 사용자라 판단한 경우이다. 당신이 API에 접근하기 위한 올바른 자격(credentials)을 전송하지 않은 경우 발생한다. (인증에 관한 내용은 추후 포스트에서 다룬다.)
  • 400 -- 서버가 당신이 bad request를 요청하였다 판단한 경우이다. 이는 무엇보다도 올바르지 않은 데이터를 전송한 경우 발생한다.
  • 403 -- 접근하려고 한 resource가 금지된 경우이다. 즉, 당신이 그것을 볼 권한이 없다.
  • 404 -- 접근하려고 한 resource가 해당 서버에 존재하지 않는다.

이제 API 문서에 존재하지 않는 http://api.open-notify.org/iss-pass endpoint로 GET request를 보내보자.

response = requests.get("http://api.open-notify.org/iss-pass")
print(response.status_code)
404

Hitting the right endpoints

iss-pass는 올바르지 않은 endpoint였기에, 우리는 404 status code를 응답받았다. .json을 끝에 붙여야하는 것을 잊은 것이다.

이제 http://api.open-notify.org/iss-pass.json로 GET request를 보내보자.

response = requests.get("http://api.open-notify.org/iss-pass.json")
print(response.status_code)
400

Query parameters

마지막 예제에서, bad request를 의미하는 400 status code를 받았다. OpenNotify API를 살펴보면, ISS Pass endpoint는 두 개의 parameters 를 필요로 한다.

ISS Pass endpoint는 지구 상에 주어진 위치에 ISS가 지나가는 시간을 반환한다. 이것을 계산하기 위해, API에 위치 정보에 대한 좌표 값을 주어야 한다. 이는 위도와 경도, 두 parameter를 입력함으로써 계산할 수 있다.

우리의 request에 params 키워드 인자 (keyword argument)를 추가함으로써 수행할 수 있다. 이 경우, 추가적인 두 매개변수는 다음과 같다.

 

  • lat -- 우리가 원하는 위치의 위도
  • lon -- 우리가 원하는 위치의 경도

이 두 파라미터를 dictionary로 만들고 requests.get 함수에 넣어 보낼 수 있다.

위와 같은 작업을 url에 query parameters를 추가하는 것으로도 가능하다. http://api.open-notify.org/iss-pass.json?lat=40.71&lon=-74.

매개변수들을 dictionary 형태로 만드는 것은 언제나 바람직한데, 이런 작업이 없는 경우 requests가 query parameter를 적절한 형식으로 포맷팅하는 작업이 추가적으로 이루어질 수 있기 때문이다.

이제 New York City의 좌표를 이용해 어떤 응답을 얻었는지 확인해보자.

# Set up the parameters we want to pass to the API.
# This is the latitude and longitude of New York City.
parameters = {"lat": 40.71, "lon": -74}

# Make a get request with the parameters.
response = requests.get("http://api.open-notify.org/iss-pass.json", params=parameters)

# Print the content of the response (the data the server returned)
print(response.content)

# This gets the same data as the command above
response = requests.get("http://api.open-notify.org/iss-pass.json?lat=40.71&lon=-74")
print(response.content)

b'{\n  "message": "success", \n  "request": {\n    "altitude": 100, \n    "datetime": 1515547452, \n    "latitude": 40.71, \n    "longitude": -74.0, \n    "passes": 5\n  }, \n  "response": [\n    {\n      "duration": 603, \n      "risetime": 1515561224\n    }, \n    {\n      "duration": 628, \n      "risetime": 1515566993\n    }, \n    {\n      "duration": 558, \n      "risetime": 1515572858\n    }, \n    {\n      "duration": 562, \n      "risetime": 1515578708\n    }, \n    {\n      "duration": 633, \n      "risetime": 1515584500\n    }\n  ]\n}\n'

b'{\n  "message": "success", \n  "request": {\n    "altitude": 100, \n    "datetime": 1515547452, \n    "latitude": 40.71, \n    "longitude": -74.0, \n    "passes": 5\n  }, \n  "response": [\n    {\n      "duration": 603, \n      "risetime": 1515561224\n    }, \n    {\n      "duration": 628, \n      "risetime": 1515566993\n    }, \n    {\n      "duration": 558, \n      "risetime": 1515572858\n    }, \n    {\n      "duration": 562, \n      "risetime": 1515578708\n    }, \n    {\n      "duration": 633, \n      "risetime": 1515584500\n    }\n  ]\n}\n'

Working with JSON data

이전에 우리가 받은 응답 메시지의 내용은 string 형태였음을 알 수 있다.(비록, bytes 객체처럼 보여지기는 했으나, 우리는 이것을 response.content.decode("utf-8")을 이용하여 쉽게 변환할 수 있다.)

String은 우리가 API에 정보를 요청하고 가져오는데 사용하는 방법이지만, string 안에서 우리가 원하는 정보를 뽑아내는 것은 어렵다. Python으로 작업하는 경우, 우리가 응답받은 string을 어떻게 해독(decode)할지 알 수 있을까? 어떻게 우리는 ISS의 altitude 값을 string 응답에서 추론할 수 있을까?

운 좋게도, JavaScript Object Notation (JSON)이라는 포맷이 있다. JSON은 list나 dictionary와 같은 데이터 구조를, 컴퓨터가 쉽게 읽을 수 있는 string 포맷으로 인코딩해주는 방법이다. JSON은 API로 주고 받는 기본 포맷이며, 대부분의 API 서버는 JSON의 형식으로 응답을 보낸다.

Python은 json 패키지를 통해 훌륭한 JSON 지원 기능을 제공한다. json 패키지는 기본 라이브러리에 속해있으므로, 이를 사용하기 위해 별도의 설치가 필요하지 않다. listsdictionaries 를 JSON으로 변환할 수도 있고, strings를 listsdictionaries 의 형태로 변환할 수도 있다. ISS Pass data의 경우, JSON 형식 내에 dictionary가 string으로 인코딩되어 들어간다.

json 라이브러리는 다음의 두 메인 메서드를 가지고 있다.

 

  • dumps -- Python 객체를 가지고 와서, string으로 변환한다.
  • loads -- JSON string을 가지고 와서, Python 객체로 변환한다.
# Make a list of fast food chains.
best_food_chains = ["Taco Bell", "Shake Shack", "Chipotle"]

# This is a list.
print(type(best_food_chains))

# Import the json library
import json

# Use json.dumps to convert best_food_chains to a string.
best_food_chains_string = json.dumps(best_food_chains)

# We've successfully converted our list to a string.
print(type(best_food_chains_string))

# Convert best_food_chains_string back into a list
print(type(json.loads(best_food_chains_string)))

# Make a dictionary
fast_food_franchise = {
    "Subway": 24722,
    "McDonalds": 14098,
    "Starbucks": 10821,
    "Pizza Hut": 7600
}

# We can also dump a dictionary to a string and load it.
fast_food_franchise_string = json.dumps(fast_food_franchise)
print(type(fast_food_franchise_string))
<class 'list'>

<class 'str'>

<class 'list'>

<class 'str'>

Getting JSON from an API request

.json() 메서드를 이용하면 응답 메시지로 python 객체를 받을 수 있다.

# Make the same request we did earlier, but with the coordinates of San Francisco instead.
parameters = {"lat": 37.78, "lon": -122.41}
response = requests.get("http://api.open-notify.org/iss-pass.json", params=parameters)

# Get the response data as a python object.  Verify that it's a dictionary.
data = response.json()
print(type(data))
print(data)
<class 'dict'>

{'response': [{'risetime': 1441456672, 'duration': 369}, {'risetime': 1441462284, 'duration': 626}, {'risetime': 1441468104, 'duration': 581}, {'risetime': 1441474000, 'duration': 482}, {'risetime': 1441479853, 'duration': 509}], 'message': 'success', 'request': {'latitude': 37.78, 'passes': 5, 'longitude': -122.41, 'altitude': 100, 'datetime': 1441417753}}

Content type

서버는 응답 메시지를 생성할 때, 단지 데이터와 status code만을 보내지는 않는다. 서버는 해당 데이터가 어떻게 생성되었고, 어떻게 해독(decode) 할 수 있는지에 대한 정보를 담은 metadata 또한 전송한다. 이 metadata는 response header 에 있다. Python에서 response object의 headers property를 이용해서 접근할 수 있다.

헤더는 dictionary로 보일 것이다. header 안에는, content-type이라는 정말 중요한 키가 있다. 이 키는 response의 형식을 말해주고, 어떻게 해독하는지 설명한다. OpenNotify API의 경우, 해당 형식은 JSON이며, 이를 통해, 우리가 어떻게 json 패키지를 통해 decode할 수 있었는지를 알 수 있다.

# Headers is a dictionary
print(response.headers)

# Get the content-type from the dictionary.
print(response.headers["content-type"])

{'Server': 'nginx/1.10.3', 'Date': 'Wed, 10 Jan 2018 02:01:20 GMT', 'Content-Type': 'application/json', 'Content-Length': '519', 'Connection': 'keep-alive', 'Via': '1.1 vegur'}

application/json

Finding the number of people in space

OpenNotify는 astros.json이라는 API endpoint를 더 가지고 있다. 이 API endpoint는 현재 우주에 몇 명의 사람이 존재하는 지 알려준다. 이 response에 대한 형식은 여기에서 찾을 수 있다.

# Get the response from the API endpoint.
response = requests.get("http://api.open-notify.org/astros.json")
data = response.json()

# 9 people are currently in space.
print(data["number"])
print(data)

6

{'number': 6, 'people': [{'craft': 'ISS', 'name': 'Alexander Misurkin'}, {'craft': 'ISS', 'name': 'Mark Vande Hei'}, {'craft': 'ISS', 'name': 'Joe Acaba'}, {'craft': 'ISS', 'name': 'Anton Shkaplerov'}, {'craft': 'ISS', 'name': 'Scott Tingle'}, {'craft': 'ISS', 'name': 'Norishige Kanai'}], 'message': 'success'}

Python API tutorial: Next steps

이제 여러분은 Python API tutorial을 마쳤고, 이제 간단한 API에 접근하거나 get requests를 만들 수 있을 것이다. requests에는 몇가지 다른 타입들이 더 존재하는데, 이는 APIs and scraping course을 통해 더 배울 수 있다.

다른 조언으로는 requests documentation을 읽고 Reddit API로 작업해보는 것이다. 사실 Reddit API를 Python에서 더 쉽게 다룰 수 있도록 해주는 PRAW라는 패키지가 존재하지만, 일단은 requests를 이용하여 배운 것들이 잘 작동하는 지 점검하는 것을 추천한다.


[1] 원문

728x90
반응형
1 2 3 4 5