하얀설표 블로그




예제)파이썬 requests 모듈 사용방법 정리





( 수정됨)


#-*- requests:2.31.0 -*-

requests 모듈의 일반적인 사용방법을 정리한 글이다.

정리한 내용 이외의 더 많은 내용이 궁금하다면 requests 공식문서 페이지에서 확인할 수 있다.
글 작성일 기준 공식문서의 한글번역은 제공되지 않고 있다.

설치방법

pip 또는 git clone 명령을 통해 설치할 수 있다.
다음 명령어들 중 자신에게 맞는 명령어를 사용하면 된다.

$ pip install requests
$ python -m pip install requests
$ git clone https://github.com/psf/requests.git

요청을 보내는 방법

requests.get(url) 명령은 requests.request("GET", url) 명령과 동일하고, requests.post(url) 명령은 requests.request("POST", url) 명령과 동일다.
그 이유는 requests.get 명령과 requests.post 명령 모두 requests.request 객체를 return하는 함수이기 때문이다.

참고로 requests.request의 method 인수는 대소문자를 구분하지 않는다.
대소문자에 관계없이 method 값을 upper() 명령을 통해 대문자로 바꿔버리기 때문이다.

다음은 requests 모듈의 request와 get, post 함수만 부분적으로 가져온 것이다. github에서 직접 확인할 수도 있다.

코드

# requests.modeles.api
def request(method, url, **kwargs):
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)
def get(url, params=None, **kwargs):
    return request("get", url, params=params, **kwargs)
def post(url, data=None, json=None, **kwargs):
    return request("post", url, data=data, json=json, **kwargs)

요청가능한 method

requests 모듈에서는 다양한 요청방법(method)을 지원한다.
공식 문서에서 제공하는 예시에는 흔히 웹사이트를 크롤링할 때 사용하는 get, post를 비롯해  put, delete, head, options 등이 가능하다고 한다.

Session

requests.Session() 객체를 이용하는 방법은 이 글에서 확인할 수 있다.

파라미터 전달방법

코드

requests.get

GET 요청의 경우 파라미터를 url에 쿼리값을 더해 전달해도 되고, params 인수로 전달해도 된다.
둘 중 어떤 방법을 사용하더라도 요청을 보내는 url은 동일하다.

import requests
r = requests.get('https://httpbin.org/get?key1=value1&key2=value2&key2=value3')
print(r.url)
payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
r = requests.get('https://httpbin.org/get', params = payload)
print(r.url)

>> https://httpbin.org/get?key1=value1&key2=value2&key2=value3
https://httpbin.org/get?key1=value1&key2=value2&key2=value3

url query와 파라미터를 함께 전달하면 어떻게 될까?

이렇게 요청하는 경우 url 쿼리와 파라미터를 모두 적용한 url을 요청하게 된다.

import requests
payload = {'key2': 'value3', 'key3': 'value4', 'key4': 'value5'}
r = requests.get('https://httpbin.org/get?key1=value1&key2=value2', params = payload)
print(r.url)

>> https://httpbin.org/get?key1=value1&key2=value2&key2=value3&key3=value4&key4=value5

requests.post

post 객체는 파라미터 전달을 위해 json과 data 2개의 인수를 제공한다.
data 인수는 디코딩된 파라미터를 전달할 때 사용하는 것이고, json 인수는 인코딩된 인수를 전달해야할 때 사용하는 것이다.

파라미터를 직접 인코딩하여 json이 아닌 data 인수로 전달하는 것도 가능하지만, 사용자의 편의성을 위해 json 인수를 제공한다고 한다.

import requests
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post('https://httpbin.org/post', data = payload)
print(r.json()['form'])

>> {'key1': 'value1', 'key2': 'value2'}

post 파라미터는 dict 객체를 생성할 때처럼 리스트 안 튜플 형태로 제공해도 정상적으로 전달된다.

코드

import requests
payload_tuples = [('key1', 'value1'), ('key1', 'value2'), ('key2', 'value3')]
r = requests.post('https://httpbin.org/post', data = payload_tuples)
print(r.json()['form'])
payload_dict = {'key1': ['value1', 'value2'], 'key2': 'value3'}
r = requests.post('https://httpbin.org/post', data = payload_dict)
print(r.json()['form'])

>> {'key1': ['value1', 'value2'], 'key2': 'value3'}
{'key1': ['value1', 'value2'], 'key2': 'value3'}

응답 개체 확인방법

