콘텐츠로 건너뛰기
Home » Requests를 사용해서 파일 다운로드 경과를 표시하기

Requests를 사용해서 파일 다운로드 경과를 표시하기

Requests는 파이썬 사용자들이 가장 많이 사용하는 HTTP 통신용 라이브러리일 것이다. 비록 파이썬 표준 라이브러리에 포함되지는 않았지만, 공식 문서에서조차 ‘Requests를 쓰는게 편리하니 추천한다’고 언급할 정도이니… 사실, 파이썬의 역사는 우리의 생각보다 훨씬 오래되었고, 그 와중에 HTTP와 관련된 공식 라이브러리에도 몇 가지 변화가 계속 있어왔다. 최초로 HTTP를 사용하여 데이터를 가져오는 기능은 urllib 에서 구현된 urlopen() 함수였는데, 이 때의 urlopen()은 단순히 open() 함수를 HTTP 너머로 작동하게 하는 것 이상도 이하도 아닌 간단한 구현체였다. HTTP와 관련된 명세는 제법 방대하고 명세 중에서 쓸만한 기능을 제작하기 위해 필요한 좀 더 좋은 구현체가 필요해졌고, 그에 대한 대안으로 제시된 것이 urllib2 이다.

urllib2 를 사용하는 시간이 쌓이면서 자연스럽게 본래의 urllib 은 제거되었고, 파이썬 3로 넘어오면서 urllib2는 방대한 내부 구조를 정리하여 다시 몇 개의 서브 패키지로 쪼개어졌다. 이렇게 정리되면서 본래의 이름인 urllib 을 되찾게 되었다. URL 상의 파일을 ‘여는’ 함수는 여전히 urlopen() 이지만, 이 함수는 이제 urllib.request.urlopen 이라는 곳에 위치하게 된다.

이와 별개로 urllib2 에서도 구현되지 않은 스펙에 대한 보충이나, 더 나은 디자인을 적용하는 등 다음 스텝으로 기능적인 완성도를 push하는 프로젝트가 있었는데, 이 프로젝트가 바로 urllib3 이다. requests 는 이 urllib3 의 기반 위에서 “인간이 이해하고 사용하기 쉬운” 인터페이스를 만들어 제공한다는 목표 아래에 진행되는 프로젝트이다.

따라서 통신의 중간과정에서 일어나는 여러 이벤트나 상황을 자동으로 제어하고, 사용자(개발자)는 최소한의 입력과 출력만 다루면 되도록 해 놓은 것이다. 그래서 requests.get() 이나 requests.post() 만 사용하면 서버와 통신하고 그 결과도 척척 손쉽게 가져와서 사용할 수 있는 것이다.

그런데 이렇게 “쓰기 쉽게” 만들어진 도구들은 그 복잡한 하부구조를 감추는 경향이 있기 때문에, 특정한 상황에서 특정한 방식으로 작동하는 방법을 일일이 지정하기가 곤란한 경우가 있다. 예를들어 파일 다운로드에 지금 몇 % 쯤 다운로드 받았는지를 보여주고 싶다거나 하는 경우 말이다.

requests.get() 함수는 리턴하는 시점에 HTTP 응답의 페이로드까지 모두 다운로드 받아서 필요한 후처리까지 완료하기 때문에, 파일 다운로드 경과를 보여주려 하면 곧바로 100%가 되어버린다. 만약 용량이 큰 파일을 요청하여 다운로드 받는다면 그만큼 시간이 오래 걸리는데 그 기간 동안 프로그램이 응답이 없는 상태가 될 수도 있다.

따라서 요청을 보낸 후 헤더만 미리 받아서 처리하고, 페이로드는 따로 다운로드 하고 싶다면 (이 방식은 기존의 urlopen() 을 통해서 데이터를 가져오던 방식과 비슷하다.) .get() , .post() 함수에 stream=True 옵션을 전달해서 호출한다. 이후 응답객체의 iter_content() 메소드를 사용하면 반복문을 통해서 특정 크기의 데이터 조각은 순차적으로 읽어올 수 있다.

따라서 아래 코드를 사용하면, 파일을 다운로드 받으면서 그 경과율을 출력하여 사용자로 하여금 몇 초간 프로그램이 멈추지 않고 계속해서 결과에 반응하는 것처럼 보이게 만들어주게 된다.

import requests

url = "https://...."
filename = "myfile.zip"

with open(filename, 'wb') as f:
  res = requests.get(url, stream=True)
  r_bytes : int = 0
  t_bytes : int = int(res.headers.get('Content-Length', 0))
  for chunk in res.iter_content(1024):
    f.write(chunck)
    r_bytes += len(chunk)
    print(f"\r{r_bytes / t_bytees : .2%}", end="")
print("\rDone.")