【重学Python】:Python 拓展之迭代器

写在之前

今天来讲讲「迭代器」的内容,其实已经拖了好多天了,感觉再不写就要忘记了。「迭代」相信对你来说已经不陌生了,我前面曾经专门用一篇文章来讲,如果你已经没有什么印象的话,就再点进去看看。

迭代器

首先我们先来看一种检查是否可迭代的方法:

>>> hasattr(list,'__iter__')

True

可以用上面的这种方法检查已经学习过的其他默认类型的对象,比如字符串,列表,字典等是否是可迭代的。

iter() 是一个特殊方法,它是迭代规则的基础,有了它,就说明对象是可迭代的。跟迭代有关的一个内建函数 iter(),这个函数我们在之前的文章中介绍过,它返回的是一个迭代器对象,比如像下面这样:

>>> list1 = [1,2,3,4]

>>> iter_list = iter(list1)

>>> iter_list

<list_iterator object at 0x00000000021CE438>

从上述代码的结果可以看出,iter_list 引用的是迭代器对象。那么在这里有一个问题,iter_list 和 list1 有区别吗?我们来试一下:

>>> hasattr(list1,'__iter__')

True

>>> hasattr(iter_list,'__iter__')

True

从上面看出它们都有 iter,说明它们都是可迭代的。

>>> hasattr(list1,"__next__")

False

>>> hasattr(iter_list,"__next__")

True

我们把像 iter_list 所引用的对象那样,称之为「迭代器对象」。显而易见的是,迭代器对象必然是可迭代的,反正则不一定。且 Python 中迭代器对象实现的是 next() 方法。

为了体现一下 Python 在这的强大之处,我们先来写一个迭代器对象:

python
复制代码
class MyRange:

   def __init__(self,n):

       self.i = 1

       self.n = n



   def __iter__(self):

       return self



   def __next__(self):

       if self.i <= self.n:

           i = self.i

           self.i += 1

           return i

       else:

           raise StopIteration()



if __name__ == "__main__":

   x = MyRange(5)

   print([i for i in x])

上述代码的运行结果如下所示:

csharp
复制代码
[1,2,3,4,5]

上述的代码仿写了类似 range() 的类,但是与 range() 又有所不同,除了结果不同以外还包括以下 2 点:

1.iter() 是类中的核心,它返回了迭代器的本身,一个实现了 iter() 方法的对象,就意味着它是可迭代的。

2.实现了 next() 方法,从而使得这个对象是迭代器对象。

接下来我们来看看 range() 本身:

python
复制代码
>>> a = range(5)

>>> hasattr(a,'__iter__')

True

>>> hasattr(a,'__next__')

False

>>> print(a)

range(0, 5)

由上面我们就可以看出,其实我们所写的类和 range() 本身还是有很大区别的。

通过上面的内容和我们之前的文章对迭代的讲述,下面我们对迭代器做一个概括:

1.在 Python 中,迭代器是遵循迭代协议的对象。我们可以使用 iter() 从任何序列得到迭代器(exp: list,turple,set and so on)。

2.当自己编写迭代器的类的时候,其中实现 iter() 和 next() 方法,如果没有元素的话,会引发 StopIteration 异常。

3.如果有很多值的话,列表会占用太多的内存,而迭代器则占用的更少,它从第一个元素开始访问,直到所有的元素被访问完结束,只能向前冲,不能后退。

迭代器不仅仅是实用而已,而且也非常的有趣,让我们来看下面的操作:

python
复制代码
>>> list1 = [x**x for x in range(3)]

>>> list1

[1, 1, 4]

>>> for i in list1:print(i)

...

1

1

4

>>> for i in list1:print(i)

...

1

1

4

我们在上面重复两次调用列表 list1 进行循环,都是能正常进行的,这个列表相当于一个可以长久使用的东西,可以重复使用。

在 Python 中,除了列表解析式以外,还可以做成元组解析式,方法也是非常的简单:

python
复制代码
>>> tuple1 = (x**x for x in range(3))

>>> tuple1

<generator object <genexpr> at 0x0000000001DF16D8>

>>> for i in tuple1:print(i)

...

1

1

4

>>> for i in tuple1:print(i)

...

