30 分钟 Python 爬虫教程

长期以来,我一直计划利用 Python 配合 Selenium 编写一个网页爬虫,但迟迟未付诸实践。直到几天前,我决定动手实现它:从 Unsplash 网站抓取一些精美的图片。这听起来似乎是一项艰巨的任务,但实际上实现起来非常简单。


图片来源:Blake Connally 发布于 Unsplash.com

环境准备

在开始之前,请确保你的开发环境已准备好以下“原料”:

  • Python(建议 3.6.3 或更高版本)
  • Pycharm(社区版即可)
  • 必要的 Python 库:

    • pip install requests
    • pip install Pillow
    • pip install selenium
  • geckodriver(详见下文说明)
  • Mozilla Firefox(若未安装)
  • 正常的网络连接
  • 大约 30 分钟的时间(甚至更少)

实战步骤

所有环境安装完毕后,我们先简要说明上述工具的作用,然后开始编写代码。

1. 配置 WebDriver

我们的首要任务是利用 Selenium WebDrivergeckodriver 打开一个浏览器窗口。

  1. Pycharm 中新建一个项目。
  2. 根据你的操作系统下载最新版的 geckodriver
  3. 将其解压,并把 geckodriver 可执行文件拖到项目文件夹中。

geckodriver 本质上是让 Selenium 能够控制 Firefox 浏览器的驱动工具,项目需要它来执行浏览器自动化操作。

2. 打开目标页面

接下来,我们需要从 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 窗口

这一步相当容易。如果一切正确,你应该能看到类似于上图所示的浏览器窗口。至此,你已经攻克了最难的部分。

3. 滚动页面与等待

接下来需要向下滚动页面,以便触发懒加载机制,让更多的图片加载出来。同时,我们需要等待几秒钟,以防网络连接较慢导致图片尚未完全加载。

由于 Unsplash 网站是使用 React 构建的,等待 5 秒钟通常已经足够。我们将使用 Python 的 time 模块进行等待,并使用 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)

测试这段代码后,你应该会看到浏览器页面稍微向下滚动了一些。

4. 定位图片元素

下一步是找到我们要下载的图片。在探索了 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 属性。我们可以遍历列表并逐一提取:

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)

5. 下载并保存图片

为了真正获取找到的图片,我们将使用 requests 库发送 HTTP 请求,并结合 PIL(Pillow)库的 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 并将其连接到你的项目中。


局限性与未来优化

整个项目是一个简单的“概念验证”(Proof of Concept),旨在弄清楚网页爬虫的基本实现原理。这意味着还有很多空间可以优化这个小工具:

  • 版权致谢:没有致谢图片的原始上传者是不太合适的做法。Selenium 有能力获取这些信息,建议为每张图片标注作者名字。
  • 驱动管理geckodriver 不应该仅放在项目文件夹中,最好安装在全局环境下,并添加到系统的 PATH 环境变量中。
  • 搜索扩展:搜索功能可以轻松扩展到支持多个查询关键字,从而简化下载多种类型图片的过程。
  • 浏览器选择:默认浏览器可以用 Chrome 替代 Firefox,甚至可以使用 PhantomJS 替代,这对于此类无头浏览项目来说通常是更好的选择。

学习愉快!

说明:本文教程基于较早版本的 Selenium 库编写。请注意,Selenium 4.0 及以上版本中,find_elements_by_css_selector 等方法已废弃,需改用 find_elements(By.CSS_SELECTOR, ...) 语法;同时 executable_path 参数也建议通过 Service 对象配置。此外,目标网站(Unsplash)的 DOM 结构可能随时间更新,文中提到的 CSS 选择器 #gridMulti img 可能需要根据实际情况调整。