跟AI学习用python制作下载器-3

python 复制代码
from http.client import responses

import requests
import threading
import os

def download_part(url,start,end,filename):
    headers = {
        "Range": f"bytes={start}-{end}"
    }

    responses = requests.get(url,headers=headers,stream=True)

    with open(filename,"wb") as f:
        f.seek(start)
        f.write(responses.content)

def multi_thread_download(url,filename,thread_num=4):

    responses = requests.head(url)
    total_size = int(responses.headers.get('content-length',0))

    print("文件大小:",total_size)

    with open(filename,"wb") as f:
        f.truncate(total_size)

    part = total_size // thread_num

    threads = []

    for i in range(thread_num):
        start = i*part

        if i==thread_num-1:
            end = total_size-1
        else:
            end = start+part-1

        t=threading.Thread(target=download_part,args=(url,start,end,filename))

        threads.append(t)
        t.start()

    for t in threads:
        t.join()

        print("多线程下载完成")

url = input("链接: ")
name = input("保存名: ")

multi_thread_download(url,name,thread_num=4)

这个版本是多线程并行下载,但是这个代码有问题,让我们一起看看

首先这个版本比上一个的多了许多东西

python 复制代码
headers = {"Range": f"bytes={start}-{end}"}

通过在请求头部假如Range,相当于告诉服务器,我不要整个文件,给我start字节到end字节的内容,就好比翻书一样,我翻书1到50页,你翻书51到100页。

这个

python 复制代码
with open(filename, "wb") as f:
    f.truncate(total_size)

f.trucate(size)会在硬盘上创建一个指定大小的空文件,为啥要用trucate呢,是因为多个线程会同时往这个文件中不同位置写数据,如果不先固定大小,文件指针会乱套。

这个

python 复制代码
t = threading.Thread(target=download_part, args=(url, start, end, filename))
threads.append(t)
t.start()

target线程要执行的任务也就是函数名,然后args是传给函数的参数。t.start()这行代码执行后,主程序不会等待下载完成而是直接冲下下一个循环。4个线程几乎同时开始工作。

这个

python 复制代码
for t in threads:
    t.join()

join()的意思是等一下,主程序运行到这里会停下,直到所有的子线程的下载任务都完成了,才会继续往下走,如果没有这行可能文件还没有下载完,程序就结束了。

这个

python 复制代码
f.seek(start)
f.write(responses.content)

f.seek()是最关键的一步,他把文件的光标移动到指定位置。比如线程A移动到0的位置写,线程B移动到500的位置写,大家各写各的最后拼成一个完整的文件。

现在得说一下问题在哪,这段代码的问题有两个

python 复制代码
with open(filename,"wb") as f:
        f.seek(start)
        f.write(responses.content)

首先就是以"wb"的方式打开文件,这里的代码是子线程使用的,如果每个子线程都wb,那么这个文件只能保留最后一个子线程写入的数据,因为wb会清空文件然后重新写。

我们可以改成这样

python 复制代码
def download_part(url,start,end,filename):
    headers = {
        "Range": f"bytes={start}-{end}"
    }

    responses = requests.get(url,headers=headers,stream=True)

    with open(filename,"rb+") as f:
        f.seek(start)
        f.write(responses.content)

可以看到我们改成了rb+,r表示我要读取现有的文件不会清空它,b处理图片等非文本文件,+这个是一个增强符号,表示在读的基础上增加写的权力。

但是这个代码还是有问题,它的问题在这里

python 复制代码
with open(filename,"rb+") as f:

f.seek(start)

f.write(responses.content)

因为responses.content会一次性的把数据全部读入内存,如果文件很大假如是4GB,我们这里又分了4个线程,那么每个线程就是1GB,那么4个线程会瞬间占用4GB的内存。所以我们要使用之前出现过的iter_content,循环写入。

可以改成这样

python 复制代码
 with open(filename,"rb+") as f:
        f.seek(start)
        for chunk in responses.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)

这里判断chunk是为了过滤保持衔接的空块。

总体代码是这样的

python 复制代码
from http.client import responses

import requests
import threading
import os

def download_part(url,start,end,filename):
    headers = {
        "Range": f"bytes={start}-{end}"
    }

    responses = requests.get(url,headers=headers,stream=True)

    with open(filename,"rb+") as f:
        f.seek(start)
        for chunk in responses.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)


def multi_thread_download(url,filename,thread_num=4):

    responses = requests.head(url)
    total_size = int(responses.headers.get('content-length',0))

    print("文件大小:",total_size)

    with open(filename,"wb") as f:
        f.truncate(total_size)

    part = total_size // thread_num

    threads = []

    for i in range(thread_num):
        start = i*part

        if i==thread_num-1:
            end = total_size-1
        else:
            end = start+part-1

        t=threading.Thread(target=download_part,args=(url,start,end,filename))

        threads.append(t)
        t.start()

    for t in threads:
        t.join()

        print("多线程下载完成")

url = input("链接: ")
name = input("保存名: ")

multi_thread_download(url,name,thread_num=4)

现在就可以使用这个代码去下载图片或者其他东西试试了

相关推荐
天外飞雨道沧桑8 小时前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
hhhjhl8 小时前
Yup 跨端三方库仓颉适配全流程
java·开发语言
运维行者_8 小时前
网络监控工具怎么选?从设备监控到智能运维的能力清单
运维·服务器·开发语言·网络·人工智能·支持向量机·php
小小de风呀8 小时前
de风——【从零开始学习Linux】(九):进程基础 —— 从冯诺依曼体系到 fork 创建进程
linux·学习
李某学编程8 小时前
基于ARM的微机原理学习笔记(一)
arm开发·笔记·学习
KeepRobots9 小时前
11-01 温哥华不回拨:用 60 行 Python 查你的工单系统会不会差一小时(KeepRobots 工程笔记)
python
xiao77mwq9 小时前
你以为 OCR 慢?实测 81.3 s 里全是故障代价:如何把「定格态」消掉,让耗时从 6.5 s 到 13 倍差距显出原形
python
深圳市益普科技有限公司9 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法
“AI国潮设计-小江”9 小时前
【AIGC实战】Python+SDXL打造“潮汕英歌舞IP”万能立牌插件,批量生成“发财元宝蛋糕”视觉营销方案(附源码)
开发语言·人工智能·python·prompt·aigc
Song_RsHt9 小时前
Redis 与微服务,什么场景下无可替代
java·redis·后端·学习·微服务