파이썬 기초 강좌: 13. 웹서비스 사용하기 예제와 실습 (Python Basics – Using Web Services Examples and Exercises)

파이썬 기초 강좌 – 웹 서비스의 기본 개념과 XML 및 JSON 포맷을 사용하여 데이터를 교환하는 방법을 자세히 설명합니다. 실습 예제를 통해 XML과 JSON 데이터를 파싱하는 방법을 배우고, Google Geocoding API를 활용한 실제 응용 프로그램을 구현하는 방법을 안내합니다. 연습 문제를 통해 추가 학습과 실전 경험을 제공합니다. 웹 서비스와 API 보안에 대한 이해를 높이고, 실습을 통해 기술을 향상시킬 수 있습니다.

파이썬 기초 강좌 - 웹서비스 사용하기 예제와 실습

1. eXtensible Markup Language (XML)

1.1 XML이란?

XML은 데이터 교환을 위해 널리 사용되는 형식입니다. HTML과 비슷해 보이지만 더 구조화된 형태를 가지고 있습니다. 아래는 XML 문서의 예시입니다.

<person>
  <name>Chuck</name>
  <phone type="intl">
    +1 734 303 4456
  </phone>
  <email hide="yes" />
</person>

각각의 태그 쌍(예: <person>...</person>)은 동일한 이름의 요소를 나타내며, 요소는 텍스트, 속성 및 다른 중첩된 요소를 포함할 수 있습니다.

1.2 실습 예제: XML 파싱

다음 예제는 XML을 파싱하고 데이터를 추출하는 간단한 애플리케이션입니다.

# xml1.py
import xml.etree.ElementTree as ET

data = '''
<person>
  <name>Chuck</name>
  <phone type="intl">
    +1 734 303 4456
  </phone>
  <email hide="yes" />
</person>'''

tree = ET.fromstring(data)
print('Name:', tree.find('name').text)
print('Attr:', tree.find('email').get('hide'))

위 코드에서 fromstring 메소드는 XML 문자열을 파싱하여 요소 트리로 변환합니다. 이는 XML 문자열을 트리 구조로 변환하여 각 요소를 탐색하고 데이터에 접근할 수 있게 합니다.

  • ET.fromstring(data): 주어진 XML 문자열을 트리 구조로 변환합니다.
  • tree.find('name').text: 트리에서 <name> 태그를 찾아 해당 요소의 텍스트 값을 반환합니다.
  • tree.find('email').get('hide'): 트리에서 <email> 태그를 찾아 해당 요소의 hide 속성 값을 반환합니다.

1.3 XML 노드 반복 처리

다음 예제는 여러 노드를 반복 처리하는 방법을 보여줍니다.

# xml2.py
import xml.etree.ElementTree as ET

input = '''
<stuff>
  <users>
    <user x="2">
      <id>001</id>
      <name>Chuck</name>
    </user>
    <user x="7">
      <id>009</id>
      <name>Brent</name>
    </user>
  </users>
</stuff>'''

stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
for item in lst:
    print('Name', item.find('name').text)
    print('Id', item.find('id').text)
    print('Attribute', item.get('x'))
  • ET.fromstring(input): XML 문자열을 트리 구조로 변환합니다.
  • stuff.findall('users/user'): 트리에서 모든 <user> 요소를 찾아 리스트로 반환합니다.
  • for item in lst: 리스트 내 각 <user> 요소를 순회합니다.
  • item.find('name').text: 각 <user> 요소 내에서 <name> 태그의 텍스트 값을 출력합니다.
  • item.find('id').text: 각 <user> 요소 내에서 <id> 태그의 텍스트 값을 출력합니다.
  • item.get('x'): 각 <user> 요소의 x 속성 값을 출력합니다.

이 예제는 XML 문서 내 여러 노드를 반복하여 처리하는 방법을 보여줍니다. 각 사용자 노드의 이름, ID 및 속성을 출력합니다.

2. JavaScript Object Notation (JSON)

2.1 JSON이란?

JSON은 JavaScript 객체와 배열 형식을 기반으로 한 데이터 교환 형식입니다. XML보다 더 간결하며, 대부분의 프로그래밍 언어에서 자연스럽게 사용됩니다. 아래는 XML과 대응되는 JSON의 예시입니다.

{
  "name": "Chuck",
  "phone": {
    "type": "intl",
    "number": "+1 734 303 4456"
  },
  "email": {
    "hide": "yes"
  }
}

2.2 실습 예제: JSON 파싱

다음 예제는 JSON 데이터를 파싱하고 데이터를 추출하는 간단한 애플리케이션입니다.

