1. HTTP 통신으로 웹 접속 Last updated: 2023-10-17 10:30:47

HTTP는 HyperText Transfer Protocol의 약자로 인터넷에서 요청과 응답을 처리하는 텍스트 기반의 통신 규약이다. 이 장에서는 파이썬을 이용해 HTTP 통신을 처리하는 방법에 대해 알아본다.

1.1 requests 모듈


1.1.1 웹페이지 다운로드 하기

다운로드할 웹페이지 주소를 알고 있는 경우 requests 라이브러리를 이용해 html 파일을 다운로드 할 수 있다. 웹페이지를 다운로드 하는 것은 웹 크롤링의 기분으로 보통 다운로드 받은 html 파일을 BeautifulSoup 라이브러리로 파싱 해서 정보를 추출하게 된다.

Hint! 페이지를 주소로 모든 내용을 다운 받을 수는 없다.

단, 다음의 경우에는 웹페이지 주소를 다운로드 하더라도 전체 웹페이지를 다운 받지 못할 수 있다.

  1. 페이지 내에서 동적으로 데이터를 요청하는 경우

    주소에 해당하는 html 페이지에는 기본적인 프레임만 포함되어 있거나 일부 반복되는 영역은 포함하지 않고, 페이지가 로딩되면서 동적으로 조회하여 내용을 채우는 경우가 있다. 이러한 경우 url로 페이지를 불러와 저장하면 일부 데이터 영역은 누락될 수 있다.

  2. 주소에 세부 경로가 포함되지 않은 경우

    대규모의 포털이나 미디어 사이트의 경우 각 페이지를 보여주더라도 주소는 각 페이지의 주소를 표시하지 않는 경우가 있다. 이러한 경우 대표 페이지만 불러오고 각 세부 페이지는 주소로 접근이 어려울 수 있다.

다음은 파이썬의 기본 모듈인 requests를 이용해 URL로 부터 웹페이지를 다운로드해 파일로 저장하는 예제이다.


[예제 7- 1] request 모듈로 웹페이지 다운로드 하기

# [1] 모듈 임포트
import requests

# [2] 접속할 URL 주소
page_url = "https://www.digitaltoday.co.kr/news/articleView.html?idxno=484315"

# [3] http 요청 헤더
user_agent = "'Mozilla/5.0"
headers = {"User-Agent": user_agent, 'Content-Type': 'text/html; charset=utf-8'}

# [4] 페이지 요청
response = requests.get(page_url, headers=headers, verify=False)

# [5] 응답 상태 확인
if response.status_code == 200:
    
    # [6] 파일 저장
    response.encoding = 'utf-8'
    with open('itworld_302605.html', 'wt', encoding='utf8') as f:
        f.write(response.text)

위 예제에 대해 설명한다.

  • [1]에서 requests 모듈을 임포트한다.

  • [2]에서 다운로드할 웹페이지의 주소를 지정한다.

  • [3]에서 http 헤더를 설정한다. http 헤더는 http 통신을 위해서는 꼭 필요한 내용이다.

  • [4]에서 웹페이지 주소와 헤더 정보를 이용해 웹페이지를 요청한다.

  • [5]에서 반환된 상태값을 확인한다. 200번인 경우 정상적으로 웹서버 접속이 되었다는 것이다.

  • [6]에서 다운로드 받은 텍스트 파일을 인코딩할 방법을 지정하고, html 형식의 텍스트 파일을 저장한다.

다음은 다운로드하여 저장한 html 파일을 웹브라우저에서 열어본 것이다.

[결과]

image

[그림 7-1-1] 다운로드한 웹페이지 화면



1.1.2 http 모듈로 웹페이지 다운로드 하기

requests 모듈과 마찬가지로 http 모듈도 웹 통신을 지원하는 모듈이다. 위와 동일한 주소를 http 모듈로 접속해 내용을 다운로드 받아 html 파일을 저장해보도록 한다. 다음은 http 모듈을 이용해 웹페이지를 다운로드하여 파일로 저장하는 예제이다.

