赞
踩
关于多线程相关的基础知识,已经在另一篇文章中有过详细描述,此处不再赘述。有需要的可以参考:
Python并发编程之threading模块
要点主要是:
这里,我们仍然以下载单张图片的简单程序为例,首先,我们可以实现一个单线程版的图片下载器,代码范例可以从这篇文章中获取:
python使用requests库下载单张图片的简单示例
import requests
import random
import os
class PicDownloader():
def __init__(self):
self.count = 0
self.file_name_prefix = 'img'
self.working_dir = '.'
def download_img(self, img_url, dir='.'):
'''
下载url指定的图片到本地dir目录
'''
self.count += 1
file_name = self.file_name_prefix + '_' + str(self.count) + '.jpg'
if os.path.isdir(dir):
self.working_dir = dir
else:
try:
os.makedirs(dir)
self.working_dir = dir
except OSError as e:
print(f'create dir: {dir} failed')
file_path = os.path.join(self.working_dir, file_name)
print(f'开始下载文件:{file_path}')
headers = self.get_headers()
try:
res = requests.get(url=img_url, headers=headers)
if 200 == res.status_code:
with open(file_path, 'wb') as file:
file.write(res.content)
except Exception as e:
print(f'下载文件失败: {file_path}')
print(repr(e))
print('文件下载成功')
# 本例中实际不需要这么多
def get_headers(self):
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]
userAgent = random.choice(user_agent_list)
headers = {'User-Agent': userAgent}
return headers
if __name__== '__main__':
downloader = PicDownloader()
downloader.download_img('https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', 'd:/test')
如何将其改造为多线程版本呢?
首先,导入threading模块,并使PicDownloader继承自threading.Thread, 构造函数中带入一些必要的参数,即可
class PicDownloader(threading.Thread):
def __init__(self, name, img_url, file_name, dir='.'):
threading.Thread.__init__(self) # 注意,这一步是必须的,先对Thread进行相关初始化
self.setName(name) # 线程名是可选的,这里设置只是为了后面观察线程方便
self.daemon = True # 按需设置
self.url = img_url
self.file_name = file_name
self.working_dir = dir
现在PicDownloader类就可以以线程的方式来运行了,我们还需要重写一下run()方法, 其实就是把原先的主要工作方法download_img()里面的内容挪到了run()方法内,你甚至可以理解为就是把download_img()重命名为run()方法。
def run(self):
'''
下载url指定的图片到本地dir目录
'''
if os.path.isdir(self.working_dir):
pass
else:
try:
os.makedirs(self.working_dir)
except OSError as e:
print(f'create dir: {dir} failed')
if not self.file_name.endswith('.jpg'):
self.file_name = self.file_name + '.jpg'
file_path = os.path.join(self.working_dir, self.file_name)
print(f'thread:{self.name} -> 开始下载文件:{file_path}')
headers = self.get_headers()
try:
res = requests.get(url=self.url, headers=headers)
if 200 == res.status_code:
with open(file_path, 'wb') as file:
file.write(res.content)
except Exception as e:
print(f'下载文件失败: {file_path}')
print(repr(e))
print(f'thread:{self.name} -> 文件下载成功:{file_path}')
至此,多线程的改造基本结束。
完整的代码:
# coding=utf-8
import requests
import threading
import random
import os
class PicDownloader(threading.Thread):
def __init__(self, name, img_url, file_name, dir='.'):
threading.Thread.__init__(self)
self.setName(name)
self.daemon = True
self.url = img_url
self.file_name = file_name
self.working_dir = dir
def run(self):
'''
下载url指定的图片到本地dir目录
'''
if os.path.isdir(self.working_dir):
pass
else:
try:
os.makedirs(self.working_dir)
except OSError as e:
print(f'create dir: {dir} failed')
if not self.file_name.endswith('.jpg'):
self.file_name = self.file_name + '.jpg'
file_path = os.path.join(self.working_dir, self.file_name)
print(f'thread:{self.name} -> 开始下载文件:{file_path}')
headers = self.get_headers()
try:
res = requests.get(url=self.url, headers=headers)
if 200 == res.status_code:
with open(file_path, 'wb') as file:
file.write(res.content)
except Exception as e:
print(f'下载文件失败: {file_path}')
print(repr(e))
print(f'thread:{self.name} -> 文件下载成功:{file_path}')
def get_headers(self):
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]
userAgent = random.choice(user_agent_list)
headers = {'User-Agent': userAgent}
return headers
if __name__== '__main__':
downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')
downloader.start()
downloader.join()
多线程版本的类class PicDownloader(threading.Thread)改造完成后,我们只需要调用start()方法就可以启动线程。例如:
if __name__== '__main__':
downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')
downloader.start()
downloader.join()
start()
此方法会在一个单独的控制线程中调用run()方法,启动线程。此方法只能调用一次。
join([timeout])
等待直到线程终止或者出现超时为止。timeout是一个浮点数,用于指定以秒为单位的超时时间。必须在start()方法之后调用,如果在线程启动之前就连接它将出现错误。
以上程序单独执行,即下载单张图片,还不能体现多线程的优势。结合爬虫程序,我们看看如何运用多线程版本的图片下载器。
假设一个最简单的场景,就是我们想从一个页面上,爬取该页面上所有的图片。
当然了,这前期有一些工作要做,比如将所有的图片url都解析出来(本例中存到了列表img_url_list中),那么之后就可以发挥出多线程版本图片下载器的威力了。示例代码如下:
count = 0
path = 'd:/test'
pic_download_threads = []
for img_url in img_url_list:
count += 1
file_name = 'img_' + str(count)
pic_download_threads.append(PicDownloader(str(count), img_url, file_name, path))
for working_thread in pic_download_threads:
working_thread.start()
for working_thread in pic_download_threads:
working_thread.join(5)
即使img_url_list里只有十几张图片的url,多线程版本也比单线程版本要快的多。如果图片url数目更多的话,速度优势就更明显了。
上面是常规的多线程处理方式,如果img_url_list里只有十几张图片的url,多线程版本将比单线程版本要快的多。
但我们回过头来考虑以上程序,假如img_url_list里面的成员数非常多的话,比如该页面可能有数百张图片需要下载时,此时会发生什么?如果像上面这么做,我们将启动数百个工作线程!!!而系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。并且,当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃。
在这种情况下,使用线程池就可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
关于线程池的使用,请移步我的另一篇博文:《在Python网络爬虫程序中使用线程池》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。