응답받은 내용을 확인하기 위해 사용하는 방법은 보통 r.json()과 r.text 명령이다.
참고로 r.content는 r.text와 비슷한 결과를 보여준다.

코드

import requests
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.get('https://httpbin.org/get', params=payload)
print(type(r.json()), [r.json()])
print(type(r.content), [r.content])
print(type(r.text), [r.text])

>> <class 'dict'> [{'args': {'key1': 'value1', 'key2': 'value2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.31.0', 'X-Amzn-Trace-Id': 'Root=1-64a61b44-55026f6c22a747e956ee8686'}, 'origin': '180.66.204.185', 'url': 'https://httpbin.org/get?key1=value1&key2=value2'}]
<class 'bytes'> [b'{\n  "args": {\n    "key1": "value1", \n    "key2": "value2"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.31.0", \n    "X-Amzn-Trace-Id": "Root=1-64a61b44-55026f6c22a747e956ee8686"\n  }, \n  "origin": "180.66.204.185", \n  "url": "https://httpbin.org/get?key1=value1&key2=value2"\n}\n']
<class 'str'> ['{\n  "args": {\n    "key1": "value1", \n    "key2": "value2"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.31.0", \n    "X-Amzn-Trace-Id": "Root=1-64a61b44-55026f6c22a747e956ee8686"\n  }, \n  "origin": "180.66.204.185", \n  "url": "https://httpbin.org/get?key1=value1&key2=value2"\n}\n']

r.content와 r.text의 차이점

r.content와 r.text의 가장 큰 차이점은 객체의 type이다.
위 코드에서 확인할 수 있는 것처럼 r.content는 bytes 객체를 return하지만 r.text는 str 객체를 return한다.
정확히는 r.content를 인코딩해 str 객체로 변환한 것이 r.text이다.

requests 모듈과 함께 사용하게 되는 bs4 모듈에서는 str 객체를 인수로 넣어줘야 하기 때문에 r.text를 자주 사용하게 된다.

인코딩값을 확인하고 변경하는 방법

r.encoding 값을 통해 응답 객체에 적용된 인코딩 값을 확인하고, 변경할 수 있다.
이 인코딩값은 r.text 개체에 영향을 주고, 작동할 때마다 인코딩 값을 사용하는 r.json() 명령에도 영향을 준다.

보통 인코딩값은 헤더의 content-type으로 전달되긴 하지만, 인코딩값을 확인할 수 없는 경우 모듈에서 자체적으로 판단한 인코딩값을 적용하게 된다.
다음 코드를 보면 인코딩값 변경없이 r.json() 명령을 문제없이 이루어졌지만, 임의의 값으로 인코딩값을 변경한 다음 r.json() 명령을 사용하자 에러가 발생하는 것을 확인할 수 있다.

코드

import requests
payload = {'key': '가나다라'}
r = requests.post('https://httpbin.org/post', data = payload)
print(r.headers.get('content-type'))
print(r.encoding)
print([r.text])
print([r.content])
print(r.json()['form'])
r.encoding = 'utf-16'
print(r.encoding)
print([r.text])
print([r.content])
print(r.json()['form'])
 >> application/json
