返回博客
指南
Robert Sfichi2021年7月6日阅读时间:8分钟

如何使用 Python 和 Selenium 构建网络爬虫

如何使用 Python 和 Selenium 构建网络爬虫

Selenium 概述

正如 Selenium 官方网站所述,Selenium 是一套用于自动化控制网页浏览器的工具集,最初作为跨浏览器测试工具推出。

Selenium 团队开发的 API 采用 WebDriver 协议来控制 Chrome 或 Firefox 等网页浏览器,并执行各种任务,例如:

  • 填写表单
  • 滚动
  • 截屏
  • 点击按钮

现在你可能在想,这一切如何应用到网页抓取中。其实很简单。

数据提取有时确实让人头疼。如今,即使没有必要,网站也常被建造成单页应用。它们弹出验证码的频率远超必要,甚至会封禁普通用户的IP地址。

简而言之,机器人检测功能令人沮丧,简直就像一个漏洞。

Selenium 能够通过解析并执行 JavaScript 代码,自动化处理网页抓取中的许多繁琐流程,例如滚动页面、抓取 HTML 元素或导出获取的数据,从而在这些情况下提供帮助。

安装

为了展示 Selenium 和 Python 的真正实力,我们将从 /r/learnprogramming 子版块抓取一些信息。除了数据抓取,我还将向您展示如何实现登录功能。既然我们已经了解了主要工具和即将使用的网站,接下来看看还需要安装哪些其他必要组件:

1. Python。我们将使用 Python 3.0。不过,只需稍作调整,您也可以使用 Python 2.0。您可以从这里下载并安装。

2. Selenium 包。你可以使用以下命令安装 Selenium 包:

pip3 install selenium

3. Pandas 包。它将用于提取数据并将抓取到的数据存储在 .csv 文件中。请运行以下命令在您的设备上安装它。

pip3 install pandas

4. BeautifulSoup 包。用于解析 HTML 和 XML 文档。只需运行以下命令:

pip3 install beautifulsoup

5. Google Chrome。点击此链接了解如何下载和安装。

6. Chrome 驱动程序。它将帮助我们配置 Selenium 的 Web 驱动程序。请点击此链接下载并安装最新版本的 chromedriver。请务必记下安装路径。

启动浏览器

让我们开始吧。创建一个新的 scraper.py 文件,并通过复制以下代码行导入 Selenium 包:

from selenium import webdriver

现在,我们将通过以下代码创建一个新的 Google Chrome 实例:

driver = webdriver.Chrome(LOCATION)

请将 LOCATION 替换为您计算机上 Chrome 驱动程序的实际路径。请查阅 Selenium 文档,根据您使用的操作系统查找 Web 驱动程序的最准确路径。

最后一步是访问我们要抓取数据的网站。在本例中,网址为 https://www.reddit.com/r/learnprogramming/top/?t=month。将以下代码行复制到新创建的 Python 文件中:

driver.get("https://www.reddit.com/r/learnprogramming/top/?t=month")

在终端窗口中运行以下命令:

python3 scraper.py

此时应会打开一个新的 Google Chrome 实例,页面顶部会显示“Chrome 正由自动化测试软件控制”。

定位特定数据

想必您已经猜到了,本教程我们将抓取 /r/learnprogramming 子版块。我们将保存帖子的标题、作者、赞数,并将其存储在一个新的 .csv 文件中。让我们看看这些信息在 HTML 页面上的位置,以及如何提取它们。

当 Google Chrome 最终加载完页面后,右键点击任意一篇帖子并选择“检查”。我们可以在 _1oQyIsiPHYt6nx7VOmd1sz 类名下找到该帖子的 HTML 容器。

Browser screenshot showing a Reddit page next to developer tools inspecting the page HTML

您也可以在不显示图形用户界面的情况下运行 Google Chrome,并通过添加几行代码来记录页面的 HTML 内容。我们将为 Chrome 驱动程序设置 headless 选项为 true(以移除图形界面),并将窗口大小设为 1080 像素(以便获取适用于我们用例的正确 HTML 代码)。

最后两行代码会在完成页面 HTML 记录后立即退出 Chrome。

新的 scraper.py 文件将如下所示:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.headless = True
options.add_argument("--window-size=1920,1080")

driver = webdriver.Chrome("./chromedriver")
driver.get("https://www.reddit.com/r/learnprogramming/top/?t=month")

print(driver.page_source)
driver.quit()

WebElement

WebElement 是一个 Selenium 对象,用于表示 HTML 元素。正如您将在后续教程中看到的,我们可以对这些元素执行多种操作,其中包括:

  • 使用 .click() 方法点击该元素
  • 通过调用 .send_keys() 方法向特定输入元素输入文本
  • 使用 element.text 读取元素的文本
  • 通过调用 .is_displayed() 方法检查元素是否显示在页面上

