技术进阶:多线程并发下载多个图片或一个图片——by.北方区平平无奇组织
评论
收藏

技术进阶:多线程并发下载多个图片或一个图片——by.北方区平平无奇组织

经验分享
既明
2022-10-17 14:57·浏览量:2912
既明
发布于 2022-10-17 14:54更新于 2022-10-17 14:572912浏览

可视化指令的http下载:获取url->循环url->指令http下载 速度大概为:39个url需要20+秒

使用多线程模块,并发20个线程对应20个图片,时间5秒

另有多线程对一文件下载(不建议使用这个多对一对多个下载目标使用,适用较大文件的下载,较多文件,存在文件分段下载之后给它拼回去)

多对多:

#线程主要运行函数:加信号量锁,主要任务:向目标url发送请求,下载返回的content,请求头设置max-age,与随机模拟的浏览器,避免服务器返回304缓存
def get_http(http_url,semaphore):
    #加信号量锁
    semaphore.acquire()
    header = {'Cache-Control': 'max-age=0','User-Agent': random.choice(USER_AGENTS),}
    filename = http_url.split('/')[-1]
    respone = requests.get(http_url,headers = header)
    filepath = now_path+'\\'+filename
    #写入文件
    open(filepath,"wb").write(respone.content)
    #信号量锁释放
    semaphore.release()

主要解释:

信号量锁:确保一时间一共设定的线程数对资源进行修改,并且线程完成后释放这个信号量,允许下个进程进入

请求头设计:模拟浏览器,并且设置max-age = 0 避免服务端返回304,下载不到数据

创建文件,并将请求返回主要内容写入

线程调用:

def main(url):
    #设定最大线程20个
    semaphore = threading.BoundedSemaphore(20)
    for i in range(len(url)):
        t = threading.Thread(target = get_http,args = (url[i],semaphore,))
        #t.setDaemon(True)
        t.start()

多对一:

class MulThreadDownload(threading.Thread):
    def __init__(self,download_url,path,filename='',download_thread_num=0,part_size=1024*1024):
        threading.Thread.__init__(self)
        self.download_url = download_url
        self.path=path
        self.file_name=filename
        self.download_thread_num=download_thread_num
        self.part_size=part_size
        self.file=None
        self.threads=[]
        self.lock=threading.Lock()
        self.session=requests.session()
        self.session.mount('http://', HTTPAdapter(max_retries=5))
        self.session.mount('https://', HTTPAdapter(max_retries=5))
        self.file_size=-1
        self.downloaded_size=0
        self.taskQ=queue.Queue()
        self.mbsize=-1
    def download_thread(self,threadid):
        while not self.taskQ.empty():
            part_dict=self.taskQ.get(block=True,timeout=None)
            headers={'Range':'bytes={0}-{1}'.format(part_dict['start'],part_dict['end'])}
            response=self.session.get(url=self.download_url,headers=headers)
            with self.lock:
                self.file.seek(part_dict['start'])
                self.file.write(response.content)
                self.downloaded_size+=part_dict['end']-part_dict['start']
            logging.debug(str(threadid)+' download succeed: '+str(part_dict))
    def analysis_filename(self):
        # 从url地址中获取文件名
        filename = self.download_url.split('/')[-1]
        logging.debug('analysis filename form url,got{0},from{1}'.format(filename,self.download_url))
        return filename
    
    def run(self):
        # 1.从url中获取文件信息,为线程分配下载资源做准备
        # 从url提取文件名
        logging.info('url:'+self.download_url)
        header = {'Cache-Control': 'max-age=0','User-Agent': random.choice(USER_AGENTS),}
        # 从文件响应头获取content-length。以及Accept-Ranges字段为分配下载做准备
        response_head = self.session.head(self.download_url,headers=header)
        if self.file_name == '':
            self.file_name = self.analysis_filename()
        # if not response_head.headers.has_key('Accept-Ranges'):
        if 'Accept-Ranges' not in response_head.headers.keys() :
            logging.fatal("不支持断点续传,不支持多线程下载")
        self.file_size = int(response_head.headers['Content-Length'])
        # 计算文件大小mb值
        self.mbsize=round(self.file_size / 1024 , 2)
        # 获取文件大小后,创建相同大小的文件
        if self.path=='':
            filepath=self.file_name
        else:
            filepath=os.path.join(self.path,self.file_name)
        self.file = open(filepath, 'wb')
        print(filepath)
        self.file.truncate(self.file_size)
        # 获得文件大小后划分下载任务。按照part_size进行划分
        # 最终分块任务数为,比如说文件大小为1gb,分块1mb,那么就要分成1024份,如果1gb多一点点,那么1025份
        part_num = self.file_size//self.part_size+1
        # 发送的请求头带上这一条就可以请求指定区间的数据 Range: bytes = 0 - 1048576
        # 创建下载队列,把Range的值字符串进行拼接
        for num in range(part_num):
            start= num*self.part_size
            end=(num+1)*self.part_size
            if num==part_num-1:
                end=self.file_size
            #rangestr='bytes={0}-{1}'.format(num*self.part_size,endSize)
            part_dict={
                    'partnum':part_num,
                    'start':start,
                       'end':end
            }
            self.taskQ.put(part_dict)
            logging.debug(str(part_dict))
            #print(rangestr)
        for i in range(self.download_thread_num):
            t=threading.Thread(target=self.download_thread,args=(i,))
            self.threads.append(t)
            t.start()
        for t in self.threads:
            t.join()

        # 全部线程运行结束,说明文件下载完成
        self.file.close()
def main(args):
    for i in range(len(url)):
        mul=MulThreadDownload(download_url= url[i],path = 'C:\\Users\\43881\\Desktop\\ceshi',filename = '' ,download_thread_num=20,part_size = 1024)
        mul.start()

主要解释:

在请求的返回中截取字段content-length,进行按大小分块

使用多线程去请求下载不同的段落,最终统合一起

统合文件使用了seek()方法,来校正偏移,通过start,end,指定下载的段落

下载多文件存在小bug,欢迎大佬指正。

收藏4
全部评论1
最新
发布评论
评论