通过网络图片小爬虫对比Python中单线程与多线(进)程的效率
admin
2023-07-31 00:47:52
0

批评 Python 的人通常都会说 Python 的多线程编程太困难了,众所周知的全局解释器锁(Global Interpreter Lock,或称 GIL)使得多个线程的 Python 代码无法同时运行。因此,如果你并非 Python 开发者,而是从其他语言如 C++ 或者 Java 转过来的话,你会觉得 Python 的多线程模块并没有以你期望的方式工作。但必须澄清的是,只要以一些特定的方式,我们仍然能够编写出并发或者并行的 Python 代码,并对性能产生完全不同的影响。如果你还不理解什么是并发和并行,建议你百度或者 Google 或者 Wiki 一下。

在这篇阐述 Python 并发与并行编程的入门教程里,我们将写一小段从 Imgur 下载最受欢迎的图片的 Python 程序。我们将分别使用顺序下载图片和同时下载多张图片的版本。在此之前,你需要先注册一个 Imgur 应用。如果你还没有 Imgur 账号,请先注册一个。

这篇教程的 Python 代码在 3.4.2 中测试通过。但只需一些小的改动就能在 Python 2中运行。两个 Python 版本的主要区别是 urllib2 这个模块。

注:考虑到国内严酷的上网环境,译者测试原作的代码时直接卡在了注册 Imgur 账号这一步。因此为了方便起见,译者替换了图片爬取资源。一开始使用的某生产商提供的图片 API ,但不知道是网络原因还是其他原因导致程序在读取最后一张图片时无法退出。所以译者一怒之下采取了原始爬虫法,参考着 requests 和 beautifulsoup4 的文档爬取了某头条 253 张图片,以为示例。译文中的代码替换为译者使用的代码,如需原始代码请参考原文 Python Multithreading Tutorial: Concurrency and Parallelism 。

Python 多线程起步

首先让我们来创建一个名为 download.py 的模块。这个文件包含所有抓取和下载所需图片的函数。我们将全部功能分割成如下三个函数:

  • get_links
  • download_link
  • setup_download_dir

第三个函数,setup_download_dir 将会创建一个存放下载的图片的目录,如果这个目录不存在的话。

我们首先结合 requests 和 beautifulsoup4 解析出网页中的全部图片链接。下载图片的任务非常简单,只要通过图片的 URL 抓取图片并写入文件即可。

代码看起来像这样:

123456789101112131415161718192021222324252627282930313233 download.py import jsonimport osimport requests from itertools import chainfrom pathlib import Path from bs4 import BeautifulSoup # 结合 requests 和 bs4 解析出网页中的全部图片链接,返回一个包含全部图片链接的列表def get_links(url):    req = requests.get(url)    soup = BeautifulSoup(req.text, \”html.parser\”)    return [img.attrs.get(\’data-src\’) for img in            soup.find_all(\’div\’, class_=\’img-wrap\’)            if img.attrs.get(\’data-src\’) is not None] # 把图片下载到本地def download_link(directory, link):    img_name = \'{}.jpg\’.format(os.path.basename(link))    download_path = directory / img_name    r = requests.get(link)    with download_path.open(\’wb\’) as fd:            fd.write(r.content) # 设置文件夹,文件夹名为传入的 directory 参数,若不存在会自动创建def setup_download_dir(directory):    download_dir = Path(directory)    if not download_dir.exists():        download_dir.mkdir()    return download_dir

接下来我们写一个使用这些函数一张张下载图片的模块。我们把它命名为single.py。我们的第一个简单版本的 图片下载器将包含一个主函数。它会调用 setup_download_dir 创建下载目录。然后,它会使用 get_links 方法抓取一系列图片的链接,由于单个网页的图片较少,这里抓取了 5 个网页的图片链接并把它们组合成一个列表。最后调用 download_link 方法将全部图片写入磁盘。这是 single.py 的代码:

12345678910111213141516171819202122232425262728293031323334353637 single.py from time import timefrom itertools import chain from download import setup_download_dir, get_links, download_link  def main():    ts = time()     url1 = \’http://www.toutiao.com/a6333981316853907714\’    url2 = \’http://www.toutiao.com/a6334459308533350658\’    url3 = \’http://www.toutiao.com/a6313664289211924737\’    url4 = \’http://www.toutiao.com/a6334337170774458625\’    url5 = \’http://www.toutiao.com/a6334486705982996738\’    download_dir = setup_download_dir(\’single_imgs\’)    links = list(chain(        get_links(url1),        get_links(url2),        get_links(url3),        get_links(url4),        get_links(url5),    ))    for link in links:        download_link(download_dir, link)    print(\’一共下载了 {} 张图片\’.format(len(links)))    print(\’Took {}s\’.format(time() ts))  if __name__ == \’__main__\’:    main() \”\”\”一共下载了 253 张图片ݜ。但必须澄清的是,只要以一些特定的方式,我们仍然能够编写出并发或者并行的 Python 代码,并对性能产生完全不同的影响。如果你还不理解什么是并发和并行,建议你百度或者 Google 或者 Wiki 一下。