# json1.py
import json

data = '''
[
  { "id": "001", "x": "2", "name": "Chuck" },
  { "id": "009", "x": "7", "name": "Brent" }
]'''

info = json.loads(data)
print('User count:', len(info))
for item in info:
    print('Name', item['name'])
    print('Id', item['id'])
    print('Attribute', item['x'])
  • json.loads(data): JSON 문자열을 파이썬 리스트로 변환합니다.
  • for item in info: 리스트 내 각 딕셔너리를 순회합니다.
  • item['name']: 각 딕셔너리에서 name 키의 값을 출력합니다.
  • item['id']: 각 딕셔너리에서 id 키의 값을 출력합니다.
  • item['x']: 각 딕셔너리에서 x 키의 값을 출력합니다.

이 예제는 JSON 문서 내 여러 항목을 반복하여 처리하는 방법을 보여줍니다. 각 사용자의 이름, ID 및 속성을 출력합니다.

3. Application Program Interfaces (APIs) and Service-Oriented Architecture (SOA)

3.1 데이터 교환의 시작: HTTP와 XML/JSON

우리는 이제 Hypertext Transport Protocol(HTTP)을 사용하여 애플리케이션 간에 데이터를 교환할 수 있습니다. 복잡한 데이터를 표현하기 위해 eXtensible Markup Language(XML) 또는 JavaScript Object Notation(JSON)을 사용할 수 있습니다. 이 기술들을 활용하여 애플리케이션 간의 계약을 정의하고 문서화하는 것이 다음 단계입니다. 이러한 애플리케이션 간의 계약을 일반적으로 API(Application Program Interfaces)라고 합니다.

3.2 API란 무엇인가?

API를 사용하면 하나의 프로그램이 다른 애플리케이션에서 사용할 수 있는 일련의 서비스를 제공하고, 해당 서비스에 접근하기 위한 규칙(API 규칙)을 공개합니다. 예를 들어, 한 프로그램이 데이터베이스에 저장된 정보를 제공하는 서비스를 API 형태로 제공하면, 다른 프로그램은 이 API를 호출하여 필요한 데이터를 가져올 수 있습니다.

3.3 서비스 지향 아키텍처(SOA)

애플리케이션의 기능이 다른 프로그램이 제공하는 서비스에 접근하는 것을 포함할 때, 이를 서비스 지향 아키텍처(SOA) 접근 방식이라고 합니다. SOA 접근 방식에서는 전체 애플리케이션이 다른 애플리케이션의 서비스를 활용하여 구성됩니다. 반면, 비-SOA 접근 방식에서는 애플리케이션이 필요한 모든 코드를 자체적으로 포함하고 있는 독립형 애플리케이션입니다.

SOA의 예시

우리는 웹에서 SOA의 많은 예를 볼 수 있습니다. 하나의 웹사이트에서 항공편 예약, 호텔 예약, 자동차 대여를 모두 할 수 있습니다. 이 경우, 호텔 데이터는 항공사 컴퓨터에 저장되지 않습니다. 대신 항공사 컴퓨터는 호텔 컴퓨터의 서비스를 호출하여 호텔 데이터를 가져와 사용자에게 제공합니다. 사용자가 항공사 사이트를 통해 호텔 예약을 하면, 항공사 사이트는 호텔 시스템의 웹 서비스를 사용하여 실제로 예약을 처리합니다. 결제 시점이 되면, 또 다른 컴퓨터가 결제 처리를 담당합니다.

3.4 SOA의 장점

SOA는 여러 가지 장점을 가지고 있습니다:

  1. 단일 데이터 복사본 유지: 데이터의 일관성을 유지하고, 특히 호텔 예약과 같이 과잉 예약을 방지합니다.
  2. 데이터 소유자의 규칙 설정: 데이터 소유자가 데이터 사용에 대한 규칙을 설정할 수 있습니다.

이러한 장점으로 인해 SOA 시스템은 성능과 사용자 요구를 충족시키도록 신중하게 설계되어야 합니다.

3.5 웹 서비스

애플리케이션이 API를 통해 웹 상에서 일련의 서비스를 제공할 때, 이를 웹 서비스라고 합니다. 웹 서비스는 SOA의 중요한 구성 요소로, 애플리케이션 간의 상호 운용성을 높이고, 다양한 기능을 통합하여 사용자에게 통합된 서비스를 제공합니다.

SOA와 웹 서비스는 현대 애플리케이션 개발에서 필수적인 요소로 자리 잡고 있습니다. API를 통해 데이터를 교환하고 서비스를 제공함으로써, 더 효율적이고 유연한 애플리케이션을 구축할 수 있습니다.