对于 tuple1,我们可以看到它是一个 generator 对象,关于这个是啥我们先不管,后面我会单独来说的。当我们把它用到循环中的时候,它明显是个一次性用品,再次使用的时候它就什么也不显示了。

python
复制代码
>>> type(list1)

<class 'list'>

>>> type(tuple1)

<class 'generator'>

由上面可以看出,list1 和 tuple1 是两种不同的对象,它们之间的区别不仅仅是 tuple1 是一个元组这么简单,它还是 generator。其它的我们先不管,你可以尝试一下在交互模式下输入 dir(tuple1),查看它是否有 iternext,我可以先告诉你,是有的。

既然是有的,那么 tuple1 引用的就是一个迭代器的对象,它的 next() 方法促使它只能向前。

写在之后

迭代器到这就写完了,从内容来看迭代器确实有其过人之处,但是它不是万能的,比如它只能向前,不能回退。还有一个是迭代器并不适合在多线程的环境中对可变集合使用,现在这个东西看起来可能还是有点困难,如果以后有机会写多线程的话,再做解释。

一直想用 Python 和 Selenium 写一个网页爬虫,但一直都没去实现。直到几天前我才决定动手实现它。写代码从 Unsplash 网站上抓取一些漂亮的图片,这看起来好像是非常艰巨的事情,但实际上却是极其简单。

简单图片爬虫的原料
简单图片爬虫的菜谱

以上的所有都安装好了?棒!在我们继续开始写代码前,我先来解释一下以上这些原料都是用来干什么的。

我们首先要做的是利用 Selenium webdrivergeckodriver 来为我们打开一个浏览器窗口。首先,在 Pycharm 中新建一个项目,根据你的操作系统下载最新版的 geckodriver,将其解压并把 geckodriver 文件拖到项目文件夹中。Geckodriver 本质上就是一个能让 Selenium 控制 Firefox 的工具,因此我们的项目需要它来让浏览器帮我们做一些事。

接下来我们要做的事就是从 Selenium 中导入 webdriver 到我们的代码中,然后连接到我们想爬取的 URL 地址。说做就做:

from selenium import webdriver
# 我们想要浏览的 URL 链接
url = "https://unsplash.com"
# 使用 Selenium 的 webdriver 来打开这个页面
driver = webdriver.Firefox(executable_path=r'geckodriver.exe')
driver.get(url)

打开浏览器窗口到指定的 URL。

一个远程控制的 Firefox 窗口。

相当容易对吧?如果以上所说你都正确完成了,你已经攻克了最难的那部分了,此时你应该看到一个类似于以上图片所示的浏览器窗口。

接下来我们就应该向下滚动以便更多的图片可以加载出来,然后我们才能够将它们下载下来。我们还想再等几秒钟,以便万一网络连接太慢了导致图片没有完全加载出来。由于 Unsplash 网站是使用 React 构建的,等个 5 秒钟似乎已经足够”慷慨”了,那就使用 Python 的 time 包等个 5 秒吧,我们还要使用一些 Javascript 代码来滚动网页——我们将会用到 [window.scrollTo()](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo) 函数来实现这个功能。将以上所说的合并起来,最终你的代码应该像这样:

import time
from selenium import webdriver

url = "https://unsplash.com"

driver = webdriver.Firefox(executable_path=r'geckodriver.exe')
driver.get(url)
# 向下滚动页面并且等待 5 秒钟
driver.execute_script("window.scrollTo(0,1000);")
time.sleep(5)

滚动页面并等待 5 秒钟。

测试完以上代码后,你应该会看到浏览器的页面稍微往下滚动了一些。下一步我们要做的就是找到我们要下载的那些图片。在探索了一番 React 生成的代码之后,我发现了我们可以使用一个 CSS 选择器来定位到网页上画廊的图片。网页上的布局和代码在以后可能会发生改变,但目前我们可以使用 #gridMulti img 选择器来获得屏幕上可见的所有 <img> 元素。