[예제 7- 2] http 모듈로 웹페이지 다운로드하기

# [1] 모듈 임포트
import http.client

# [2] 접속할 서버의 HTTP 커넥션
conn = http.client.HTTPSConnection("www.digitaltoday.co.kr")

# [3] 페이지 요청
conn.request("GET", "/news/articleView.html?idxno=484315")

# [4] 응답 상태 확인
r = conn.getresponse()
if r.getcode() == 200:
    
    # [5] 파일 저장
    with open('itworld_302605(2).html', 'wb') as f:
        f.write(r.read())
    
# [6] HTTP 커넥션 닫기
conn.close()

위 예제에 대해 설명한다.

  • [1]에서 http.client 모듈을 임포트한다.

  • [2]에서 서버 주소를 입력하고 HTTPSConnection 객체를 생성한다. 커넥션 객체는 conn 변수에 저장한다.

  • [3]에서 세부 주소를 입력하고 GET 방식으로 페이지를 요청한다.

  • [4]에서 응답 상태를 확인한다. 응답 코드가 200번이면 정상이다.

  • [5]에서 다운로드한 내용을 html 파일로 저장한다.

  • [6]에서 사용이 끝난 HTTPSConnection 객체를 닫아준다.


1.2 urllib 모듈

urllib는 파이썬에서 url에 해당하는 정보를 가져오기 위해 많이 사용하는 모듈이다. urllib는 사용이 상대적으로 간단하고, URL을 통해 웹페이지 뿐만 아니라 이미지 등 파일도 쉽게 로컬에 저장할 수 있다.


1.2.1 웹페이지 다운로드 하기

urllib를 사용해 웹페이지를 파일로 저장하는 예제를 살펴보자.

[예제 7- 3] urrlib 모듈로 웹페이지 다운로드 하기

# [1] 모듈 임포트
from urllib import request

# [2] 접속할 URL 주소
page_url = "https://www.digitaltoday.co.kr/news/articleView.html?idxno=484315"

# [3] 페이지 요청
response = request.urlopen(page_url)

# [4] 응답 상태 확인
if response.status == 200:
    
    # [5] 파일 저장
    with open('itworld_302605(3).html', 'wb') as f:
        f.write(response.read())

위 예제에 대해 설명한다.

  • [1]에서 urllib의 request 모듈을 임포트한다.

  • [2]에서 다운로드할 웹페이지의 주소를 지정한다.

  • [3]에서 세부 주소를 입력하고 urlopen( ) 함수를 이용해 요청한다.

  • [4]에서 응답 상태를 확인한다. 응답 코드가 200번이면 정상이다.

  • [5]에서 read( ) 함수로 다운로드한 내용을 읽어서 html 파일로 저장한다. 이 때 파일 쓰기 옵션은 ‘wb’로 바이너리 데이터를 그대로 파일로 저장한다.


1.2.2 urllib 모듈로 웹페이지 다운로드 하기

이번에는 urllib 모듈을 이용해 웹을 다운로드 하는 방법에 대해 알아보자. 바이너리 데이터를 텍스트로 디코딩 한 후에 텍스트 파일로 저장하는 것이 이전과 다르다.

[예제 7- 4] 다운로드한 바이너리 파일을 텍스트로 디코딩하기

# [1] 모듈 임포트
from urllib import request

# [2] 접속할 URL 주소
page_url = "https://www.digitaltoday.co.kr/news/articleView.html?idxno=484315"

# [3] 페이지 요청
response = request.urlopen(page_url)

# [4] 응답 상태 확인
if response.status == 200:

    # [5] 파일 저장
    data = response.read()
    text = data.decode('utf8')
    
    # [6] 파일 저장
    with open('itworld_302605(4).html', 'wt', encoding='utf8') as f:
        f.write(text)

