在数字时代,我们每天都能够轻松访问到各种各样的图片,不论是美景、艺术品还是有趣的表情包。然而,有时候我们需要大量的图片数据,例如用于机器学习的训练集或者简单的个人收藏。在这种情况下,如何高效地获取和保存这些图片就成了一个挑战。今天,我将向大家介绍一种使用进程池和线程池相结合的方法,以实现高效的图片下载。
准备工作
首先,让我们明确一下需要的准备工作。我们将使用Python来完成这个任务,所以确保你已经安装了Python环境。此外,我们还需要以下库:
- requests:用于发送HTTP请求和获取网页内容。
- lxml:用于解析HTML文档。
- multiprocessing:用于创建进程池和进程共享变量。
- concurrent.futures:用于创建线程池。
确保你已经安装了这些库,如果没有,可以使用pip来安装。
获取图片地址
首先,我们需要获取要下载的图片的地址。在这个例子中,我们将从一个网站上抓取动漫壁纸的图片地址。我们使用了requests库来发送GET请求,然后使用lxml来解析HTML文档,提取出图片的地址。这些地址将被放入一个进程队列中,以便后续的下载。
# 获取图片地址
def get_url(url, que):
resp = requests.get(url, headers=headers)
resp.encoding = resp.apparent_encoding
date = resp.text
tree = etree.HTML(date)
list_url = tree.xpath('//ul[@class="clearfix"]/li/a')
for i in list_url:
url_img = i.xpath('./img/@src')
que.put('https://pic.netbian.com' + str(*url_img)) # put写入进程变量队列中
resp.close()
下载并保存图片
接下来,我们需要编写函数来下载并保存图片。我们使用requests库发送GET请求,然后将图片保存到本地。为了避免文件名冲突,我们可以使用一个共享的数值型变量来为每张图片分配一个唯一的文件名。这个变量会在多个进程之间共享,确保每张图片的文件名都不同。
# 下载并保存图片
def download_ove(url, name):
try:
resp = requests.get(url, headers=headers)
path_img = os.path.join(path, f'{name.value}.jpg')
with open(path_img, 'wb') as f:
f.write(resp.content)
print(f'保存成功{name.value}.jpg')
name.value += 1 # 数值型进程变量自增
except Exception as ex:
print('下载出错', ex)
下载图片的主函数
现在,让我们来编写下载图片的主函数。我们使用了线程池来并发下载图片,这样可以提高下载速度。通过创建一个ThreadPoolExecutor对象,我们可以指定同时运行的线程数量,这里设置为20。然后,我们从进程队列中获取图片地址,并将下载任务提交给线程池处理。
# 获取进程队列中的url并启用线程池下载保存图片
def download_img(que, name):
# 创建线程池,指定20个线程处理数据
with ThreadPoolExecutor(20) as t:
while True:
try:
s = que.get(timeout=3) # 获取进程队列中的数据,等待3秒若是还没获取到数据抛出异常
t.submit(download_ove, s, name) # 添加进线程池
except Exception as ec:
print(ec)
break
异常处理
在我们的代码中,我们还加入了异常处理机制。如果下载出现错误,我们会捕获异常并打印出错信息。这有助于我们及时发现问题并进行处理。
# 接收进程池与线程池异常的回调函数
def err_call_back(err):
print(f'出错啦~ error:{str(err)}')
执行主程序
最后,我们在主程序中执行上述函数。我们首先创建了一个进程共享的队列变量和一个数值型的共享变量,用于存储图片地址和文件名计数。然后,我们根据需要获取多个页面的图片地址,将下载任务提交给进程池,并等待任务完成。这样,我们就能够高效地下载大量图片了。
if __name__ == '__main__':
if not os.path.exists(path):
os.mkdir(path)
que = multiprocessing.Manager().Queue() # 创建一个进程之间共享的队列变量
name = multiprocessing.Manager().Value('i', 0) # 创建一个进程之间共享的数值型变量,'i'表示整型数字,0表示从0开始
for i in range(1, 4):
if i != 1:
url = f'https://pic.netbian.com/4kdongman/index_{i}.html'
else:
url = 'https://pic.netbian.com/4kdongman/'
get_url(url, que)
# 创建进程池,并将函数添加到进程池内,指定最多开辟1个进程处理数据(开多了怕把网址玩崩)
with multiprocessing.Pool(1) as pool:
pool.apply(download_img, args=(que, name)) # 将函数以同步的方式添加进进程池
pool.close() # 结束进程池,不在往内添加数据
pool.join() # 等待进程池运行结束
print('结束')
结束语
通过使用进程池和线程池的组合,我们能够高效地下载大量图片,提高了任务的并发性和效率。这种方法对于需要大规模获取图片数据的任务非常有用,例如构建图像数据集或爬取网站上的图片资源。
希望这个教程对你有所帮助,如果你有任何问题或建议,欢迎在评论中留言。祝愿你在图片下载任务中取得成功!