为什么请求停止?

Why does requests stop?

我正在使用 tkinter 和 requests 制作这个应用程序,它应该像一个下载管理器。我正在使用请求,最近我发现 requests.get(url) 函数中的 stream 关键字参数能够在下载内容时写下内容。我的问题是,当用户下载多个文件或仅下载大文件时,请求似乎就停止了。奇怪的是它不会像预期的行为那样引发错误。为什么会这样?我该如何解决这个问题?没有GUI的简单版下载(我发现这个具体url有点问题):

import requests
import time

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
headers = requests.head(url, headers={'accept-encoding': ''}).headers
print(headers)
r = requests.get(url, allow_redirects=True, stream=True)
# headers = r.headers
name = url.split('/')[-1].split('.')[0]
print(name)
format_name = '.' + headers['Content-Type'].split('/')[1]
file_size = int(headers['Content-Length'])
downloaded = 0
print(name + format_name)
start = last_print = time.time()
with open(name + format_name, 'wb') as fp:
    for chunk in r.iter_content(chunk_size=4096):
        downloaded += fp.write(chunk)
        now = time.time()
        if now - last_print >= 1:
            pct_done = round(downloaded / file_size * 100)
            speed = round(downloaded / (now - start) / 1024)
            print(f"Download {pct_done} % done, avg speed {speed} kbps")
            last_print = time.time()

