返回博客
指南
米赫内亚-奥克塔维安·马诺拉切2023年2月3日阅读时间:12分钟

如何使用 Python 运行无头浏览器进行网页抓取:技巧与窍门

如何使用 Python 运行无头浏览器进行网页抓取:技巧与窍门

什么是 Python 无头浏览器?

从宏观角度来看,浏览器是一种允许用户浏览网页并与之交互的计算机程序。无头浏览器也是如此,只不过它没有图形用户界面。这意味着,Python 无头浏览器是一个能够:

  • 访问互联网上的任何网站
  • 渲染网站提供的 JavaScript 文件
  • 与该网页的组件进行交互

考虑到它没有关联的图形用户界面(GUI),这会引发一些关于交互方式的疑问。然而,答案其实很简单。正因为没有GUI,人类无法直接与该页面进行交互。而这就是Web驱动程序发挥作用的地方。Web驱动程序是一种允许进行自我检查和控制的接口。简而言之,Web驱动程序是一类框架,它使我们能够通过编程方式控制各种网页浏览器。

有几种框架支持在 Python 中实现浏览器自动化。但其中最主要的是Selenium。Selenium 是一套主要用于自动化测试的工具集。但在实际应用中,它也被广泛用于网页抓取。

为什么要在 Python 中使用无头浏览器?

根据 Selenium 主页的页眉显示:

“Selenium 用于自动化浏览器。仅此而已!如何利用这一功能,完全取决于你。”

这让我们相信,自动化浏览器具有多种应用场景。但为什么要以无头模式运行它们呢?答案依然很简单。与有头浏览器相比,Python 无头浏览器消耗的资源(CPU 和内存)更少。这主要是因为它无需渲染任何图形元素。

然而,与 Python 的 `requests` 这样的基础 HTTP 客户端相比,少即是多这一原则依然成立。这是因为无头浏览器为了与网页交互并渲染 JavaScript 文件,仍然会启动大量进程。众所周知,`requests` 无法渲染 JavaScript,它只能用于获取原始 HTML。而在当今时代,这对于网页抓取来说远远不够。 大多数现代 Web 平台都高度依赖 JavaScript 来构建 DOM。例如,如果你尝试使用 `curl` 抓取一个 React 应用,你会得到一个空白的网页,提示你“启用 JavaScript”:

<!doctype html>

<html lang="en">

  <head>

 	...

  </head>

  <body>

 	<noscript> You need to enable JavaScript to run this app. </noscript>

 	<div id="root"></div>

  </body>

</html>

虽然无法通过 `requests` 实现这一点,但可以使用无头浏览器来完成。这解答了我们最初提出的问题之一。现代网页爬虫之所以使用无头浏览器而非 `requests`,是因为否则响应结果将无法确定。

无头浏览器的缺点有哪些?

Python 无头浏览器(以及绝大多数自动化浏览器)的主要缺点在于其指纹。如果你关注我的文章,就会知道我有时会提到“隐蔽性”。这指的是自动化浏览器不被察觉的能力。

在 Python 中,无头浏览器很容易辨别。首先,检查浏览器的某个简单属性(例如 `navigator.webdriver`)就能立即判断该浏览器是否由 WebDriver 控制。在网页抓取中,主要挑战之一就是寻找避免被检测的方法。我们称这些方法或技巧为“规避策略”。您可以在此处阅读更多相关内容。

以 Web Scraping API 为例,我们拥有一支专门的团队,一直在致力于完善我们的“隐身模式”。这是为了确保我们的浏览器指纹在每次请求中都是独一无二且无法被检测到的。

Python Selenium 支持的无头浏览器

首先,要知道 Selenium 功能非常强大。而且它并不局限于 Python。C#、Ruby、Java、Python 甚至 JavaScript 都有相应的 Selenium 客户端和 WebDriver。而 Selenium WebDriver 的兼容性更是令人印象深刻。它支持所有主流浏览器:

在网页抓取领域,最常用的 Python 无头浏览器是 Chrome 和 Firefox。我认为这主要是因为这两款浏览器既性能出色,又具备跨平台特性。例如,你可以在 MacOS 环境下开发网页抓取项目,然后轻松将其部署到 Linux 系统上。

如何在 Python 中打开无头浏览器

既然我们已经讲了一些理论概念,我想现在可以放心地进入实践环节了。在本节中,我将向大家展示如何使用 Selenium 构建一个网页爬虫。进行这个项目时,请确保您的电脑已安装 Python 和 Chrome 浏览器。

#1:配置环境

和往常一样,在 Python 中,我们应该将所有内容封装在虚拟环境内。如果你对虚拟环境还不熟悉,请先阅读这篇文章。现在,让我们打开一个新的终端窗口,然后:

  • 创建一个新文件夹
  • 导航至该文件夹
  • 创建一个新的虚拟环境
  • 激活虚拟环境