在这篇阐述 Python 并发与并行编程的入门教程里,我们将写一小段从 Imgur 下载最受欢迎的图片的 Python 程序。我们将分别使用顺序下载图片和同时下载多张图片的版本。在此之前,你需要先注册一个 Imgur 应用。如果你还没有 Imgur 账号,请先注册一个。

这篇教程的 Python 代码在 3.4.2 中测试通过。但只需一些小的改动就能在 Python 2中运行。两个 Python 版本的主要区别是 urllib2 这个模块。

注:考虑到国内严酷的上网环境,译者测试原作的代码时直接卡在了注册 Imgur 账号这一步。因此为了方便起见,译者替换了图片爬取资源。一开始使用的某生产商提供的图片 API ,但不知道是网络原因还是其他原因导致程序在读取最后一张图片时无法退出。所以译者一怒之下采取了原始爬虫法,参考着 requests 和 beautifulsoup4 的文档爬取了某头条 253 张图片,以为示例。译文中的代码替换为译者使用的代码,如需原始代码请参考原文 Python Multithreading Tutorial: Concurrency and Parallelism 。

Python 多线程起步

首先让我们来创建一个名为 download.py 的模块。这个文件包含所有抓取和下载所需图片的函数。我们将全部功能分割成如下三个函数:

  • get_links
  • download_link
  • setup_download_dir

第三个函数,setup_download_dir 将会创建一个存放下载的图片的目录,如果这个目录不存在的话。

我们首先结合 requests 和 beautifulsoup4 解析出网页中的全部图片链接。下载图片的任务非常简单,只要通过图片的 URL 抓取图片并写入文件即可。

代码看起来像这样:

123456789101112131415161718192021222324252627282930313233 download.py import jsonimport osimport requests from itertools import chainfrom pathlib import Path from bs4 import BeautifulSoup # 结合 requests 和 bs4 解析出网页中的全部图片链接,返回一个包含全部图片链接的列表def get_links(url):    req = requests.get(url)    soup = BeautifulSoup(req.text, \”html.parser\”)    return [img.attrs.get(\’data-src\’) for img in            soup.find_all(\’div\’, class_=\’img-wrap\’)            if img.attrs.get(\’data-src\’) is not None] # 把图片下载到本地def download_link(directory, link):    img_name = \'{}.jpg\’.format(os.path.basename(link))    download_path = directory / img_name    r = requests.get(link)    with download_path.open(\’wb\’) as fd:            fd.write(r.content) # 设置文件夹,文件夹名为传入的 directory 参数,若不存在会自动创建def setup_download_dir(directory):    download_dir = Path(directory)    if not download_dir.exists():        download_dir.mkdir()    return download_dir

接下来我们写一个使用这些函数一张张下载图片的模块。我们把它命名为single.py。我们的第一个简单版本的 图片下载器将包含一个主函数。它会调用 setup_download_dir 创建下载目录。然后,它会使用 get_links 方法抓取一系列图片的链接,由于单个网页的图片较少,这里抓取了 5 个网页的图片链接并把它们组合成一个列表。最后调用 download_link 方法将全部图片写入磁盘。这是 single.py 的代码:

12345678910111213141516171819202122232425262728293031323334353637 single.py from time import timefrom itertools import chain from download import setup_download_dir, get_links, download_link  def main():    ts = time()     url1 = \’http://www.toutiao.com/a6333981316853907714\’    url2 = \’http://www.toutiao.com/a6334459308533350658\’    url3 = \’http://www.toutiao.com/a6313664289211924737\’    url4 = \’http://www.toutiao.com/a6334337170774458625\’    url5 = \’http://www.toutiao.com/a6334486705982996738\’    download_dir = setup_download_dir(\’single_imgs\’)    links = list(chain(        get_links(url1),        get_links(url2),        get_links(url3),        get_links(url4),        get_links(url5),    ))    for link in links:        download_link(download_dir, link)    print(\’一共下载了 {} 张图片\’.format(len(links)))    print(\’Took {}s\’.format(time() ts))  if __name__ == \’__main__\’:    main() \”\”\”一共下载了 253 张图片pan class=\”crayon-s\”>Took 166.0219452381134s\”\”\”

在我的笔记本上,这段脚本花费了 166 秒下载 253 张图片。请注意花费的时间因网络的不同会有所差异。166 秒不算太长。但如果我们要下载更多的图片呢?2530 张而不是 253 张。平均下载一张图片花费约 1.5 秒,那么 2530 张图片将花费约 28 分钟。25300 张图片将要 280 分钟。但好消息是通过使用并发和并行技术,其将显著提升下载速度。

接下来的代码示例只给出为了实现并发或者并行功能而新增的代码。为了方便起见,全部的 python 脚本可以在 这个GitHub的仓库 获取。(注:这是原作者的 GitHub 仓库,是下载 Imgur 图片的代码,本文的代码存放在这:concurrency-parallelism-demo

相关内容

热门资讯

500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
python绘图库Matplo... 本文简单介绍了Python绘图库Matplotlib的安装,简介如下: matplotlib是pyt...
Prometheus+Graf... 一,Prometheus概述 1,什么是Prometheus?Prometheus是最初在Sound...