我们可以通过 [find_elements_by_css_selector()](http://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.find_element_by_css_selector) 得到这些元素的一个列表,但我们想要的是这些元素的 src 属性。我们可以遍历这个列表并一一抽取出 src 来:

复制代码
import time
from selenium import webdriver

url = "https://unsplash.com"

driver = webdriver.Firefox(executable_path=r'geckodriver.exe')
driver.get(url)

driver.execute_script("window.scrollTo(0,1000);")
time.sleep(5)
# 选择图片元素并打印出他们的 URL
image_elements = driver.find_elements_by_css_selector("#gridMulti img")
for image_element in image_elements:
    image_url = image_element.get_attribute("src")
    print(image_url)

选择图片元素并获得图片 URL。

现在为了真正获得我们找到的图片,我们会使用 requests 库和 PIL 的部分功能,也就是 Image。我们还会用到 io 库里面的 BytesIO 来将图片写到文件夹 ./images/ 中(在项目文件夹中创建)。现在把这些都一起做了,我们要先往每张图片的 URL 链接发送一个 HTTP GET 请求,然后使用 ImageBytesIO 来将返回的图片存储起来。以下是实现这个功能的其中一种方式:

import requests
import time
from selenium import webdriver
from PIL import Image
from io import BytesIO

url = "https://unsplash.com"

driver = webdriver.Firefox(executable_path=r'geckodriver.exe')
driver.get(url)

driver.execute_script("window.scrollTo(0,1000);")
time.sleep(5)
image_elements = driver.find_elements_by_css_selector("#gridMulti img")
i = 0

for image_element in image_elements:
    image_url = image_element.get_attribute("src")
    # 发送一个 HTTP GET 请求,从响应内容中获得图片并将其存储
    image_object = requests.get(image_url)
    image = Image.open(BytesIO(image_object.content))
    image.save("./images/image" + str(i) + "." + image.format, image.format)
    i += 1

下载图片。

这就是爬取一堆图片所需要做的所有了。很显然的是,除非你想随便找些图片素材来做个设计原型,否则这个小小的爬虫用处可能不是很大。所以我花了点时间来优化它,加了些功能:

  • 允许用户通过指定一个命令行参数来指定搜索查询,还有一个数值参数指定向下滚动次数,这使得页面可以显示更多的图片可供我们下载。
  • 可以自定义的 CSS 选择器。
  • 基于搜索查询关键字的自定义结果文件夹
  • 通过截断图片的预览图链接来获得全高清图片
  • 基于图片的 URL 给图片文件命名。
  • 爬取最终结束后关闭浏览器。

你可以(你也应该)尝试自己实现这些功能。全功能版本的爬虫可以在这里下载。记得要先按照文章开头所说的,下载 geckodriver 然后连接到你的项目中。


不足之处,注意事项和未来优化项

整个项目是一个简单的“验证概念”,以弄清楚网页爬虫是如何做的,这也就意味着有很多东西可以做,来优化这个小工具:

  • 没有致谢图片最开始的上传者是个很不好的做法。Selenium 肯定是有能力处理这种情况的,那么每个图片都带有作者的名字。
  • Geckodriver 不应该被放在项目文件夹中,而是安装在全局环境下,并被放到 PATH 系统变量中。
  • 搜索功能可以轻易地扩展到多个查询关键字,那么下载很多类型图片地过程就可以被简化了。
  • 默认浏览器可以用 Chrome 替代 Firefox,甚至可以用 PhantomJS 替代,这对这种类型的项目来说是更好的。

如果你对Python感兴趣的话,可以试试我整理的这份Python全套学习资料,微信扫描下方二维码免费领取

包括:Python永久使用安装包、Python web开发,Python,Python数据分析,人工智能、机器学习等学习教程。带你从零基础系统性的学好Python!
在这里插入图片描述

零基础Python学习资源介绍

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

二、Python学习软件

工欲善其事,必先利其器。学习Python常用的开发软件都在这里了!
在这里插入图片描述

三、Python入门学习视频

还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~在这里插入图片描述

四、Python练习题

每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
在这里插入图片描述

五、Python实战案例

光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。这份资料也包含在内的哈~在这里插入图片描述

六、Python面试资料

我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

七、资料领取

上述完整版Python全套学习资料已经上传CSDN官方,需要的小伙伴可自行微信扫描下方CSDN官方认证二维码输入“领取资料”免费领取!!
在这里插入图片描述