~ mkdir headless_scraper

~ cd headless_scraper

~ python3 -m venv env

~ source env/bin/activate

#2:安装依赖项

很明显,我们的项目需要 Selenium 和一个 Web 驱动程序。幸运的是,我们可以使用 Python 的包管理器 `pip` 安装这两者。在同一个终端窗口中,输入以下命令:

~ pip install selenium webdriver-manager

现在,你已经为成功做好了充分准备!我们可以开始实际编写代码了。先说明一下,本文主要介绍如何与无头浏览器进行交互。要实现完整的网页抓取方案,还需要付出更多努力。但我相信,只要你持续关注我们的博客文章,很快就能掌握这项技能。

#3:打开自动浏览器

目前,我们已经有了项目,但还没有可以执行的文件。让我们创建一个新的 `.py` 文件,并在 IDE 中打开它:

~ 运行 headles_scraper.py

~ 代码 .

现在您应该已经进入 Visual Studio Code 或您的 IDE 了。您可以先在 `handle_scraper.py` 中导入必要的包:

from selenium import webdriver

from webdriver_manager.chrome import ChromeDriverManager

后者是一个包,可帮助您轻松管理 Selenium 支持的各种浏览器的 Web 驱动程序。您可以在此处了解更多相关信息。接下来,我们将使用 Selenium 创建一个新的浏览器并打开一个网站:

driver = webdriver.Chrome(ChromeDriverManager().install())

driver.get('https://webscrapingapi.com')

现在运行这个文件,你会发现它确实能正常工作。不过,它并没有使用 Python 无头浏览器,而是打开了一个带有界面的 Chrome 窗口:

WebScrapingAPI 主页的焦点图片在浏览器窗口中突出显示了用于网页抓取的 REST API

#4:在无头环境中构建

我们的目标是构建一个资源友好的网页爬虫。因此,理想情况下,我们希望使用 Selenium 打开一个无头浏览器。幸运的是,有一种简单的方法可以将 Selenium 从有头模式切换为无头模式。我们只需利用Chrome WebDriver 的选项即可。那么,让我们导入 `Options` 并添加两行代码:

...

from selenium.webdriver.chrome.options import Options

...

options = Options()

options.headless = True

driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)

driver.get('https://webscrapingapi.com')

再次运行脚本。如您所见,这次没有弹出窗口。但它真的在后台运行吗?一个快速可视化并验证的方法是使用 Selenium 截取屏幕截图。只需在脚本末尾添加以下这行代码:

driver.get_screenshot_as_file('headless.png')

如果一切顺利,你看到的画面应该和我的一样:

WebScrapingAPI 的“亮点”板块,其中包含一个“立即开始”按钮,用于推广用于网页抓取的 REST API

#5:添加数据抓取功能

什么是网页爬虫?简而言之,网页爬虫是一种通过调用服务器端点并从中收集数据的程序。对于网站而言,这些数据通常由 HTML 文件组成。但如今某些服务器也会返回 JSON 对象。因此,我们姑且统称为“数据”。在接下来的章节中,让我们设定更高的目标,尝试使用面向对象编程!我们的目标是:

  • 创建一个 Scraper 类
  • 添加一个用于提取原始数据的方法
  • 添加一个从单个元素中提取数据的方法
  • 添加一个方法,用于从同一类的元素中提取数据

因此,我们需要实现三种基本方法。不过,从学习的角度来看,这三种方法不仅为我们打开了网络爬虫的大门,也让我们得以接触 Python 的面向对象编程。我觉得这真的很酷!现在,让我们删除之前编写的所有代码,从头开始:

from selenium import webdriver

from selenium.webdriver.chrome.options import Options

from selenium.webdriver.common.by import By

from selenium.webdriver.remote.webelement import WebElement

class Scraper:

   def __init__(self, headless: bool = True) -> None:

       self.headless = headless

       pass

   def setup_scraper(self) -> None:

       self.options = Options()

       self.options.headless = self.headless

       self.driver = webdriver.Chrome(options=self.options)

   def navigate(self, target) -> None:

       self.driver.get(target) if target else print('[!] 未提供目标。 请指定一个 URL.')

   def extract_raw_data(self) -> str:

       return self.driver.page_source

   def extract_single_element(self,  selector: str, selector_type: By = By.CSS_SELECTOR) -> WebElement:

      return self.driver.find_element(selector_type, selector)

  

   def extract_all_elements(self, selector: str, selector_type: By = By.CSS_SELECTOR) -> list[WebElement]:

       return self.driver.find_elements(selector_type, selector)