Selenium 实战示例

现在项目已搭建完成,我们可以开始进行网页抓取了。

登录

我们将通过登录 Reddit 账户并抓取之前展示的数据,来展示 Selenium 的强大功能。首先,让我们让 Selenium 点击页面顶部的登录按钮。检查页面 HTML 后,我们可以发现登录按钮的类名是 _2tU8R9NTqhvBrhoNAXWWcP

login_button = driver.find_element_by_class_name('_2tU8R9NTqhvBrhoNAXWWcP')
login_button.click()
Reddit page with a login popup modal blocking the content, illustrating an obstacle when automating browsing

这将打开登录弹窗,其中可见需要填写的用户名和密码输入框。接下来继续执行以下代码:

driver.switch_to_frame(driver.find_element_by_class_name('_25r3t_lrPF3M6zD2YkWvZU'))

driver.find_element_by_id("loginUsername").send_keys('USERNAME')
driver.find_element_by_id("loginPassword").send_keys('PASSWORD')

driver.find_element_by_xpath("//button[@type='submit']").click()

如果检查该模态框元素,我们会发现其容器是一个 iframe。这就是为什么我们在代码开头需要切换到 frame 模式,因为如果不这样做,选择输入框会导致错误。

接下来,我们获取输入字段并输入正确的凭据,然后点击提交按钮。这将带我们返回 /r/learnprogramming 页面,但此时我们已登录并准备好点赞!

截取屏幕截图

使用 Selenium 和 Python 截屏非常简单。你只需在声明 WebDriver 之后,在 scraper.py 文件中编写以下命令即可。

driver.save_screenshot('screenshot.png')

值得注意的是,您可以通过添加以下代码行来设置 Google Chrome 窗口的大小:

from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--window-size=1920,1080")

在我们的示例中,截图效果如下:

Reddit r/learnprogramming page with a cookie consent banner at the bottom of the screen

提取数据

如前所述,我们需要获取帖子的标题、作者以及赞数。首先导入 BeautifulSoup 和 Pandas 包,并为每种所需信息类型创建三个空数组。

from bs4 import BeautifulSoup
import pandas as pd

titles = []
upvotes=[]
authors = []

我们将使用 BeautifulSoup 解析 HTML 文档,编写以下代码:

content = driver.page_source
soup = BeautifulSoup(content, features="html.parser")

成功解析 HTML 文档并选择正确的选择器后,我们将提取标题、赞数和作者信息,并将其赋值给相应的数组:

for element in soup.findAll('div', attrs={'class': '_1oQyIsiPHYt6nx7VOmd1sz'}):
   title = element.find('h3', attrs={'class': '_eYtD2XCVieq6emjKBH3m'})
   upvote = element.find('div', attrs={'class': '_3a2ZHWaih05DgAOtvu6cIo'})
   author = element.find('a', attrs={'class': '_23wugcdiaj44hdfugIAlnX'})
   titles.append(title.text)
   upvotes.append(upvote.text)
   authors.append(author.text)

最后,我们将使用之前导入的 Pandas 包,将这些信息存储到 CSV 文件中。

df = pd.DataFrame({'Post title': titles, 'Author': authors, 'Number of upvotes': upvotes})
df.to_csv('posts.csv', index=False, encoding='utf-8')

就这样!让我们来看看导出的文件:

Spreadsheet view of a posts.csv file listing Reddit post titles, authors, and upvote counts

看起来包含我们所需的所有信息。

额外提示:有时,网站在首次加载时提供的数据并不完整。大多数情况下,数据加载操作会在用户向下滚动时触发。如果您需要向下滚动以获取更多数据,可以使用 .execute_script() 方法,如下所示:

scrollDown = "window.scrollBy(0,2000);"
driver.execute_script(scrollDown)

结语

希望你和我一样享受制作这个网页抓取工具的过程。编程并非总是充满乐趣,但编写这样的小脚本让我回想起初学编程时的时光,也让整个过程变得更有趣。

不过,本教程中构建的脚本尚无法胜任繁重的工作。它缺少几项关键功能,而这些功能正是让网页抓取体验流畅无瑕的关键。例如通过移动或住宅代理连接,以及破解验证码等。

如果您正在寻找更专业的数据提取方案,不妨了解一下 WebScrapingAPI 的功能,亲自验证它是否符合您的需求。该服务提供免费套餐,您只需投入 30 分钟的时间即可体验。

感谢您抽出时间阅读本文。祝您爬取顺利!

关于作者
Robert Sfichi, 全栈开发工程师 @ WebScrapingAPI
Robert Sfichi全栈开发工程师

罗伯特·斯菲奇是 WebScrapingAPI 的团队成员,致力于产品开发,并协助构建可靠的解决方案,以支持该平台及其用户。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。