-
Requests 알아보기Project 2023. 6. 28. 15:45728x90
오늘은 웹 크롤러를 만들기 전에, Python의 requests에 알아보자.
Requests 공식 문서는 다음과 같다.
https://requests.readthedocs.io/en/latest/
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