我添加了类型注解,主要是为了便于理解,而非提升性能。这样,你就能从 I/O 的角度直观地理解这个应用程序。现在,这些方法基本上不言自明。我们并没有对数据进行任何操作,只是将其返回。如果你愿意,这可以作为你使用 Python 无头浏览器构建复杂爬虫的起点。

到目前为止,运行该文件没有任何效果。这是因为我们只是声明了 Scraper 及其方法。现在我们需要使用它们。因此,让我们添加以下代码:

# 初始化一个新的 Scraper 并导航至目标页面

scraper = Scraper()

scraper.setup_scraper()

scraper.navigate('https://httpbin.org')

# 提取并打印整个 HTML 文档

raw_data = scraper.extract_raw_data()

print(raw_data)

# 根据类名提取并打印某个元素

single_element = scraper.extract_single_element('title', By.CLASS_NAME)

print(single_element.text)

# 提取并打印属于某个标签类型的所有元素

all_elements = scraper.extract_all_elements('a', By.TAG_NAME)

print([el.get_attribute('href') for el in all_elements])

就这样。现在运行你的脚本,你就会看到一些操作在进行。再次强调,这仅仅是一个旨在帮助你入门的原型。如果你想进一步了解如何在网页抓取中使用 Python 无头浏览器,我建议你尝试:

这样一来,你既能学到知识,又能为自己的作品集增添一个项目。

除了 Python 无头浏览器之外,还有哪些最佳替代方案?

Python 是构建网络爬虫最流行的编程语言之一。然而,它并非唯一的解决方案,也并非最佳选择!在本节中,我们将探讨 Python 无头浏览器的替代方案。我们将首先探讨为何要寻找替代方案,并结合具体示例进行说明。

选择现成的解决方案而非自己开发 Python 网络爬虫的主要原因在于资源问题。一套完整的网络爬虫解决方案需要您实现 IP 轮换系统、采用一些规避技术、考虑性能问题,这些还只是其中的一部分。因此,开发网络爬虫不仅成本高昂,而且耗时费力。更不用说维护基础设施还会产生更多的成本。

Python 无头浏览器的第二个缺点与性能有关。虽然 Python 是一门优秀的语言,且非常易于使用,但它并不以速度见长。例如,与 Java(它也有 Selenium 包)不同,Python 既是动态类型语言,又是解释型语言。相比其他语言,这两项特性使其运行速度明显较慢。现在我们已经有了一个大致的了解,让我们来具体探讨一下。 以下是 Selenium 和 Python 无头浏览器的五大替代方案:

#1:网页抓取 API

如果您想解决我们指出的第一个缺点,那么您需要考虑使用第三方爬虫服务商。而 Web Scraping API 提供了一套完整的爬虫解决方案。此外,我们的服务还具备以下丰富功能:

  • 适用于数据中心和住宅代理的IP轮换系统
  • 隐身模式
  • 验证码破解工具

仅凭这三点,目标网站就几乎不可能追上我们的爬虫并将其拦截。此外,还有丰富的爬取功能。借助 Web Scraping API,您可以根据选择器提取数据、切换设备类型、截取屏幕截图,以及使用更多功能。完整的功能列表请参见此处

#2:Puppeteer

Puppeteer相当于 JavaScript 版的 Selenium。它是 Web 自动化领域使用最广泛的库之一。与 Selenium 不同,Puppeteer 的默认状态是无头模式。因此,您无需额外添加代码即可使其运行于无头模式。 更有趣的是,Puppeteer 还提供了 Python 版本的 API 实现。您可以阅读这篇博客,其中我详细介绍了如何使用 Pyppeteer 构建网络爬虫。

#3:剧作家

Playwright是微软贡献者开发的另一款 Web 自动化工具。它之所以广受欢迎,主要是因为它支持多种语言和平台。他们的口号实际上是“任何浏览器,任何平台”。其 API 可在任何操作系统上通过以下任一语言进行调用:

以上是 Python 无头浏览器的几种主要替代方案。当然,还有其他工具可供选择。ZombieJSHtmlUnit只是众多选项中的两个。我认为选择哪种技术既取决于性能,也取决于个人偏好。因此,我建议您试用所有这些工具,然后挑选最适合您的那一款。

结论

使用 Python 无头浏览器有利有弊。一方面,你可以构建一个定制化的解决方案,并随时添加更多功能;另一方面,开发和维护成本可能相当高。此外,还有隐蔽性方面的问题。如果你需要专业的解决方案,我认为最好选择第三方服务商。但如果是出于学习目的,我始终鼓励大家去尝试和探索这项技术。

关于作者
Mihnea-Octavian Manolache,全栈开发工程师 @ WebScrapingAPI
米赫内亚-奥克塔维安-马诺拉什全栈开发工程师

Mihnea-Octavian Manolache 是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

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

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