위 예제에 대해 설명한다.

  • [1]에서 urllib의 request 모듈을 임포트한다.

  • [2]에서 다운로드할 웹페이지의 주소를 지정한다.

  • [3]에서 세부 주소를 입력하고 urlopen( ) 함수를 이용해 요청한다.

  • [4]에서 응답 상태를 확인한다. 응답 코드가 200번이면 정상이다.

  • [5]에서 read( ) 함수로 다운로드한 바이너리 데이터를 가져온다. 그리고 decode( ) 함수를 이용해 텍스트 데이터로 변환한다.

  • [6]에서 파일을 열어서 쓰는데, 이번에는 ‘wt’모드로 텍스트 데이터를 파일로 쓴다.


1.2.3 urllib 모듈로 이미지 파일 다운로드 하기

urllib 모듈을 사용하면 텍스트 파일 뿐만 아니라 이미지 파일, 비디오 파일 등 파일 주소만 알고 있다면 쉽게 다운로드 할 수 있다. 다음은 기상청에서 제공하는 태풍 카눈(KHANUN)의 23년 8월 10일 13시 현재 진행 상황에 대한 이미지를 다운로드 하는 예제이다.

[예제 7- 5] urllib 모듈로 이미지 파일 다운로드 하기

# [1] 모듈 임포트
from urllib import request

# [2] 접속할 URL 주소
page_url = f"https://www.weather.go.kr/repositary/image/typ/img/RTKO63_202308101300]06_ko.png"

# [3] 페이지 요청
response = request.urlopen(page_url)

# [4] 응답 상태 확인
if response.status == 200:
    
    # [5] 파일 저장
    with open('RTKO63_202308101300]06_ko.png', 'wb') as f:
        f.write(response.read())

위 예제에 대해 설명한다.

  • [1]에서 urllib의 request 모듈을 임포트한다.

  • [2]에서 다운로드할 이미지 파일의 주소를 지정한다.

  • [3]에서 세부 주소를 입력하고 urlopen( ) 함수를 이용해 요청한다.

  • [4]에서 응답 상태를 확인한다. 응답 코드가 200번이면 정상이다.

  • [5]에서 read( ) 함수로 다운로드한 내용을 읽어서 바이너리 데이터를 읽어서 파일로 쓴다.

다음은 다운로드 된 이미지 파일 이다.


image

[그림 7-1-2] 다운로드한 이미지



1.3. Restful API 요청하기

http 통신을 통해 html 파일을 통째로 다운로드 받는 방법 말고 url을 통해 원하는 데이터만 json 형태로 받아오는 방식이 Restful API 이다. 이 방법을 사용하면 군더더기 없이 필요한 데이터만 json 형태로 보내거나 받을 수 있으므로 화면이 필요 없이 DB 조회와 비즈니스 처리를 위한 백엔드 서버에 많이 활용된다. Restful API 테스트를 하기 위해 별도 서버를 구축해야 하나 이 책의 내용 범위를 벗어나므로 간단하게 Restful API 요청 테스트를 도와주는 Restfule API Fake 사이트를 사용해보도록 한다.


1.3.1 Fake Site 사이트 소개


A. Restful API 테스트를 위한 https://reqres.in 사이트

reqres.in은 Restful API 테스트를 위한 기능을 제공하는 사이트이다. 우리가 Restful API 요청과 결과를 직접 확인해 보려면 Restful Server를 구축해야 한다. 만일 서버 자원이 넉넉하거나 Restfule Server 구축하는데 어려움이 없다면 상관 없지만 이 또한 시간과 자원이 필요하고, 구현에 어려움이 발생할 수도 있다. 이러한 문제점을 도와주고자 생겨난 것이 reqres.in 과 같은 Restful API Fake Site 이다. 이런 사이트의 특징은 서버를 직접 구축할 필요가 없고, 간단히 client를 테스트해 볼 수 있다는 점이 있다. reqres.in 사이트는 특히 테스트를 위한 API 들을 다양하게 갖추고 있어 바로 테스트가 가능하고, GET, POST, PUT, DELETE 등 대부분의 명령을 지원하며, 1개월 1억 건 까지 무료로 사용할 수 있다.