utf-8
['{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "key": "\\uac00\\ub098\\ub2e4\\ub77c"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Content-Length": "40", \n    "Content-Type": "application/x-www-form-urlencoded", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.31.0", \n    "X-Amzn-Trace-Id": "Root=1-64a64ae7-3592110912cdae2d1273e7fd"\n  }, \n  "json": null, \n  "origin": "180.66.204.185", \n  "url": "https://httpbin.org/post"\n}\n']
[b'{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "key": "\\uac00\\ub098\\ub2e4\\ub77c"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Content-Length": "40", \n    "Content-Type": "application/x-www-form-urlencoded", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.31.0", \n    "X-Amzn-Trace-Id": "Root=1-64a64ae7-3592110912cdae2d1273e7fd"\n  }, \n  "json": null, \n  "origin": "180.66.204.185", \n  "url": "https://httpbin.org/post"\n}\n']
{'key': '가나다라'}
utf-16
['\u0a7b†愢杲≳›絻\u202c\u200a∠慤慴㨢∠Ⱒਠ†昢汩獥㨢笠ⱽਠ†昢牯≭›\u0a7b††欢祥㨢∠畜捡〰畜ぢ㠹畜㉢㑥畜㝢挷ਢ†ⱽਠ†栢慥敤獲㨢笠\u200a†∠捁散瑰㨢∠⼪∪\u202c\u200a†∠捁散瑰䔭据摯湩≧›朢楺Ɒ搠晥慬整Ⱒਠ††䌢湯整瑮䰭湥瑧≨›㐢∰\u202c\u200a†∠潃瑮湥\u2d74祔数㨢∠灡汰捩瑡 
潩⽮\u2d78睷\u2d77潦浲甭汲湥潣敤≤\u202c\u200a†∠潈瑳㨢∠瑨灴楢\u2e6e牯≧\u202c\u200a†∠獕牥䄭敧瑮㨢∠祰桴湯爭煥敵瑳⽳⸲ㄳ〮Ⱒਠ††堢䄭穭\u2d6e牔捡ⵥ摉㨢∠潒瑯ㄽ㘭愴㐶敡ⴷ㔳㈹ㄱ㤰㈱摣敡搲㈱㌷㝥摦ਢ†ⱽਠ†樢潳≮›畮汬\u202c\u200a∠牯杩湩㨢∠㠱⸰㘶㈮㐰ㄮ㔸Ⱒਠ†產汲㨢∠ 
瑨灴㩳⼯瑨灴楢\u2e6e牯⽧潰瑳ਢ\u0a7d']
[b'{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "key": "\\uac00\\ub098\\ub2e4\\ub77c"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Content-Length": "40", \n    "Content-Type": "application/x-www-form-urlencoded", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.31.0", \n    "X-Amzn-Trace-Id": "Root=1-64a64ae7-3592110912cdae2d1273e7fd"\n  }, \n  "json": null, \n  "origin": "180.66.204.185", \n  "url": "https://httpbin.org/post"\n}\n']
Traceback (most recent call last):
  File "C:\seolpyo\Lib\site-packages\requests\models.py", line 971, in json
    return complexjson.loads(self.text, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\json\__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\seolpyo\example.py", line 13, in <module>
    print(r.json()['form'])
          ^^^^^^^^
  File "C:\seolpyo\Lib\site-packages\requests\models.py", line 975, in json
    raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

임의의 헤더를 사용하는 방법

임의의 헤더를 이용하고 싶다면 headers 인수에 dict 객체를 전달하면 된다.

import requests
url = 'https://api.github.com/some/endpoint'
headers = {'user-agent': 'my-app/0.0.1'}
r = requests.get(url, headers = headers)

쿠키 변경방법

헤더 변경방법과 비슷한 방식으로 쿠키를 변경할 수 있다.
쿠키에 해당하는 인수는 cookies이다.

import requests
url = 'https://httpbin.org/cookies'
cookies = dict(cookies_are='working')
r = requests.get(url, cookies = cookies)
print(r.json())

>> '{"cookies": {"cookies_are": "working"}}'

url별 쿠키 설정방법

requests.cookies.RequestsCookieJar() 명령을 통해 도메인별, path별로 적용할 쿠키를 설정하는 것도 가능하다.
다음 코드를 보면 tasty_cookie와 gross_cookie가 설정되어있지만 /cookies path에 해당하는 쿠키만 적용된 것을 확인할 수 있다.

코드

import requests
jar = requests.cookies.RequestsCookieJar()
jar.set('tasty_cookie', 'yum', domain = 'httpbin.org', path = '/cookies')
jar.set('gross_cookie', 'blech', domain = 'httpbin.org', path = '/elsewhere')
r = requests.get('https://httpbin.org/cookies', cookies = jar)
print(r.json())

>> {'cookies': {'tasty_cookie': 'yum'}}

응답 상태코드 확인방법

r.ststus_code 명령으로 응답 상태코드를 확인할 수 있다.
r.raise_for_status() 명령을 사용하면 제대로 된 응답을 받지 못한 경우 에러를 발생(raise)시킬 수 있다.

코드

import requests
r = requests.get('https://httpbin.org/status/200')
print(r.status_code)
r.raise_for_status()
r = requests.get('https://httpbin.dev/redirect/1', allow_redirects = False)
print(r.status_code)
r.raise_for_status()
r = requests.get('https://httpbin.org/status/404')
print(r.status_code)
r.raise_for_status()

>> 200
302
404
Traceback (most recent call last):
  File "c:\seolpyo\example.py", line 10, in <module>
    r.raise_for_status()
  File "C:\seolpyo\Lib\site-packages\requests\models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404

r.raise_for_status()

r.raise_for_status() 명령이 상태코드 200이 아닌 경우 raise하는 함수라고 생각할 수도 있는데, 그렇지 않다.
위 코드를 보면 302 리다이렉트가 발생하는 페이지에서 에러가 발생하지 않고, 404 응답에만 에러가 발생한 것을 확인할 수 있다.

그 이유는 상태코드값이 400 이상이고 600 미만인 경우에만 raise하도록 설정되어있기 때문이다.
다음은 raise_for_status() 함수 부분만 가져온 것이다. 전체 코드는 이 링크에서 확인할 수 있다.

코드

# requests.modeles.Response.raise_for_status
def raise_for_status(self):
        try:
            reason = self.reason.decode("utf-8")
        except UnicodeDecodeError:
            reason = self.reason.decode("iso-8859-1")
    else:
        reason = self.reason
    if 400 <= self.status_code < 500:
        http_error_msg = (
            f"{self.status_code} Client Error: {reason} for url: {self.url}"
        )
    elif 500 <= self.status_code < 600:
        http_error_msg = (
            f"{self.status_code} Server Error: {reason} for url: {self.url}"
        )
    if http_error_msg:
        raise HTTPError(http_error_msg, response=self)

redirect되었는지 확인하는 방법

리다이렉트가 설정된 url의 경우 보통 301 redirect를 하게 된다.
이 경우 requests 모듈은 리다이렉트로 이동된 최종 url로 접속하게 되는데, 리다이렉트가 되었는지 확인하는 방법은 2가지가 있다.

  1. 리다이렉트를 비허용한 상태에서 url에 응답 요청 보내기
  2. requests 객체의 이동 기록을 조회하기

권장되는 것은 2안이며, 1안은 그냥 이런게 가능하다는 것을 상기시키기 위해 포함했다.
리다이렉트를 비허용하는 방법은 allow_redirects 인수를 False로 선언하면 된다.

리다이렉트를 비허용한 상태에서는 r.ststus_code를 확인하면 리다이렉트가 이루어지는 url인지 확인이 가능하고,
리다이렉트를 허용한 상태에서는 r.history를 조회해 리다이렉트가 이루어졌었는지를 확인할 수 있다.

코드

import requests
r = requests.get('http://github.com', allow_redirects = False)
print(r.url)
print(r.status_code)
print(r.history)
r = requests.get('http://github.com')
print(r.url)
print(r.status_code)
print(r.history)

>> http://github.com/
301
[]
https://github.com/
200
[<Response [301]>]

응답 대기시간 설정방법

요청을 보내는 서버의 상태에 따라 응답시간이 다르기 마련이다.
requests의 기본 대기시간은 무한(None)이지만, 필요한 경우 timeout 인수를 선언해 대기시간을 설정하는 것이 가능하다.
timeout은 Connection timed out과 Read timed out 2개를 설정할 수 있는데, 2개의 int 또는 float을 포함한 tuple 객체를 timeout 인수에 전달하면 된다.
0번 index 값이 Connection timed out, 1번 index 값이 Read timed out 값이 된다.

다음은 2초의 응답 대기시간을 가지는 url에 timeout을 설정했을 때와 아닐 때의 예시 코드이다.
1번째 요청에서는 2초 대기 후 응답을 받는데 아무런 문제가 없지만, 2번째 요청에서는 대기시간(1초)동안 응답이 오지않아 에러가 발생했다.

코드

import requests
r = requests.get('https://httpstat.us/200?sleep=2000')
print(r.status_code)
r = requests.get('https://httpstat.us/200?sleep=2000', timeout = 1)
print(r.status_code)

>> 200
Traceback (most recent call last):
  File "C:\seolpyo\Lib\site-packages\urllib3\connectionpool.py", line 536, in _make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
  File "C:\seolpyo\Lib\site-packages\urllib3\connection.py", line 454, in getresponse
    httplib_response = super().getresponse()
                       ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\http\client.py", line 1375, in getresponse
    response.begin()
  File "C:\Program Files\Python311\Lib\http\client.py", line 318, in begin
    version, status, reason = self._read_status()
                              ^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\http\client.py", line 279, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\socket.py", line 706, in readinto
    return self._sock.recv_into(b)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\ssl.py", line 1278, in recv_into
    return self.read(nbytes, buffer)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\ssl.py", line 1134, in read
    return self._sslobj.read(len, buffer)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TimeoutError: The read operation timed out

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\seolpyo\Lib\site-packages\requests\adapters.py", line 486, in send


공감 : 0







white.seolpyo.com