4. Security and API Usage

4.1 API 키와 보안

API 사용 시 보안을 위해 API 키를 포함해야 합니다. 이는 사용자를 식별하고 사용량을 추적하는 데 도움을 줍니다. OAuth와 같은 기술을 사용하여 요청을 암호화할 수도 있습니다.

4.2 실습 예제: Google Geocoding API

다음은 Google Geocoding API를 사용하여 사용자로부터 입력받은 위치 정보를 기반으로 지리적 좌표와 주소를 검색하는 Python 애플리케이션입니다. 이 예제는 urllib 라이브러리를 사용하여 HTTP 요청을 보내고, JSON 라이브러리를 사용하여 응답 데이터를 처리합니다.

import urllib.request, urllib.parse, urllib.error
import json
import ssl

api_key = 42
serviceurl = 'http://py4e-data.dr-chuck.net/json?'

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

while True:
    address = input('Enter location: ')
    if len(address) < 1: break

    parms = dict()
    parms['address'] = address
    if api_key is not False: parms['key'] = api_key
    url = serviceurl + urllib.parse.urlencode(parms)
    print('Retrieving', url)
    uh = urllib.request.urlopen(url, context=ctx)
    data = uh.read().decode()
    print('Retrieved', len(data), 'characters')

    try:
        js = json.loads(data)
    except:
        js = None

    if not js or 'status' not in js or js['status'] != 'OK':
        print('==== Failure To Retrieve ====')
        print(data)
        continue

    print(json.dumps(js, indent=4))
    lat = js['results'][0]['geometry']['location']['lat']
    lng = js['results'][0]['geometry']['location']['lng']
    print('lat', lat, 'lng', lng)
    location = js['results'][0]['formatted_address']
    print(location)
  1. 라이브러리 임포트: 필요한 라이브러리들을 불러옵니다.
import urllib.request, urllib.parse, urllib.error
import json
import ssl
  • `urllib.request`, `urllib.parse`, `urllib.error`: HTTP 요청을 보내고 URL을 인코딩하며 에러를 처리하기 위해 사용합니다.
  • `json`: JSON 데이터를 파싱하고 처리하기 위해 사용합니다.
  • `ssl`: SSL 인증서를 무시하고 안전하지 않은 연결을 허용하기 위해 사용합니다.
  1. API 키와 서비스 URL 설정: API 키와 기본 서비스 URL을 설정합니다.
api_key = 42
serviceurl = 'http://py4e-data.dr-chuck.net/json?'
  • `api_key`: Google Geocoding API 키를 설정합니다. 이 예제에서는 간단히 `42`로 설정되어 있습니다.
  • `serviceurl`: API 요청을 보낼 기본 URL을 설정합니다.
  1. SSL 컨텍스트 설정: SSL 인증서를 무시하도록 설정합니다.
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
  • `ctx`: SSL 컨텍스트를 생성하여 호스트 이름 검사를 비활성화하고 인증서 검증을 하지 않도록 설정합니다.
  1. 사용자 입력 반복 처리: 사용자로부터 위치를 입력받아 API 요청을 반복적으로 처리합니다.
while True:
    address = input('Enter location: ')
    if len(address) < 1: break
  • `while True`: 무한 루프를 사용하여 반복적으로 사용자 입력을 받습니다.
  • `address = input(‘Enter location: ‘)`: 사용자가 위치를 입력합니다.
  • `if len(address) < 1: break`: 입력된 위치가 없으면 루프를 종료합니다.
  1. 파라미터 구성 및 URL 생성: 사용자 입력과 API 키를 사용하여 URL을 생성합니다.
parms = dict()
parms['address'] = address
if api_key is not False: parms['key'] = api_key
url = serviceurl + urllib.parse.urlencode(parms)
print('Retrieving', url)
  • `parms = dict()`: 빈 딕셔너리를 생성합니다.
  • `parms[‘address’] = address`: 입력된 주소를 `parms` 딕셔너리에 추가합니다.
  • `if api_key is not False: parms[‘key’] = api_key`: API 키를 `parms` 딕셔너리에 추가합니다.
  • `url = serviceurl + urllib.parse.urlencode(parms)`: `parms` 딕셔너리를 URL 쿼리 문자열로 인코딩하여 최종 URL을 생성합니다.
  • `print(‘Retrieving’, url)`: 생성된 URL을 출력합니다.
  1. HTTP 요청 및 응답 처리: 생성된 URL을 사용하여 HTTP 요청을 보내고 응답을 처리합니다.