image

[그림 7-1-3] http://reqres.in 사이트 화면



1.3.2 GET 방식 요청


A. 주소 방식으로 Restful API 요청 테스트

JSON 목록을 조회하는 LIST USERS를 Restful API로 요청해 본다. 먼저 https://reqres.in 사이트로 이동한다. 아래쪽으로 내려보면 호출이 가능한 API 목록이 보이다. 우리는 사용자 목록을 조회하는 ① LIST USERS 를 클릭한다. 그러면 ②Request 목록에서 호출할 수 있는 주소가 표시된다. ‘/api/users?page=2’라고 쓰여 있으므로 우리가 호출할 url은 ‘https://reqres.in/api/users?page2’가 된다. 해당 API를 호출하면 반환되는 값이 ③Response에 표시된다. 정상적인 응답인 경우 200번이 상태값으로 반환되며 아래 부분에 실제 수신될 json 형식의 데이터 값도 보이다.

image

[그림 7-1-4] GET 방식 요청과 결과 예시

위 내용을 참고하여 Restful API를 호출하는 예제는 다음과 같다.


[예제 7- 6] Restful API 테스트하기

# [1] 모듈 임포트
import requests
import json
import pprint

# [2] 접속할 URL 주소
page_url = "https://reqres.in/api/users?page=2"

# [3] http 요청 헤더
user_agent = "Mozilla/5.0"
headers = {"User-Agent": user_agent, 'Content-Type': 'text/html; charset=utf-8'}

# [4] 요청
response = requests.get(page_url, headers=headers, verify=False)
if response.status_code == 200:
    # [5] 결과 dict 변환
    res_json = json.loads(response.text)
    pprint.pprint(res_json)

[결과]

{'data': [{'avatar': '<https://reqres.in/img/faces/7-image.jpg>',
           'email': 'michael.lawson@reqres.in',
           'first_name': 'Michael',
           'id': 7,
           'last_name': 'Lawson'},
          {'avatar': '<https://reqres.in/img/faces/8-image.jpg>',
           'email': 'lindsay.ferguson@reqres.in',
           'first_name': 'Lindsay',
           'id': 8,
           'last_name': 'Ferguson'},
          {'avatar': '<https://reqres.in/img/faces/9-image.jpg>',
           'email': 'tobias.funke@reqres.in',
           'first_name': 'Tobias',
           'id': 9,
           'last_name': 'Funke'},
          {'avatar': '<https://reqres.in/img/faces/10-image.jpg>',
           'email': 'byron.fields@reqres.in',
           'first_name': 'Byron',
           'id': 10,
           'last_name': 'Fields'},
          {'avatar': '<https://reqres.in/img/faces/11-image.jpg>',
           'email': 'george.edwards@reqres.in',
           'first_name': 'George',
           'id': 11,
           'last_name': 'Edwards'},
          {'avatar': '<https://reqres.in/img/faces/12-image.jpg>',
           'email': 'rachel.howell@reqres.in',
           'first_name': 'Rachel',
           'id': 12,
           'last_name': 'Howell'}],
 'page': 2,
 'per_page': 6,
 'support': {'text': 'To keep ReqRes free, contributions towards server costs '
                     'are appreciated!',
             'url': '<https://reqres.in/#support-heading>'},
 'total': 12,
 'total_pages': 2}

