从Python3.2引入的concurrent.futures模块,Python2.5以上需要在pypi中安装futures包。
future指一种对象,表示异步执行的操作。这个概念的作用很大,是concurrent.futures模块和asyncio包的基础。
网络下载的三种风格
为了高效的处理网络IO,需要使用并发,因为网络有很高的延迟,所以为了不浪费CPU周期去等待,最好再收到网络响应之前去做其他的事情。
下面有三种示例程序,
第一个程序是依序下载的,第二个是使用theadpool来自concurrent.futures模块,第三个是使用asyncio包
按照顺序下载
下面示例是依次下载
import os
import time
import sys
import requests
POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
'MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'
DEST_DIR = 'downloads/'
def sava_flag(img, filename):
path = os.path.join(DEST_DIR, filename)
with open(path, 'wb') as fp:
fp.write(img)
def get_flag(cc):
url = "{}/{cc}/{cc}.gif".format(BASE_URL, cc=cc.lower())
resp = requests.get(url)
return resp.content
def show(text):
print(text, end=" ")
sys.stdout.flush() # 能在一行中显示
def download_many(cc_list):
for cc in sorted(cc_list):
image = get_flag(cc)
show(cc)
sava_flag(image, cc.lower() + ".gif")
return len(cc_list)
def main():
t0 = time.time()
count = download_many(POP20_CC)
elapsed = time.time() - t0
msg = '\n{} flags download in {:.2f}s'
print(msg.format(count, elapsed))
if __name__ == '__main__':
main()
打印
BD BR CD CN DE EG ET FR ID IN IR JP MX NG PH PK RU TR US VN
20 flags download in 24.16s
知识点:
按照惯例,requests不在标准库中,在导入标准库之后,用一个空行分隔开
sys.stdout.flush() : 显示一个字符串,然后刷新sys.stdout,这样能在一行消息中看到进度。Python中正常情况下,遇到换行才会刷新stdout缓冲。
使用conrurrent.futures模块多线程下载
concurrent.futures模块的主要特色是TheadPoolExecutor和ProcessPoolExecutor类,这两个类实现的结构能分别在不同线程或者进程中执行可调用对象。
这两个类内部维护着一个工作线程池或者进程池,以及要执行的任务队列。不过,这个接口抽象的层级很高,无需关心任何实现细节。
下面展示如何使用TheadPoolExecutor.map方法,最简单的方式实现并发下载。
import os
import time
import sys
from concurrent import futures
import requests
POP20_CC = ('CN IN US ID BR PK NG BD RU JP '
'MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'
DEST_DIR = 'downloads/'
max_workers = 20 # 设定线程数
def sava_flag(img, filename):
path = os.path.join(DEST_DIR, filename)
with open(path, 'wb') as fp:
fp.write(img)
def get_flag(cc):
url = "{}/{cc}/{cc}.gif".format(BASE_URL, cc=cc.lower())
resp = requests.get(url)
return resp.content
def show(text):
print(text, end=" ")
sys.stdout.flush() # 能在一行中显示
def download_one(cc):
image = get_flag(cc)
show(cc)
sava_flag(image, cc.lower() + ".gif")
def download_many(cc_list):
works = min(len(cc_list), max_workers) # 取其中的最小值,以免创建多余的线程
with futures.ThreadPoolExecutor(works) as executor:
res = executor.map(download_one, cc_list)
return len(list(res))
def main():
t0 = time.time()
count = download_many(POP20_CC)
elapsed = time.time() - t0
msg = '\n{} flags download in {:.2f}s'
print(msg.format(count, elapsed))
if __name__ == '__main__':
main()
打印
FR IN RU ID BD JP CN VN TR CD PH NG DE ET US EG IR BR MX PK
20 flags download in 2.92s
知识点:
使用线程数实例化ThreadPoolExecute类。executor.__exit__方法会调用executor.shutdown(wait=True)方法,它会在所有线程执行完毕前阻塞线程。