【问题标题】:How to reconnect in requests to continue a download如何重新连接请求以继续下载
【发布时间】:2021-04-11 12:07:44
【问题描述】:

我一直在 tkinter 和 requests 中制作这个下载管理器应用程序,我意识到有时如果用户同时下载多个文件,它无法跟上,所有下载都没有任何结果错误。我还尝试了 urllib3 和标准 urllib,尽管 urrlib 的唯一区别是它只是引发了错误,但仍然失败。如果下载结束,我想以某种方式制作我的程序:

  • 首先检查文件大小是否小于应有的大小
  • 如果是,则获取该文件的大小并制作一个范围标题,如下所示:{"Range": f"bytes={current_size}-{file_size}"}
  • 将文件的其余部分存储在临时文件中。下载后,从两个文件中获取数据并将其写入一个(合并文件)

我使用了 while 循环和临时计数器,但问题是当请求无法跟上并到达 while 循环时,它会生成数百万个临时文件,每个文件的大小为 197 字节,但它不会工作。我还尝试只使用一个 if 循环,希望它能够被修复,不同之处在于它没有创建数百万个文件但仍然无法正常工作。最后,我尝试编写一个单独的模拟程序,该程序直接获取其余文件并将其合并为下载一半的文件,并且它可以工作,但是由于某种原因,当我在我的程序中尝试它时却没有。请记住,我不想为每个临时文件创建一个线程,因为它可以很容易地写在与下载文件的线程相同的线程中。我怎样才能做到这一点?我的代码(请注意,此函数在单独的线程中运行):

currently_downloading = np.array([], dtype='S')
current_temp = 0

def download_files():
    global files_downloading, times_clicked, currently_downloading, packed, last_temp, current_temp
    try:
        abort = False
        win = None
        available_num = 0
        downloaded = 0
        url = str(url_entry.get())
        try:
            headers = requests.head(url, headers={'accept-encoding': ''}).headers
        except ValueError:
            raise InvalidURL()

        try:
            file_size = float(headers['Content-Length'])
        except TypeError:
            raise NotDownloadable()

        name = ""
        formatname = ""

        if num.get() == 1:
            name = url.split("/")[-1].split(".")[0]
        else:
            if name_entry.get().strip() != "":
                for char in str(name_entry.get()):
                    if char in banned_chars:
                        print("Usage of banned characters")
                        raise BannedCharsUsage()
                else:
                    name = str(name_entry.get())
            else:
                raise EmptyName()

        if var.get() == 1:
            formatname = '.' + headers['Content-Type'].split('/')[1]
        else:
            if str(format_entry.get())[0] == '.' and len(format_entry.get()) >= 3:
                formatname = str(format_entry.get())
            else:
                raise InvalidFormat()

        fullname = str(name) + formatname
        path = (str(output_entry.get()) + "/").replace(r" \ ".strip(), "/")
        if chum.get() == 1:
            conn = sqlite3.connect("DEF_PATH.db")
            c = conn.cursor()
            c.execute("SELECT * FROM DIRECTORY_LIST WHERE SELECTED_DEF = 1")
            crnt_default_path = np.array(c.fetchone())
            path = str(crnt_default_path[0] + "/").replace(r" \ ".strip(), "/")
            conn.commit()
            conn.close()
        else:
            pass

        all_files_dir = np.array([], dtype='S')
        for file in os.listdir(path):
            all_files_dir = np.append(all_files_dir, path + file)
        all_files_dir = np.concatenate((all_files_dir, currently_downloading))
        while path + fullname in all_files_dir:
            for element in currently_downloading:
                if element not in all_files_dir:
                    all_files_dir = np.append(all_files_dir, element)
            available_num += 1

            if num.get() == 1:
                name = url.split("/")[-1].split(".")[0] + f" ({available_num})"
            else:
                name = str(name_entry.get()) + f" ({available_num})"
            fullname = name + formatname
            if path + fullname not in all_files_dir:
                currently_downloading = np.append(currently_downloading, path + fullname)
                available_num = 0
                break
        else:
            currently_downloading = np.append(currently_downloading, path + fullname)

        def cancel_dl():
            nonlocal abort
            abort = True

        start_time = time.time()
        try:
            r = requests.get(url, allow_redirects=False, stream=True)
            start = last_print = time.time()
            with open(path + fullname, 'wb') as fp:
                for chunk in r.iter_content(chunk_size=4096):
                    if abort:
                        raise AbortException()
                    downloaded += fp.write(chunk)
                    if downloaded > 1000000:
                        lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000000, 2)} MB")
                    else:
                        lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000, 2)} KB")
                    pct_done = int(downloaded / file_size * 100)
                    lbl_percent.config(text=f"{round(pct_done, 2)} %")
                    download_prg["value"] = pct_done
                    now = time.time()
                    if now - last_print >= 1:
                        speed_sec = round(downloaded / (now - start))
                        if speed_sec > 1000000:
                            lbl_speed.config(text=f"{round(speed_sec / 1000000, 3)} MB/s")
                        else:
                            lbl_speed.config(text=f"{round(speed_sec / 1000, 3)} KB/s")
                        last_print = time.time()
            while os.stat(path + fullname).st_size < file_size:
                current_temp += 1
                rng = {"Range": f"bytes={os.stat(path + fullname).st_size}-{file_size}"}
                r = requests.get(url, allow_redirects=False, stream=True, headers=rng)
                start = last_print = time.time()
                with open(f"temp/Temp-{current_temp}{formatname}", 'wb') as fp:
                    for chunk in r.iter_content(chunk_size=4096):
                        if abort:
                            raise AbortException()
                        downloaded += fp.write(chunk)
                        if downloaded > 1000000:
                            lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000000, 2)} MB")
                        else:
                            lbl_crnt_size.config(text=f"Downloaded: {round(downloaded / 1000, 2)} KB")
                        pct_done = int(downloaded / file_size * 100)
                        lbl_percent.config(text=f"{round(pct_done, 2)} %")
                        download_prg["value"] = pct_done
                        now = time.time()
                        if now - last_print >= 1:
                            speed_sec = round(downloaded / (now - start))
                            if speed_sec > 1000000:
                                lbl_speed.config(text=f"{round(speed_sec / 1000000, 3)} MB/s")
                            else:
                                lbl_speed.config(text=f"{round(speed_sec / 1000, 3)} KB/s")
                            last_print = time.time()
                with open(f"temp/Temp-{current_temp}{formatname}", 'rb') as fp:
                    temp_binary = fp.read()
                with open(path + fullname, 'rb') as fp:
                    main_binary = fp.read()
                with open(path + fullname, 'wb') as fp:
                    fp.write(main_binary + temp_binary)

        except AbortException:
            if os.path.exists(path + fullname):
                os.remove(path + fullname)