위 예제에 대해 설명한다.

  • [1]에서 필요한 모듈을 임포트한다.

  • [2]에서 호출할 Restful API의 URL 변수를 지정한다. GET 방식 URL은 ‘?’ 기호 뒤에 원하는 파라미터를 ‘&’ 연산자로 연결해서 직접 나열할 수 있다.

  • [3]에서 http 호출을 위한 header를 지정한다.

  • [4]에서 실제 요청을 수행하고 응답 결과를 받아 상태 코드를 출력한다. 정상인 경우 200번 코드를 반환한다.

  • [5]에서 반환 된 결과의 문자열을 json 모듈을 이용해 dictionary 객체로 변환한다. 출력한 결과를 보면 홈페이지의 Response와 조금 다르다. 사실 내용은 같지만 json.loads( ) 함수에서 dictionary 형식으로 변환할 때 key 순서가 알파벳 순서로 정렬되어 그렇다. 실제 key와 value의 순서는 다르지만 내용은 동일하다.


B. 파라미터 방식으로 Restful API 요청 테스트

위에서 GET 방식으로 Restful API를 호출하는 방식에 대해 알아보았다. GET 방식은 파라미터를 뒤에 나열해서 작성된 주소를 이용해 호출할 수 있다. 하지만, 파라미터가 복잡한 경우에는 별도로 params 변수에 dict 형태로 파라미터와 해당하는 값을 지정할 수도 있다. 다음은 위와 동일한 요청이지만 파라미터를 주소가 아니라 별도 변수로 지정해 호출하는 방식에 대한 예제이다.

[예제 7- 7] 파라미터 방식으로 Restful API 테스트하기

# [1] 모듈 임포트
import requests
import json
import pprint

# [2] 접속할 URL 주소
page_url = "https://reqres.in/api/users"
params = {'page': '2'}

# [3] http 요청 헤더
user_agent = "Mozilla/5.0"
headers = {"User-Agent": user_agent, 'Content-Type': 'text/html; charset=utf-8'}

# [4] 요청
response = requests.get(page_url, headers=headers, verify=False, params=params)
if response.status_code == 200:
    # [5] 결과 dict 변환
    res_json = json.loads(response.text)
    pprint.pprint(res_json)

[결과]

{'data': [{'avatar': '<https://reqres.in/img/faces/7-image.jpg>',
           'email': 'michael.lawson@reqres.in',
           'first_name': 'Michael',
           'id': 7,
           'last_name': 'Lawson'},
          {'avatar': '<https://reqres.in/img/faces/8-image.jpg>',
           'email': 'lindsay.ferguson@reqres.in',
           'first_name': 'Lindsay',
           'id': 8,
           'last_name': 'Ferguson'},
          {'avatar': '<https://reqres.in/img/faces/9-image.jpg>',
           'email': 'tobias.funke@reqres.in',
           'first_name': 'Tobias',
           'id': 9,
           'last_name': 'Funke'},
          {'avatar': '<https://reqres.in/img/faces/10-image.jpg>',
           'email': 'byron.fields@reqres.in',
           'first_name': 'Byron',
           'id': 10,
           'last_name': 'Fields'},
          {'avatar': '<https://reqres.in/img/faces/11-image.jpg>',
           'email': 'george.edwards@reqres.in',
           'first_name': 'George',
           'id': 11,
           'last_name': 'Edwards'},
          {'avatar': '<https://reqres.in/img/faces/12-image.jpg>',
           'email': 'rachel.howell@reqres.in',
           'first_name': 'Rachel',
           'id': 12,
           'last_name': 'Howell'}],
 'page': 2,
 'per_page': 6,
 'support': {'text': 'To keep ReqRes free, contributions towards server costs '
                     'are appreciated!',
             'url': '<https://reqres.in/#support-heading>'},
 'total': 12,
 'total_pages': 2}

