ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Requests 알아보기
    Project 2023. 6. 28. 15:45
    728x90

    오늘은 웹 크롤러를 만들기 전에, Python의 requests에 알아보자.

    Requests 공식 문서는 다음과 같다.

    https://requests.readthedocs.io/en/latest/

     

    Requests: HTTP for Humans™ — Requests 2.31.0 documentation

    Requests: HTTP for Humans™ Release v2.31.0. (Installation) Requests is an elegant and simple HTTP library for Python, built for human beings. Behold, the power of Requests: >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.

    requests.readthedocs.io

    Requests는 다양한 기능을 제공한다.

    • Keep-Alive & Connection Pooling
    • International Domains and URLs
    • Sessions with Cookie Persistence
    • Browser-style SSL Verification
    • Automatic Content Decoding
    • Basic/Digest Authentication
    • Elegant Key/Value Cookies
    • Automatic Decompression
    • Unicode Response Bodies
    • HTTP(S) Proxy Support
    • Multipart File Uploads
    • Streaming Downloads
    • Connection Timeouts
    • Chunked Requests
    • .netrc Support

    다음과 같은 기능을 제공한다고 하는데, 차근차근 알아보자.

     

    * 기본 코드

    import requests # 모듈 불러오기
    
    r = requests.get('https://api.github.com/events') # 웹 페이지 가져오기

    requests의 실행 코드이다. 여기서 r은 get 메서드 내 uri에 보관된 정보를 담은 객체라고 보면 된다.

    Requests의 API는 모든 HTTPS의 요청은 명확하다는 것을 보여준다. 그 예로, HTTP  요청은 다음과 같이 표현할 수 있다.

    r = requests.post('https://httpbin.org/post', data={'key': 'value'}) # 데이터 삽입
    
    r = requests.put('https://httpbin.org/put', data={'key':'value'}) # 데이터 수정
    
    r = requests.delete('https://httpbin.org/delete') # 데이터 삭제
    
    r = requests.head('https://httpbin.org/get')
    
    r = requests.options('https://httpbin.org/get')

    이처럼 requests를 이용하면 Python 환경에서 HTTP 요청 구현이 용이하다.

    * Passing Parameters In URLs

    URL의 쿼리 문자열 중 몇몇 종류의 데이터만 보고 싶을 때가 있다. 이때 직접 Url을 만든다면, 이 데이터는 ? 표시가 붙은 Url 내 키/값 쌍으로 주어질 것이다(Ex. httpbin.org/get??key=val). Requests는 이러한 쿼리를 'param' 키워드 아규먼트를 사용하며 문자열 딕셔너리 형으로 제공한다.  예시로, key1 = value1과 key2 = value2를 httpbin.org/get에서 보고 싶을 때, 다음과 같은 코드를 사용한다.

     

    payload = {'key1' : 'value1', 'key2' : 'value2'}
    r = requests.get('https:"//httpbin.org/get', params=payload)
    
    # 인코딩된 URL 확인
    print(r.url)
    
    https://httpbin.org/get?key2=value2&key1=value1

    리스트로 두개의 값을 불러올 수도 있다.

    payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
    
    r = requests.get('https://httpbin.org/get', params=payload)
    
    # 인코딩된 URL 확인
    print(r.url)
    https://httpbin.org/get?key1=value1&key2=value2&key2=value3

     

    * Response Content

    Request는 서버로부터 온 컨텐츠(응답)를 자동적으로 디코딩한다. 대부분의 유니코드 문자 집합들은 자연스럽게 디코딩된다.

    만약 우리가 request를 만들면, Requests는 HTTP header에 기반한 인코딩된 응답을 추론한다. Requests로부터 추론된 text는 r.text에 접근하면 볼 수 있다. 또한 Requests의 인코딩 방식에 대해서도 알 수 있는데, r.encoding에 접근하면 현재 방식을 파악할 수 있고 변경도 가능하다.

    # Requests 현재 인코딩 방식 확인
    r.encoding
    'utf-8'
    
    # Requests 인코딩 방식 변경
    r.encoding = 'ISO-8859-1'

    이 같은 특성을 이용해, 인코딩된 컨텐츠에 대해 특별한 로직을 적용할 수도 있다. 일례로 HTML과 XML의 경우 body 내 content를 세세히 파악해야 하는 경우가 있다. 이럴 때 r.content를 입력하여 인코딩을 확인하고, 그에 맞게 r.encoding을 지정하면 올바른 인코딩 방식으로 r.text를 사용할 수 있다. 

    필요하다면 Requests에서 커스텀 인코딩 기능을 사용할 수도 있다. 나만의 인코딩을 만든뒤 codecs 모듈을 이용해 등록한다면, r.encoding의 값으로 사용할 수 있고 Requests는 그에 해당하는 디코딩된 내용을 제공할 것이다.

     

    * Binary Response Content

    또한 byte 단위로 텍스트가 아닌 요청들에도 접근할 수 있다.

    from PIL import Image
    from io import BytesIO
    
    i = Image.open(BytesIO(r.content))

     

    * JSON Response Content

    Requests 모듈은 Json 데이터를 다룰 수도 있다.

    import requests
    
    r = requests.get('https://api.github.com/events')
    r.json()
    [{'repository': {'open_issues': 0, 'url': 'https://github.com/...

    JSON 디코딩이 실패하면, r.json() 은 Exception을 발생시킨다. 예를 들어 204 Response(내용 없음)을 받았거나, 유효하지 않은 JSON 형식이 포함된 Response일 때 requests.exceptions.JSONDecodeError를 발생시킨다. 이 예외는 다양한 파이썬 버전과 json 암호화 라이브러리들로부터 만들어지는 다양한 예외들에 대해 상호 운용성을 제공한다(유연함을 가진다고 얘기하는것 같은데..).

    이 때 주의해야 할 점은, 성공적으로 r.json()의 결과가 나왔다고 해서 반드시 response를 성공적으로 가져온 것은 아니라는 점이다. 왜냐하면 몇몇 서버들은 실패한 response(예시: HTTP 500)에 대한 응답으로 json 객체를 전송해주기도 하기 때문이다. 요청의 성공 여부를 파악하고 싶다면 r.raise_for_status() 나 r.status_code를 확인하면 된다.

    * Raw Response Content

    r = requests.get('https://api.github.com/events', stream=True)
    
    r.raw
    <urllib3.response.HTTPResponse object at 0x101194810>
    
    r.raw.read(10)
    b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

    r.raw에 접근하면, 서버로부터 온 socket response를 얻을 수 있다. 이를 위해 첫 요청(get 메서드 사용 시)을 할 때 stream=True 인자를 추가하여 입력해야 한다.

    * 더 복잡한 POST 요청들

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

    HTML과 같은 형태를 보내고 싶다면, post 메서드 내에 data argument를 지정하여 만들 수 있다. 이 argument를 이용해 튜플 리스트나 딕셔너리를 통한 다중값을 지정할 수도 있다.   

    payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
    r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
    payload_dict = {'key1': ['value1', 'value2']}
    r2 = requests.post('https://httpbin.org/post', data=payload_dict)
    print(r1.text)
    {
      ...
      "form": {
        "key1": [
          "value1",
          "value2"
        ]
      },
      ...
    }
    r1.text == r2.text
    True
    import json
    
    url = 'https://api.github.com/some/endpoint'
    payload = {'some': 'data'}
    
    r = requests.post(url, data=json.dumps(payload))

    위의 코드는 Content-Type header를 추가하지 않는다. 만약 header가 필요하고 dict를 인코딩하고 싶지 않다면, json 파라미터를 사용해 자동으로 인코딩하게끔 할 수도 있다. json 파라미터는 데이터나 파일들의 통과 여부를 무시한다는 점에 주의하자.

    url = 'https://api.github.com/some/endpoint'
    payload = {'some': 'data'}
    
    r = requests.post(url, json=payload)

    * Multipart 인코딩 파일 

    url = 'https://httpbin.org/post'
    files = {'file': open('report.xls', 'rb')}
    
    r = requests.post(url, files=files)
    r.text
    {
      ...
      "files": {
        "file": "<censored...binary...data>"
      },
      ...
    }

    requests는 멀티파트 인코딩 파일을 간단하게 POST하게끔 해준다.

    파일 이름과 내용, 헤더를 명시하여 지정하는 것이 가능하며, 문자열로 전송하여 파일로 만들 수 있다.

    # 명시적 지정
    url = 'https://httpbin.org/post'
    files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
    
    r = requests.post(url, files=files)
    r.text
    {
      ...
      "files": {
        "file": "<censored...binary...data>"
      },
      ...
    }
    
    # 문자열로 파일 만들기
    url = 'https://httpbin.org/post'
    files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
    
    r = requests.post(url, files=files)
    r.text
    {
      ...
      "files": {
        "file": "some,data,to,send\\nanother,row,to,send\\n"
      },
      ...
    }

    * Response Status Code

    간단한 방법으로 response의 상태를 알 수 있다.

    r = requests.get("https://httpin.org/get")
    r.status_code # 200
    
    r.status_code == requests.codes.ok # TRUE / FALSE
    
    # 에러를 만들고 싶을 때
    
    bad_r = requests.get("https://httpbin.org/status/404")
    bad_r.status_code # 404
    
    bad_r.raise_for_status()
    Traceback (most recent call last):
      File "requests/models.py", line 832, in raise_for_status
        raise http_error
    requests.exceptions.HTTPError: 404 Client Error

    status_code가 200일 경우 raise_for_status는 None을 출력한다.

    Response Header

    Python Dictionary를 통해 서버의 반응을 볼 수도 있다.

    r.headers
    {
        'content-encoding': 'gzip',
        'transfer-encoding': 'chunked',
        'connection': 'close',
        'server': 'nginx/1.0.4',
        'x-runtime': '148ms',
        'etag': '"e1ca502697e5c9317743dc078f67693f"',
        'content-type': 'application/json'
    }

    * Cookies

    url = 'http://example.com/some/cookie/setting/url'
    r = requests.get(url)
    
    r.cookies['example_cookie_name']
    'example_cookie_value'

    Server로부터 받은 response가 쿠키를 포함하고 있다면, 위와 같이 requests를 이용해 쉽게 접근할 수 있다.

    반대로, Server에 쿠키를 전송하고 싶다면 cookies 파라미터를 사용해 쿠키를 보낼 수 있다.

    url = 'https://httpbin.org/cookies'
    cookies = dict(cookies_are='working')
    
    r = requests.get(url, cookies=cookies)
    r.text
    '{"cookies": {"cookies_are": "working"}}'

    쿠키는 RequestsCookieJar라는 곳에서 나온다. dict 처럼 기능을 하지만 다양한 도메인과 경로에서 적합한 더 완벽한 인터페이스라고 할 수 있다.

    jar = requests.cookies.RequestsCookieJar()
    jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
    jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
    url = 'https://httpbin.org/cookies'
    r = requests.get(url, cookies=jar)
    r.text
    '{"cookies": {"tasty_cookie": "yum"}}'

    * History

    - Response 객체의 리다이렉션을 추적

    - Response.history list: Response를 완성시키기 위해 만들어진 Response 객체들을 포함

    - allow_redirects arguement를 통해 redirection 제어 가능

     

    * Timeout

    - timeout 파라미터로 response 대기 시간 설정 가능

    requests.get('https://github.com/', timeout=0.001)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    
    requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80)
    : Request timed out. (timeout=0.001)

     

    [Errors and Exception]

    1. 네트워크 문제 발생시 (DNS 접속 실패, 연결 차단 등) : ConnectionError

    2. request timeout : Timeout Exception

    3. HTTP request가 성공적이지 않을 때, Response.raise_for_status()는 HTTPError 발생

    4. request가 리디렉션 한계치를 초과했을 때 : TooManyRedirects Exception 발생

    728x90

    'Project' 카테고리의 다른 글

    웹 크롤러 설계  (0) 2023.07.12
    [Parsing] Scrapy를 활용하여 웹 스크래핑 하기  (0) 2023.07.09
    PySpider  (0) 2023.07.08
    웹 크롤링 데이터를 MySQL에 저장하기  (0) 2023.07.03
    Client와 Server, REST API, JSON  (0) 2023.06.26
Designed by Tistory.