更新: 我检查了另外两个可能有答案的 Whosebug 问题,但显然还有一些问题没有得到解答(link:Streaming download large file with python-requests interrupting, link: What exactly is Python's file.flush() doing?) .我尝试使用提到的两个功能作为问题的解决方案,但一些下载仍然停止。新版代码:

import requests
import time
import os

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
headers = requests.head(url, headers={'accept-encoding': ''}).headers
print(headers)
r = requests.get(url, allow_redirects=True, stream=True)
name = url.split('/')[-1].split('.')[0]
print(name)
format_name = '.' + headers['Content-Type'].split('/')[1]
file_size = int(headers['Content-Length'])
downloaded = 0
print(name + format_name)
start = last_print = time.time()
with open(name + format_name, 'wb') as fp:
    for chunk in r.iter_content(chunk_size=4096):
        downloaded += fp.write(chunk)
        # Added the 'flush' and 'fsync' function as mentioned in the issues
        fp.flush()
        os.fsync(fp.fileno())
        now = time.time()
        if now - last_print >= 1:
            pct_done = round(downloaded / file_size * 100)
            speed = round(downloaded / (now - start) / 1024)
            print(f"Download {pct_done} % done, avg speed {speed} kbps")
            last_print = time.time()

即使添加了这两个功能,请求似乎也停止了。我怀疑 requests 有时无法保持连接,因为在一天中的某些时候,当我的互联网不那么强大时,这个问题发生得最多,但我还是不明白为什么它不会引发像 [=27 这样的错误=] 库。如果不是这样,那我该如何解决呢?

这个问题可能出在服务器端,如果您的客户端连接速度太慢,服务器可能会关闭请求。在慢速移动连接上使用 python 下载大文件时,我遇到过这种情况。我建议修改服务器的代码库或访问更好的连接,尽管之前的连接是更好的长期解决方案。

祝你好运

我做了3处修改,其中只有一处直接影响结果。

  1. 我添加了 r.raise_for_status() 来检查是否有任何错误,这是一个很好的做法。
  2. 我使用 name = url.split('/')[-1].split('?')[0] 作为文件名,结果是 'a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4',可能是您想要的,因为它具有正确的扩展名。
  3. 我将 chunk_size 增加了 64 倍,这可能就是诀窍。
import requests
import time

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
headers = requests.head(url, headers={'accept-encoding': ''}).headers
print(headers)
r = requests.get(url, allow_redirects=True, stream=True)
r.raise_for_status() # check for errors
# headers = r.headers
name = url.split('/')[-1].split('?')[0]
print(name)
file_size = int(headers['Content-Length'])
downloaded = 0
start = last_print = time.time()
with open(name, 'wb') as fp:
    for chunk in r.iter_content(chunk_size=4096 * 64):
        downloaded += fp.write(chunk)
        now = time.time()
        if now - last_print >= 1:
            pct_done = round(downloaded / file_size * 100)
            speed = round(downloaded / (now - start) / 1024)
            print(f"Download {pct_done} % done, avg speed {speed} kbps")
            last_print = time.time()

打印:

{'Accept-Ranges': 'bytes', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 'Access-Control-Allow-Origin': '*', 'Access-Control-Expose-Headers': 'Server,range,Content-Length,Content-Range', 'Cache-Control': 'max-age=8640000', 'Content-Length': '101751914', 'Content-Type': 'video/mp4', 'Date': 'Sat, 21 Nov 2020 18:04:48 GMT', 'Etag': '"5e379fa7-6109c6a"', 'Expires': 'Mon, 01 Mar 2021 18:04:48 GMT', 'Last-Modified': 'Sun, 19 Nov 2000 08:52:00 GMT'}
a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4
Download 0 % done, avg speed 249 kbps
Download 3 % done, avg speed 1386 kbps
Download 11 % done, avg speed 3443 kbps
Download 19 % done, avg speed 4525 kbps
Download 28 % done, avg speed 5399 kbps
Download 38 % done, avg speed 6218 kbps
Download 50 % done, avg speed 6997 kbps
Download 63 % done, avg speed 7763 kbps
Download 78 % done, avg speed 8463 kbps
Download 89 % done, avg speed 8733 kbps

我应该补充一点,它也适用于 4096 的原始 chunk_size,尽管 慢得多 。老实说,我不能给你一个确切的原因,为什么它会为你绞死,但肯定没有理由不尝试使用我建议的更大(但不是不合理的大)chunk_size

更新

我已多次尝试 运行ning 代码,发现性能差异很大。尽管指定了 chunk_size,代码似乎最终以更小的块迭代。这是一个示例 运行,它仍然完成了一点:

{'Accept-Ranges': 'bytes', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 'Access-Control-Allow-Origin': '*', 'Access-Control-Expose-Headers': 'Server,range,Content-Length,Content-Range', 'Cache-Control': 'max-age=8640000', 'Content-Length': '101751914', 'Content-Type': 'video/mp4', 'Date': 'Sat, 21 Nov 2020 19:14:13 GMT', 'Etag': '"5e379fa7-6109c6a"', 'Expires': 'Mon, 01 Mar 2021 19:14:13 GMT', 'Last-Modified': 'Sun, 19 Nov 2000 08:52:00 GMT'}
a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4
Download 0 % done, avg speed 243 kbps
Download 3 % done, avg speed 1240 kbps
Download 12 % done, avg speed 3803 kbps
Download 19 % done, avg speed 4484 kbps
Download 24 % done, avg speed 4601 kbps
Download 29 % done, avg speed 4615 kbps
Download 33 % done, avg speed 4503 kbps
Download 37 % done, avg speed 4411 kbps
Download 40 % done, avg speed 4126 kbps
Download 42 % done, avg speed 3907 kbps
Download 44 % done, avg speed 3674 kbps
Download 45 % done, avg speed 3462 kbps
Download 46 % done, avg speed 3238 kbps
Download 47 % done, avg speed 3061 kbps
Download 47 % done, avg speed 2913 kbps
Download 48 % done, avg speed 2753 kbps
Download 49 % done, avg speed 2613 kbps
Download 49 % done, avg speed 2504 kbps
Download 50 % done, avg speed 2396 kbps
Download 50 % done, avg speed 2286 kbps
Download 51 % done, avg speed 2190 kbps
Download 52 % done, avg speed 2108 kbps
Download 52 % done, avg speed 2035 kbps
Download 53 % done, avg speed 1975 kbps
Download 53 % done, avg speed 1907 kbps
Download 54 % done, avg speed 1859 kbps
Download 55 % done, avg speed 1831 kbps
Download 56 % done, avg speed 1796 kbps
Download 57 % done, avg speed 1759 kbps
Download 58 % done, avg speed 1724 kbps
Download 60 % done, avg speed 1693 kbps
Download 60 % done, avg speed 1663 kbps
Download 61 % done, avg speed 1633 kbps
Download 62 % done, avg speed 1605 kbps
Download 63 % done, avg speed 1580 kbps
Download 64 % done, avg speed 1555 kbps
Download 65 % done, avg speed 1536 kbps
Download 65 % done, avg speed 1515 kbps
Download 66 % done, avg speed 1496 kbps
Download 67 % done, avg speed 1476 kbps
Download 68 % done, avg speed 1456 kbps
Download 69 % done, avg speed 1438 kbps
Download 70 % done, avg speed 1421 kbps
Download 70 % done, avg speed 1405 kbps
Download 71 % done, avg speed 1391 kbps
Download 72 % done, avg speed 1372 kbps
Download 73 % done, avg speed 1357 kbps
Download 73 % done, avg speed 1344 kbps
Download 74 % done, avg speed 1330 kbps
Download 75 % done, avg speed 1320 kbps
Download 76 % done, avg speed 1310 kbps
Download 77 % done, avg speed 1297 kbps
Download 78 % done, avg speed 1289 kbps
Download 79 % done, avg speed 1284 kbps
Download 80 % done, avg speed 1279 kbps
Download 81 % done, avg speed 1275 kbps
Download 83 % done, avg speed 1272 kbps
Download 84 % done, avg speed 1271 kbps
Download 85 % done, avg speed 1270 kbps
Download 87 % done, avg speed 1269 kbps
Download 88 % done, avg speed 1265 kbps
Download 89 % done, avg speed 1260 kbps
Download 89 % done, avg speed 1252 kbps
Download 90 % done, avg speed 1244 kbps
Download 91 % done, avg speed 1237 kbps
Download 92 % done, avg speed 1230 kbps
Download 92 % done, avg speed 1224 kbps
Download 94 % done, avg speed 1214 kbps
Download 95 % done, avg speed 1204 kbps
Download 95 % done, avg speed 1195 kbps
Download 96 % done, avg speed 1186 kbps
Download 97 % done, avg speed 1177 kbps
Download 98 % done, avg speed 1168 kbps
Download 98 % done, avg speed 1160 kbps
Download 99 % done, avg speed 1151 kbps
Download 100 % done, avg speed 1144 kbps

版本使用urllib3

import urllib3
import time

http = urllib3.PoolManager()
url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
r = http.request('HEAD', url)
headers = r.headers
print(headers)
r = http.request('GET', url, preload_content=False)
name = url.split('/')[-1].split('?')[0]
print(name)
file_size = int(headers['Content-Length'])
downloaded = 0
start = last_print = time.time()
with open(name, 'wb') as fp:
    for chunk in r.stream(4096 * 64):
        downloaded += fp.write(chunk)
        now = time.time()
        if now - last_print >= 1:
            pct_done = round(downloaded / file_size * 100)
            speed = round(downloaded / (now - start) / 1024)
            print(f"Download {pct_done} % done, avg speed {speed} kbps")
            last_print = time.time()
    r.release_conn()

版本使用urllib

import time
from urllib.request import urlopen

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
response = urlopen(url)
file_size = int(response.getheader('Content-Length'))
print('File size =', file_size)
name = url.split('/')[-1].split('?')[0]
print(name)
downloaded = 0
start = last_print = time.time()
with open(name, 'wb') as fp:
    while True:
        chunk = response.read(4096 * 64)
        if not chunk:
            break
        downloaded += fp.write(chunk)
        now = time.time()
        if now - last_print >= 1:
            pct_done = round(downloaded / file_size * 100)
            speed = round(downloaded / (now - start) / 1024)
            print(f"Download {pct_done} % done, avg speed {speed} kbps")
            last_print = time.time()

版本使用 urlliburlretrieve

from urllib.request import urlretrieve
import time


def report_hook(numblocks, blocksize, file_size):
    global start, last_print
    now = time.time()
    if now - last_print >= 1:
        downloaded = numblocks * blocksize
        pct_done = round(downloaded / file_size * 100)
        speed = round(downloaded / (now - start) / 1024)
        print(f"Download {pct_done} % done, avg speed {speed} kbps")
        last_print = now


url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
name = url.split('/')[-1].split('?')[0]
print(name)
start = time.time()
last_print = start
urlretrieve(url, name, report_hook)

版本使用 wget

wget 非常健壮。如果你在 Windows,你可以下载一个版本 here。第一个版本从 wget 获取管道 stderr 输出并显示每一行,如下所示:

--2020-11-24 09:20:02--  https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY
Resolving aspb2.cdn.asset.aparat.com (aspb2.cdn.asset.aparat.com)... 91.229.46.35
Connecting to aspb2.cdn.asset.aparat.com (aspb2.cdn.asset.aparat.com)|91.229.46.35|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 101751914 (97M) [video/mp4]
Saving to: 'a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4'

     0K ........ ........ ........ ........ ........ ........  3%  509K 3m9s
  3072K ........ ........ ........ ........ ........ ........  6% 1.36M 2m5s
  6144K ........ ........ ........ ........ ........ ........  9% 1.94M 96s
  9216K ........ ........ ........ ........ ........ ........ 12% 1.14M 88s
 12288K ........ ........ ........ ........ ........ ........ 15%  931K 86s
 15360K ........ ........ ........ ........ ........ ........ 18%  970K 83s
 18432K ........ ........ ........ ........ ........ ........ 21% 1.28M 77s
 21504K ........ ........ ........ ........ ........ ........ 24% 1.90M 69s
 24576K ........ ........ ........ ........ ........ ........ 27% 2.64M 62s
 27648K ........ ........ ........ ........ ........ ........ 30% 2.87M 56s
 30720K ........ ........ ........ ........ ........ ........ 34% 2.07M 51s
 33792K ........ ........ ........ ........ ........ ........ 37% 1.30M 49s
 36864K ........ ........ ........ ........ ........ ........ 40%  713K 49s
 39936K ........ ........ ........ ........ ........ ........ 43%  731K 49s
 43008K ........ ........ ........ ........ ........ ........ 46%  663K 48s
 46080K ........ ........ ........ ........ ........ ........ 49%  657K 48s
 49152K ........ ........ ........ ........ ........ ........ 52% 1.01M 45s
 52224K ........ ........ ........ ........ ........ ........ 55% 1.76M 41s
 55296K ........ ........ ........ ........ ........ ........ 58% 1.49M 37s
 58368K ........ ........ ........ ........ ........ ........ 61% 1.32M 34s
 61440K ........ ........ ........ ........ ........ ........ 64% 1.20M 31s
 64512K ........ ........ ........ ........ ........ ........ 68%  966K 29s
 67584K ........ ........ ........ ........ ........ ........ 71%  977K 26s
 70656K ........ ........ ........ ........ ........ ........ 74%  857K 24s
 73728K ........ ........ ........ ........ ........ ........ 77%  803K 21s
 76800K ........ ........ ........ ........ ........ ........ 80%  753K 19s
 79872K ........ ........ ........ ........ ........ ........ 83%  842K 16s
 82944K ........ ........ ........ ........ ........ ........ 86% 1.14M 13s
 86016K ........ ........ ........ ........ ........ ........ 89% 1.79M 10s
 89088K ........ ........ ........ ........ ........ ........ 92% 2.21M 7s
 92160K ........ ........ ........ ........ ........ ........ 95% 2.19M 4s
 95232K ........ ........ ........ ........ ........ ........ 98% 2.45M 1s
 98304K ........ ........                                    100% 2.54M=88s

2020-11-24 09:21:31 (1.10 MB/s) - 'a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4' saved [101751914/101751914]

来源:

import subprocess
import time

def run_wget(url, outfile):
    cmd = ['wget', '--progress=dot:mega', '-O', outfile, url]
    p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
    for stderr_line in iter(p.stderr.readline, ""):
        yield stderr_line
    p.stderr.close()
    return_code = p.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
name = url.split('/')[-1].split('?')[0]
print(name)
start = time.time()
for line in run_wget(url, name):
    print(line, end='')
print('Total time:', time.time() - start)

第二个版本,处理输出以生成类似于其他解决方案的列表:

a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4
file size = 101751914
Download 3% done, avg speed 384 kbps
Download 6% done, avg speed 570 kbps
Download 9% done, avg speed 672 kbps
Download 12% done, avg speed 703 kbps
Download 15% done, avg speed 732 kbps
Download 18% done, avg speed 784 kbps
Download 21% done, avg speed 857 kbps
Download 24% done, avg speed 895 kbps
Download 27% done, avg speed 884 kbps
Download 30% done, avg speed 868 kbps
Download 34% done, avg speed 885 kbps
Download 37% done, avg speed 818 kbps
Download 40% done, avg speed 818 kbps
Download 43% done, avg speed 849 kbps
Download 46% done, avg speed 885 kbps
Download 49% done, avg speed 920 kbps
Download 52% done, avg speed 929 kbps
Download 55% done, avg speed 937 kbps
Download 58% done, avg speed 946 kbps
Download 61% done, avg speed 957 kbps
Download 64% done, avg speed 878 kbps
Download 68% done, avg speed 696 kbps
Download 71% done, avg speed 611 kbps
Download 74% done, avg speed 564 kbps
Download 77% done, avg speed 550 kbps
Download 78% done, avg speed 543 kbps
Download 80% done, avg speed 526 kbps
Download 83% done, avg speed 534 kbps
Download 86% done, avg speed 542 kbps
Download 89% done, avg speed 548 kbps
Download 92% done, avg speed 553 kbps
Download 95% done, avg speed 556 kbps
Download 98% done, avg speed 557 kbps
Download 100% done, avg speed 563 kbps
Total time: 176.51619601249695

来源:

import subprocess
import time
import re

def run_wget(url, outfile):
    cmd = ['wget', '--progress=dot:mega', '-O', outfile, url]
    p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
    for stderr_line in iter(p.stderr.readline, ""):
        yield stderr_line
    p.stderr.close()
    return_code = p.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

url = "https://aspb2.cdn.asset.aparat.com/aparat-video/a5e07b7f62ffaad0c104763c23d7393215613675-1080p.mp4?wmsAuthSign=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6Ijg0ZTVmNjhhMGJkNDJlMmM0MWFjZjgyNzY5YWU4NmMzIiwiZXhwIjoxNjA1NzM3NjIxLCJpc3MiOiJTYWJhIElkZWEgR1NJRyJ9.eaqnWYevFhe-CHG1TGR3SuoTbnVNBEJmLj-ZSxjtNbY"
name = url.split('/')[-1].split('?')[0]
print(name)
file_size = None
start = time.time()

for line in run_wget(url, name):
    if file_size is None:
        m = re.match(r'Length: (\d+)', line)
        if m:
            file_size = int(m[1])
            print('file size =', file_size)
    else:
        m = re.search(r'(\d+)%', line)
        if m:
            pct_done = int(m[1])
            downloaded = file_size / 100 * pct_done
            elapsed = time.time() - start
            speed = round(downloaded / elapsed / 1024)
            print(f"Download {pct_done}% done, avg speed {speed} kbps")

print('Total time:', time.time() - start)