uh = urllib.request.urlopen(url, context=ctx)
data = uh.read().decode()
print('Retrieved', len(data), 'characters')
  • `uh = urllib.request.urlopen(url, context=ctx)`: URL을 열어 HTTP 요청을 보내고 응답을 받습니다.
  • `data = uh.read().decode()`: 응답 데이터를 읽고 디코딩하여 문자열로 변환합니다.
  • `print(‘Retrieved’, len(data), ‘characters’)`: 수신된 데이터의 길이를 출력합니다.
  1. JSON 데이터 파싱 및 검증: 응답 데이터를 JSON 형식으로 파싱하고 유효성을 검증합니다.
try:
    js = json.loads(data)
except:
    js = None

if not js or 'status' not in js or js['status'] != 'OK':
    print('==== Failure To Retrieve ====')
    print(data)
    continue
  • `try: js = json.loads(data)`: JSON 데이터를 파싱하여 파이썬 딕셔너리로 변환합니다.
  • `except: js = None`: JSON 파싱에 실패하면 `js`를 `None`으로 설정합니다.
  • `if not js or ‘status’ not in js or js[‘status’] != ‘OK’`: JSON 데이터가 없거나 `status` 값이 `OK`가 아니면 실패 메시지를 출력하고 다음 반복을 계속합니다.
  1. JSON 데이터 출력 및 정보 추출: 파싱된 JSON 데이터를 출력하고 필요한 정보를 추출합니다.
print(json.dumps(js, indent=4))
lat = js['results'][0]['geometry']['location']['lat']
lng = js['results'][0]['geometry']['location']['lng']
print('lat', lat, 'lng', lng)
location = js['results'][0]['formatted_address']
print(location)
  • `print(json.dumps(js, indent=4))`: JSON 데이터를 읽기 쉽게 포맷하여 출력합니다.
  • `lat = js[‘results’][0][‘geometry’][‘location’][‘lat’]`: 위도를 추출합니다.
  • `lng = js[‘results’][0][‘geometry’][‘location’][‘lng’]`: 경도를 추출합니다.
  • `print(‘lat’, lat, ‘lng’, lng)`: 추출된 위도와 경도를 출력합니다.
  • `location = js[‘results’][0][‘formatted_address’]`: 포맷된 주소를 추출합니다.
  • `print(location)`: 추출된 주소를 출력합니다.

geojson.py를 실행하면 아래와 같은 결과를 출력합니다.

$ python geojson.py
Enter location: Ann Arbor, MI
Retrieving http://py4e-data.dr-chuck.net/json?address=Ann+Arbor%2C+MI&key=42
Retrieved 2173 characters
{
    "results": [
        {
            "address_components": [
                {
                    "long_name": "Ann Arbor",
                    "short_name": "Ann Arbor",
                    "types": [
                        "locality",
                        "political"
                    ]
                },
                {
                    "long_name": "Washtenaw County",
                    "short_name": "Washtenaw County",
                    "types": [
                        "administrative_area_level_2",
                        "political"
                    ]
                },
                {
                    "long_name": "Michigan",
                    "short_name": "MI",
                    "types": [
                        "administrative_area_level_1",
                        "political"
                    ]
                },
                {
                    "long_name": "United States",
                    "short_name": "US",
                    "types": [
                        "country",
                        "political"
                    ]
                }
            ],
            "formatted_address": "Ann Arbor, MI, USA",
            "geometry": {
                "bounds": {
                    "northeast": {
                        "lat": 42.3239728,
                        "lng": -83.6758069
                    },
                    "southwest": {
                        "lat": 42.222668,
                        "lng": -83.799572
                    }
                },
                "location": {
                    "lat": 42.2808256,
                    "lng": -83.7430378
                },
                "location_type": "APPROXIMATE",
                "viewport": {
                    "northeast": {
                        "lat": 42.3239728,
                        "lng": -83.6758069
                    },
                    "southwest": {
                        "lat": 42.222668,
                        "lng": -83.799572
                    }
                }
            },
            "place_id": "ChIJMx9D1A2wPIgR4rXIhkb5Cds",
            "types": [
                "locality",
                "political"
            ]
        }
    ],
    "status": "OK"
}
lat 42.2808256 lng -83.7430378
Ann Arbor, MI, USA
Enter location:

5. 연습문제 (1)

위의 geojson.py 파일을 수정하여 검색된 데이터에서 국가 코드를 출력하도록 하세요. 국가 코드가 없는 경우 에러를 처리하도록 합니다.

정답 코드

import urllib.request, urllib.parse, urllib.error
import json
import ssl

