- 内容提要
- 作者简介
- 技术评审者简介
- 致谢
- 译者序 会编程的人不一样
- 前言
- 本书的读者对象
- 编码规范
- 什么是编程
- 本书简介
- 下载和安装 Python
- 启动 IDLE
- 如何寻求帮助
- 聪明地提出编程问题
- 小结
- 第一部分 Python 编程基础
- 第1章 Python 基础
- 第2章 控制流
- 第3章 函数
- 第4章 列表
- 第5章 字典和结构化数据
- 第6章 字符串操作
- 第二部分 自动化任务
- 第7章 模式匹配与正则表达式
- 第8章 读写文件
- 第9章 组织文件
- 第10章 调试
- 第11章 从 Web 抓取信息
- 第12章 处理 Excel 电子表格
- 第13章 处理 PDF 和 Word 文档
- 第14章 处理 CSV 文件和 JSON 数据
- 第15章 保持时间、计划任务和启动程序
- 第16章 发送电子邮件和短信
- 第17章 操作图像
- 第18章 用 GUI 自动化控制键盘和鼠标
- 附录A 安装第三方模块
- 附录B 运行程序
- 附录C 习题答案
15.7 项目:多线程 XKCD 下载程序
在第11章,你编写了一个程序,从XKCD网站下载所有的XKCD漫画。这是一个单线程程序:它一次下载一幅漫画。程序运行的大部分时间,都用于建立网络连接来开始下载,以及将下载的图像写入硬盘。如果你有宽带因特网连接,单线程程序并没有充分利用可用的带宽。
多线程程序中有一些线程在下载漫画,同时另一些线程在建立连接,或将漫画图像文件写入硬盘。它更有效地使用Internet连接,更迅速地下载这些漫画。打开一个新的文件编辑器窗口,并保存为multidownloadXkcd.py。你将修改这个程序,添加多线程。经过全面修改的源代码可从http://nostarch.com/automatestuff/下载。
第1步:修改程序以使用函数
该程序大部分是来自第 11 章的相同下载代码,所以我会跳过 Requests 和BeautifulSoup代码的解释。需要完成的主要变更是导入threading模块,并定义downloadXkcd()函数,该函数接受开始和结束的漫画编号作为参数。
例如,调用downloadXkcd(140,280)将循环执行下载代码,下载漫画http://xkcd. com/140、http://xkcd.com/141、http://xkcd.com/142等,直到http://xkcd.com/279。你创建的每个线程都会调用downloadXkcd(),并传入不同范围的漫画进行下载。
将下面的代码添加到multidownloadXkcd.py程序中:
#! python3 # multidownloadXkcd.py - Downloads XKCD comics using multiple threads. import requests, os, bs4, threading ❶ os.makedirs('xkcd', exist_ok=True) # store comics in ./xkcd ❷ def downloadXkcd(startComic, endComic): ❸ for urlNumber in range(startComic, endComic): # Download the page. print('Downloading page http://xkcd.com/%s...' % (urlNumber)) ❹ res = requests.get('http://xkcd.com/%s' % (urlNumber)) res.raise_for_status() ❺ soup = bs4.BeautifulSoup(res.text) # Find the URL of the comic image. ❻ comicElem = soup.select('#comic img') if comicElem == []: print('Could not find comic image.') else: ❼ comicUrl = comicElem[0].get('src') # Download the image. print('Downloading image %s...' % (comicUrl)) ❽ res = requests.get(comicUrl) res.raise_for_status() # Save the image to ./xkcd. imageFile = open(os.path.join('xkcd', os.path.basename(comicUrl)), 'wb') for chunk in res.iter_content(100000): imageFile.write(chunk) imageFile.close() # TODO: Create and start the Thread objects. # TODO: Wait for all threads to end.
导入需要的模块后,❶行创建了一个目录来保存漫画,并开始定义downloadxkcd()❷。循环遍历指定范围中的所有编号❸,并下载每个页面❹。用Beautiful Soup查看每一页的HTML❺,找到漫画图像❻。如果页面上没有的漫画图像,就打印一条消息。否则,取得图片的URL❼,并下载图像❽。最后,将图像保存到我们创建的目录中。
第2步:创建并启动线程
既然已经定义 downloadXkcd(),我们将创建多个线程,每个线程调用downloadXkcd(),从 XKCD 网站下载不同范围的漫画。将下面的代码添加到multidownloadXkcd.py中,放在downloadXkcd()函数定义之后:
#! python3 # multidownloadXkcd.py - Downloads XKCD comics using multiple threads. --snip-- # Create and start the Thread objects. downloadThreads = [] # a list of all the Thread objects for i in range(0, 1400, 100): # loops 14 times, creates 14 threads downloadThread = threading.Thread(target=downloadXkcd, args=(i, i + 99)) downloadThreads.append(downloadThread) downloadThread.start()
首先,我们创建了一个空列表downloadThreads,该列表帮助我们追踪创建的多个Thread对象。然后开始for循环。在每次循环中,我们利用threading.Thread()创建一个Thread对象,将它追加到列表中,并调用start(),开始在新线程中运行downloadXkcd()。因为for循环将变量i设置为从0到1400,步长为100,所以i在第一次迭代时为0,第二次迭代时为100,第三次为200,以此类推。因为我们将args=(I, I+99)传递给threading.Thread(),所以在第一次迭代时,传递给downloadXkcd()的两个参数将是0和99,第二次迭代是100和199,第三次是200和299,以次类推。
由于调用了Thread对象的start()方法,新的线程开始运行downloadXkcd()中的代码,主线程将继续for循环的下一次迭代,创造下一个线程。
第3步:等待所有线程结束
主线程正常执行,同时我们创建的其他线程下载漫画。但是假定主线程中有一些代码,你希望所有下载线程完成后再执行。调用Thread对象join()方法将阻塞,直到该线程完成。利用一个for循环,遍历downloadThreads列表中的所有Thread对象,主线程可以调用其他每个线程的join()方法。将以下代码添加到程序的末尾:
#! python3 # multidownloadXkcd.py - Downloads XKCD comics using multiple threads. --snip-- # Wait for all threads to end. for downloadThread in downloadThreads: downloadThread.join() print('Done.')
所有的join()调用返回后,'Done.'字符串才会打印,如果一个Thread对象已经完成,那么调用它的join()方法时,该方法就会立即返回。如果想扩展这个程序,添加一些代码,在所有漫画下载后运行,就可以用新的代码替换print('Done.')。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论