本人前段时间遇到一个问题,就是公司同事给我发来一个“爬虫文件”,需要大概爬取2000多份图片到我们的本地来,但是他的爬虫文件不能满足我的使用,所以为了自己用的方便,所以进行了改造,并使用多线程对其进行了“加持”,一共两套代码,一套是基于cpu核心数自动分配的线程池,一个是可以自定义线程数的多线程,这里我就给大家放的是按照cpu自动分配的代码!

我发现上代码之前大家应该明白两个事情,

1、你需要一定的爬虫基础,需要知道爬虫的基本运作原理

2、你需要知道多线程能解决的事情

总结:python 的多线程只不过是在来回io之间切换线程,而我们需要做的
声明:因为我爬取的是公司的网址,这里就不会给大家看具体的爬取方法,所以如何构造你要的爬取任务是你首先要解决的问题

第一步:构建一个“生产者”,这样说可能很多人都会懵,所以我把他看做是一个“任务池”,就是说你需要提前构建很多个任务,以我这个案例来说,我的任务就是2000个图片需要下载到本地,所以第一步就是要构造2000个任务,因为我们爬取数据不是对数据一无所知,我们爬取的东西都在固定的位置或者实在服务器的固定文件夹,这样只需要我们去将这些需求提取出来就可以了,这里我提取的文件第一步就是一个包含3个元素的文件,其中包含的是

[“图片地址(后半段)”,图片名称,图片地址] * 2000 这一步我称之为任务包

第二步:然后等待系统将这些任务同时执行若干个,以我这个需求来说,就是需要将2000个任务,使用“消费者”也就是能解决这些任务的“特工”程序去解决,在这里的这些特工是自动分配的用的是 from multiprocessing.dummy import Pool as pl 这个代码里面的map()函数,这里不设置就是按照系统分配默认的数量去进行制造“特工”,比如说一般cpu只会使用自己一半的占用率去执行任务,所以我这边大概的线程池有7个左右,也就是说本来2000个任务需要一个进程去做,需要做2000/1次,而现在变成2000/7次,时间效率肯定大大降低,但是这是使用资源换时间的方法,资源的使用率也增加了7倍,这一步一般的up主都成他为“消费者”,也没错,就是概念和理解的问题,我更喜欢理解他为:执行任务的“特工”,这些特工需要做的就是同一类型的事,就是将2000个任务返回的二进制流,保存到本地

 

废话不多说上代码


import os.path
import time
from multiprocessing.dummy import Pool as pl
import requests



class fileread(object):
  

    #一次拿到所有图片的url的后半段地址,这一步就是要构造2000个[“图片地址(后半段)”,图片名称,图片地址] ,然后将其放在一个列表里面此处我省略,返回出去的是一个列表的big_data,大数据包,这里的数据包还不能用,因为这里只包含图片的地址后半段,还需要增加他的域名我放到了下面的函数
    def select_Id(self):
                pass

        return big_data

    #数据包,数据包在这里解包,和拼装,组成一个完成的url图片地址,和他的名字,他的存放文件夹[“图片地址(完整)”,图片名称,图片地址],这里返回的就是完整的2000个任务


def select_waubill(self):
        big_data =  fileread.select_Id(self)
        url_data = []
        for i in big_data :
           url = ("https://www.***.com/"+i[1][1:-1])
           name = i[0]
           supliername = i[2]
           list_1 = [url,name,supliername]
           url_data.append(list_1)
        return url_data

    #并发请求,
    def get_url(self,data):
      
                try:
                    res = requests.get(data[0],timeout=500)
                    if res:
                        image_data = [res.content,data[1],data[2]]
                        self.save_image(image_data)
                    else:
                        pass
                    print('访问成功',data)

                except:
                    print('访问没有成功1',data)
                    url2.append(data)
                print(len(url2))
    #单发死循环,直到所有图片下载出
    def get_url2(self,data):
            if len(data[0])<50:
                pass
            else:
                try:
                    res = requests.get(data[0],timeout=500)
                    if res:
                        image_data = [res.content,data[1],data[2]]
                        self.save_image(image_data)

                    else:
                        pass
                    print('访问成功2',data)

                except:
                    print('访问没有成功2',data)
                    self.get_url2(data)

    # 保存照片
    def save_image(self,data):
        try:
            if os.path.exists(save_path + '/' + data[2]):
                with open(save_path + '/' + data[2] + '/' + data[1], 'wb') as f:
                    f.write(data[0])

            else:
                os.mkdir(save_path + '/' + data[2])
                with open(save_path + '/' + data[2] + '/' + data[1], 'wb') as f:
                    f.write(data[0])
        except:
            print('图片已经存在了')


if __name__ == '__main__':
    inside = time.time()
    # 请求超时的数据放在url2里最后单发重新执行
    url2 = []
    save_path = "D:/照片中心"  # 下载地址
    f = fileread()
    # #数据集合包
    big_data2 = f.select_waubill()
    #并发开始
    pool = pl()
    pool.map(f.get_url,big_data2)
    pool.close()
    pool.join()
    print(f'开始单发请求下载{len(url2)}条')
    for i in url2:
        get_url2 = f.get_url2(i)

    end = time.time()
    print(end-inside)