【问题讨论】:

  • 与其允许如此多的下载导致连接失败,不如设置一个下载队列,一次下载一个(或两个,或三个)不是更容易吗?
  • @joedeandev,嗯,我希望程序像谷歌浏览器一样,这样你就可以同时下载文件,但这不是唯一的问题,因为我意识到如果用户的互联网连接不是很快它仍然失败,如果用户的互联网连接中断怎么办?我需要考虑这些协议。
  • @OmidKetabollahi 为什么不将下载的内容添加到临时缓存文件夹中?所以即使下载失败,下载的数据仍然会保存在那个文件夹中,当用户重新连接时,可以查看文件夹中的缓存文件,然后继续?
  • 这里已经有答案了:stackoverflow.com/questions/22894211/…
  • 这能回答你的问题吗? How to resume file download in Python?

标签: python python-3.x file tkinter python-requests


【解决方案1】:

没有内置函数可以执行此操作,因此您必须手动执行此操作。

您需要做的第一件事是记录您已写入文件的块/缓冲区的数量。

之前下载函数声明一些变量,比如 x=0。 (统计有多少数据写入文件)

然后在下载函数中检查 x == 0。 如果为true则正常下载, 否则:使用范围标头继续下载

阅读以下范围标头示例:- source

如果 Web 服务器支持范围请求,那么您可以将 Range 标头添加到您的请求中:

    Range: bytes=StartPos-StopPos

您将收到 StartPos 和 StopPos 之间的部分。如果不知道 StopPos,只需使用:

    Range: bytes=StartPos-

所以你的代码是:

    def resume_download(fileurl, resume_byte_position):
        resume_header = {'Range': 'bytes=%d-' % resume_byte_position}
        return requests.get(fileurl, headers=resume_header, stream=True,  verify=False, allow_redirects=True)

另一个例子:- https://www.oreilly.com/library/view/python-cookbook/0596001673/ch11s06.html

在写入每个块后也更新变量 x (x = x + chunk_size)

在下载部分的最后,添加“if”语句来检查下载文件的文件大小是否与服务器上文件的文件大小相同(您可以通过 requests.header.get(' Content-Length')。如果文件大小不同,则再次调用下载函数。

【讨论】:

  • @Tashiq Banyal,有没有办法知道是否支持范围标头?
  • -1 请注明您复制答案的原始答案。 stackoverflow.com/a/22894873/8491642
  • @Amit Singh 我已经提供了原始答案链接,请参阅答案中的“阅读范围标题(源)的以下示例”行。
  • @Omid Ketabollahi 喜欢response = requests.get(url_here) if response.headers.get("Accept-Ranges") == "none" : ``` print('不支持范围标头!')``` else : ``` print('支持范围标头') ```
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-12
  • 2023-01-27
相关资源
最近更新 更多