api_key = 42
serviceurl = 'http://py4e-data.dr-chuck.net/json?'

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

while True:
    address = input('Enter location: ')
    if len(address) < 1: break

    parms = dict()
    parms['address'] = address
    if api_key is not False: parms['key'] = api_key
    url = serviceurl + urllib.parse.urlencode(parms)
    print('Retrieving', url)
    uh = urllib.request.urlopen(url, context=ctx)
    data = uh.read().decode()
    print('Retrieved', len(data), 'characters')

    try:
        js = json.loads(data)
    except:
        js = None

    if not js or 'status' not in js or js['status'] != 'OK':
        print('==== Failure To Retrieve ====')
        print(data)
        continue

    print(json.dumps(js, indent=4))
    lat = js['results'][0]['geometry']['location']['lat']
    lng = js['results'][0]['geometry']['location']['lng']
    print('lat', lat, 'lng', lng)
    location = js['results'][0]['formatted_address']
    print(location)

    try:
        address_components = js['results'][0]['address_components']
        country_code = None
        for component in address_components:
            if 'country' in component['types']:
                country_code = component['short_name']
                break
        if country_code:
            print('Country code:', country_code)
        else:
            print('Country code not found')
    except Exception as e:
        print('Error retrieving country code:', e)

상세 설명

  1. 라이브러리 임포트: 필요한 라이브러리들을 불러옵니다.
import urllib.request, urllib.parse, urllib.error
import json
import ssl
  1. API 키와 서비스 URL 설정: API 키와 기본 서비스 URL을 설정합니다.
api_key = 42
serviceurl = 'http://py4e-data.dr-chuck.net/json?'
  1. SSL 컨텍스트 설정: SSL 인증서를 무시하도록 설정합니다.
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
  1. 사용자 입력 반복 처리: 사용자로부터 위치를 입력받아 API 요청을 반복적으로 처리합니다.
while True:
    address = input('Enter location: ')
    if len(address) < 1: break
  1. 파라미터 구성 및 URL 생성: 사용자 입력과 API 키를 사용하여 URL을 생성합니다.
parms = dict()
parms['address'] = address
if api_key is not False: parms['key'] = api_key
url = serviceurl + urllib.parse.urlencode(parms)
print('Retrieving', url)
  1. HTTP 요청 및 응답 처리: 생성된 URL을 사용하여 HTTP 요청을 보내고 응답을 처리합니다.
uh = urllib.request.urlopen(url, context=ctx)
data = uh.read().decode()
print('Retrieved', len(data), 'characters')
  1. JSON 데이터 파싱 및 검증: 응답 데이터를 JSON 형식으로 파싱하고 유효성을 검증합니다.
try:
    js = json.loads(data)
except:
    js = None

if not js or 'status' not in js or js['status'] != 'OK':
    print('==== Failure To Retrieve ====')
    print(data)
    continue
  1. JSON 데이터 출력 및 정보 추출: 파싱된 JSON 데이터를 출력하고 필요한 정보를 추출합니다.
print(json.dumps(js, indent=4))
lat = js['results'][0]['geometry']['location']['lat']
lng = js['results'][0]['geometry']['location']['lng']
print('lat', lat, 'lng', lng)
location = js['results'][0]['formatted_address']
print(location)
  1. 국가 코드 추출 및 출력: 응답 데이터에서 국가 코드를 추출하고 출력합니다.
try:
    address_components = js['results'][0]['address_components']
    country_code = None
    for component in address_components:
        if 'country' in component['types']:
            country_code = component['short_name']
            break
    if country_code:
        print('Country code:', country_code)
    else:
        print('Country code not found')
except Exception as e:
    print('Error retrieving country code:', e)
  • `address_components = js[‘results’][0][‘address_components’]`: 주소 구성 요소를 가져옵니다.
  • `country_code = None`: 국가 코드를 저장할 변수를 초기화합니다.
  • `for component in address_components`: 각 주소 구성 요소를 반복합니다.
  • `if ‘country’ in component[‘types’]`: 구성 요소가 ‘country’ 유형을 포함하는지 확인합니다.
  • `country_code = component[‘short_name’]`: ‘short_name’을 국가 코드로 저장합니다.
  • `if country_code`: 국가 코드가 있으면 출력합니다.
  • `else`: 국가 코드가 없으면 ‘Country code not found’를 출력합니다.
  • `except Exception as e`: 예외가 발생하면 에러 메시지를 출력합니다.

파이썬 기초 강좌: 12. 네트워크 프로그래밍 예제와 실습 (Python Basics – Networked programs Examples and Exercises)

파이썬 기초 강좌: 14

Leave a Comment