위 예제에 대해 설명한다.

  • [1]에서 필요한 모듈을 임포트한다.

  • [2]에서 호출할 Restful API의 URL 변수를 지정한다. 전할 파라미터를 파이썬 dictionary 형식으로 키와 값으로 짝을 작성한다.

  • [3]에서 http 호출을 위한 header를 지정한다.

  • [4]에서 실제 요청을 수행하고 응답 결과를 받아 상태 코드를 출력한다. 다른 점은 params 변수에 작성한 dictionary 변수를 할당하는 것이다.

  • [5]에서 반환 된 결과의 문자열을 json 모듈을 이용해 dictionary 객체로 변환한다. 출력한 결과를 보면 홈페이지의 Response와 조금 다르다. 사실 내용은 같지만 json.loads( ) 함수에서 dictionary 형식으로 변환할 때 key 순서가 알파벳 순서로 정렬되어 그렇다.


1.3.2 POST 방식으로 요청


A. POST 방식으로 Restful API 요청 테스트

POST 방식으로 호출하는 방식에 대해 알아보자. GET 방식은 URL에 주소와 함께 파라미터 정보도 함께 보내기 때문에 쉽게 노출되고 URL 길이 제한을 받는다. 주로 데이터를 조회할 때 사용한다. POST 방식은 파라미터를 URL이 아니라 Body 부분에 담아서 전달한다. 따라서 길이 제한이 거의 없다. 때문에 데이터를 서버에 전달해 항목을 삽입하거나 업데이트할 때 사용한다. 또한 Body 부분의 암호화가 가능하므로 사용자 로그인 등 보다 민감한 데이터를 전송할 때 사용한다. 다음은 POST 방식을 이용해 http://reqres.in 사이트에서 사용자 로그인을 시도한 예제이다. 사용자 email과 password를 전달하고 정상이면 임시 토큰을 반환하는 예제이다.

[예제 7- 8] POST 방식으로 Restful API 테스트하기

# [1] 모듈 임포트
import requests
import json
import pprint

# [2] 접속할 URL 주소
page_url = "https://reqres.in/api/login"
param = {"email": "eve.holt@reqres.in", "password": "cityslicka"}

# [3] 요청
headers = {'Content-Type': 'application/json; chearset=utf-8'}

# [4] 요청
response = requests.post(page_url, data=json.dumps(param), headers=headers)
if response.status_code == 200:
    # [5] 결과 dict 변환
    res_json = json.loads(response.text)
    pprint.pprint(res_json)
else:
    print(response)

위 예제에 대해 설명한다.

  • [1]에서 필요한 모듈을 임포트한다.

  • [2]에서 호출할 Restful API의 URL 변수를 지정한다. 전할 파라미터인 email과 password를 dictionary 형식으로 작성한다.

  • [3]에서 http 호출을 위한 header를 지정한다. 파라미터 전달은 json 형식이다.

  • [4]에서 실제 요청을 수행한다. 이 때 파라미터는 json.dumps( )함수를 이용해 dictionary형식을 json 형식으로 변환한다.

  • [5]에서 반환 된 임시 토큰을 출력한다.


B. GET 방식과 POST 방식 비교

다음은 GET 방식과 POST 방식의 특징을 비교한 것이다. 사용 환경에 따라 특징에 맞는 방식을 선택하는데 참고하기 바란다.

[표 9] GET 방식과 POST 방식 비교

구분

GET 방식

POST 방식

주요 용도

클라이언트가 서버로부터 정보를 요청할 때 사용한다.

클라이언트가 서버 리소스를 생성하거나 업데이트할 때 사용한다.

파라미터 전달 방식

URL 주소 끝에 '?'를 붙이고 '변수명=값' 형태로 이어 붙인다.

HTTP 메시지의 body 부분에 담겨서 전달, 암호화 가능하다.

특징

요청에 대해 서버 캐쉬 가능하다.
요청이 브라우저 히스토리에 남는다.
URL 노출로 보안이 낮은 요청에 사용한다.

요청은 서버에 캐쉬 되지 않는다.
요청은 브라우저 히스토리에 남지 않는다.
요청시 데이터 길이 제한이 없다