python 多线程 XKCD 下载程序

在第 11 章,你编写了一个程序,从 XKCD 网站下载所有的 XKCD 漫画。这是一个单线程程序:它一次下载一幅漫画。程序运行的大部分时间,都用于建立网络连接来开始下载,以及将下载的图像写入硬盘。如果你有宽带因特网连接,单线程程序并没有充分利用可用的带宽。多线程程序中有一些线程在下载漫画,同时另一些线程在建立连接,或将漫画图像文件写入硬盘。它更有效地使用 Internet 连接,更迅速地下载这些漫画。打开一个新的文件编辑器窗口,并保存为 multidownloadXkcd.py。你将修改这个程序,添加多线程。经过全面修改的源代码可从 http://nostarch.com/automatestuff/下载。
 

修改程序以使用函数
该程序大部分是来自第 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, encComic):
		# 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,并下载图像。最后,将图像保存到我们创建的目录中。
 

创建并启动线程
既 然已经 定义 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 循环的下一次迭代,创造下一个线程。
 

等待所有线程结束
主线程正常执行,同时我们创建的其他线程下载漫画。但是假定主线程中有一些代码,你希望所有下载线程完成后再执行。调用 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.')。
 

完整代码

#! 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, encComic):
		# 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()

# 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()

# Wait for all threads to end.
for downloadThread in downloadThreads:
	downloadThread.join()
print('Done.')

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值