2024年的许多现代网站严重依赖 javascript 使用 React、Angular、Vue.js 等框架呈现交互式数据,这使得网络抓取成为一项挑战。 在本教程中,我们将了解如何使用无头浏览器从动态网页中抓取数据。现有的可用工具有哪些以及如何使用它们?在使用网络浏览器进行抓取时,有哪些常见的挑战、技巧和捷径。
什么是动态网页?
最常遇到的网络抓取问题之一是: 为什么我的抓取器看不到我在网络浏览器中看到的数据? 动态页面使用复杂的 javascript 驱动的网络技术,将处理卸载到客户端。换句话说,它为用户提供了数据和逻辑,但他们必须将它们放在一起才能看到整个呈现的网页。 此类页面的示例非常简单:
<html> <head> <title>Example Dynamic Page</title> </head> <body> <h1>loading...</h1> <p>loading...</p> <script> var data = { title: "Awesome Product 3000", content: "Available 2024 on jingzhengli.com, maybe." }; document.addEventListener("DOMContentLoaded", function(event) { // when page loads take data from variable and put it on page: document.querySelector("h1").innerHTML = data["title"]; document.querySelector("p").innerHTML = data["content"]; }); </script> </body> </html>
在禁用 javascript 的情况下打开此页面,我们不会看到任何内容,只是加载指示器,如旋转的圆圈或要求我们启用 javascript 的文本。这是因为内容是在页面加载后使用 javascript 从变量加载的。因此,抓取器看不到完全呈现的页面,因为它不是能够使用 javascript 加载页面的网络浏览器。
如何抓取动态网页?
抓取时有几种方法可以处理动态 javascript 生成的内容: 首先,我们可以对网站的行为进行逆向工程并将其复制到我们的抓取程序中。 不幸的是,这种方法非常耗时且具有挑战性。它需要深入的网络开发技能和专业工具。
或者,我们可以将一个真正的网络浏览器集成到我们的网络抓取程序中,从而自动抓取动态网页。为此,我们今天将介绍各种浏览器自动化库:Selenium、Puppeteer 和 Playwright。
浏览器自动化如何工作?
Chrome 和 Firefox(及其衍生产品)等现代浏览器带有内置自动化协议,允许其他程序控制这些 Web 浏览器。 目前,有两种流行的浏览器自动化协议:
- 旧的webdriver 协议是通过一个名为webdriver的额外浏览器层实现的。Webdriver 拦截动作请求并发出浏览器控制命令。
- 较新的Chrome DevTools 协议(简称CDP )。与 webdriver 不同,CDP 控制层在大多数现代浏览器中隐式可用。
在本文中,我们将主要介绍 CDP,但这些协议的开发人员体验非常相似,通常甚至可以互换。有关这些协议的更多信息,请参阅官方文档页面Chrome DevTools 协议和WebDriver MDN 文档
抓取任务示例
为了更好地说明这一挑战,我们将使用一个真实世界的网络抓取示例。 我们将从https://www.airbnb.com/experiences抓取在线体验数据。 我们将保持我们的演示任务简短,看看如何完全呈现单个体验页面,如: https: //www.airbnb.com/experiences/2496585并返回完全呈现的内容以供进一步处理。 Airbnb 是最受欢迎的网站之一,它使用 React Javascript 框架生成其动态页面。如果没有浏览器模拟,我们必须先对网站的 javascript 代码进行逆向工程,然后才能看到并抓取其完整的 HTML 内容。 然而,有了浏览器自动化,我们的过程就简单多了:
- 启动网络浏览器(如 Chrome 或 Firefox)。
- 转到页面https://www.airbnb.com/experiences/2496585。
- 等待动态内容页面加载和呈现。
- 抓取整页源内容并使用BeautifulSoup等常用工具对其进行解析
让我们尝试使用 3 种流行的浏览器自动化工具来实现此流程:Selenium、Puppeteer、和Playwright ,看看它们如何匹配!
Selenium
Selenium 是为自动化网站测试而创建的首批大型自动化客户端之一。它支持两种浏览器控制协议:webdriver 和 CDP(仅自 Selenium v4+ 起)。 Selenium 是我们今天列表中最古老的工具,这意味着它拥有相当大的社区和大量功能,并且几乎在所有编程语言中都得到支持,并且几乎可以在所有 Web 浏览器中运行: 语言:Java、Python、C#、Ruby、JavaScript、Perl、PHP、R、Objective-C 和 Haskell 浏览器:Chrome、Firefox、Safari、Edge、Internet Explorer(及其衍生产品 ) while – 意味着大量的免费资源。易于理解的用于常见自动化任务的同步 API。 作为一个缺点,Selenium 的一些功能已经过时并且不是很直观。这也意味着 Selenium 无法利用 Python 的 asyncio 等新语言功能,因此它比其他支持异步客户端 API 的工具(如 Playwright 或 Puppeteer)要慢得多。
让我们来看看如何使用 Selenium webdriver 来解决我们的 airbnb.com 爬虫问题:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.expected_conditions import visibility_of_element_located browser = webdriver.Chrome() # start a web browser browser.get("https://www.airbnb.com/experiences/272085") # navigate to URL # wait for page to load # by waiting for <h1> element to appear on the page title = ( WebDriverWait(driver=browser, timeout=10) .until(visibility_of_element_located((By.CSS_SELECTOR, "h1"))) .text ) # retrieve fully rendered HTML content content = browser.page_source browser.close() # we then could parse it with beautifulsoup from bs4 import BeautifulSoup soup = BeautifulSoup(content, "html.parser") print(soup.find("h1").text)
在上面,我们首先启动一个 Web 浏览器窗口并导航到一个 Airbnb 体验页面。然后我们通过等待第一个标题元素出现在页面上来等待页面加载。最后,我们提取完全加载的 HTML 内容并使用 BeautifulSoup 对其进行解析。 接下来,让我们看看比 Selenium 更快、更现代的替代品:Javascript 的 Puppeteer 和 Python 的 Playwright。
Puppeteer
Puppeteer 是 Google 的 Javascript 异步 Web 浏览器自动化库(以及通过非官方Pyppeteer包提供的 Python)。 语言:Javascript、Python(非官方) 浏览器:Chrome、Firefox(实验性) 优点:CDP 的第一个强大实现,由 Google 维护,旨在成为通用的浏览器自动化工具。 与 Selenium 相比,puppeteer 支持的语言和浏览器更少,但它完全实现了 CDP 协议,并且背后有 Google 强大的团队。 Puppeteer 还将自己描述为一个通用的浏览器自动化客户端,而不是将自己融入网络测试领域——这是一个好消息,因为网络抓取问题得到了官方支持。 让我们看看我们的 airbnb.com 示例在 puppeteer 和 javascript 中的样子:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://airbnb.com/experiences/272085'); await page.waitForSelector("h1"); await page.content(); await browser.close(); })();
如您所见,Puppeteer 示例看起来与我们的 Selenium 示例几乎相同,除了await
指示该程序的异步性质的关键字。Puppeteer 还有一个更友好、更现代的 API,用于等待元素出现在页面上和其他常用功能。
Puppeteer 很棒,但在维护复杂的网络抓取系统时,Chrome 浏览器 + Javascript 可能不是最佳选择。为此,让我们继续我们的浏览器自动化之旅,看看 Playwright,它以更多语言和浏览器实现,使其更易于访问和扩展。
Playwright
Playwright 是 Microsoft 以多种语言提供的同步和异步 Web 浏览器自动化库。 Playwright 的主要目标是可靠的端到端现代 Web 应用程序测试,尽管它仍然实现所有通用浏览器自动化功能(如 Puppeteer 和 Selenium),并且拥有不断增长的网络抓取社区。 语言:Javascript、.Net、Java 和 Python 浏览器:Chrome、Firefox、Safari、Edge、Opera 优点:功能丰富、跨语言、跨浏览器,并提供异步和同步客户端实现。由微软维护。
让我们继续我们的 airbnb.com 示例,看看它在 Playwright 和 Python 中的样子:
# asynchronous example: import asyncio from playwright.async_api import async_playwright async def run(): async with async_playwright() as pw: browser = await pw.chromium.launch() pages = await browser.new_page() await page.goto('https://airbnb.com/experiences/272085') await page.wait_for_selector('h1') return url, await page.content() asyncio.run(run()) # synchronous example: from playwright.sync_api import sync_playwright def run(): with sync_playwright() as pw: browser = pw.chromium.launch() pages = browser.new_page() page.goto('https://airbnb.com/experiences/272085') page.wait_for_selector('h1') return url, page.content()
Playwright 的 API 与 Selenium 或 Puppeteer 的 API 没有太大区别,它既提供同步客户端以实现简单的脚本便利,又提供异步客户端以实现额外的性能扩展。 Playwright 似乎符合浏览器自动化的所有要求:它以多种语言实现,支持大多数网络浏览器,并提供异步和同步客户端。
选择哪一个?
我们已经介绍了三个主要的浏览器自动化客户端:Selenium、Puppeteer 和 Playwright——那么应该选择哪一个呢? 好吧,这完全取决于您正在从事的项目。但是,Playwright 和 Puppeteer 通过提供速度更快的异步客户端,比 Selenium 有很大的优势。 一般来说,Selenium 拥有最多的教育资源和社区支持,但 Playwright 在网络抓取社区中迅速流行起来。 为了结束本介绍,让我们快速了解一下基于浏览器的网络抓取工具必须应对的常见挑战,这可以帮助您决定选择这些库中的哪一个。
挑战和技巧
虽然自动化单个浏览器实例似乎是一项简单的任务,但在网络抓取方面,还有许多额外的挑战需要解决,例如:
- 避免被阻塞。
- 会话持久性。
- 代理集成。
- 缩放和资源使用优化。
不幸的是,没有一个浏览器自动化客户端首先是为网络抓取而设计的,因此这些问题的解决方案必须由每个开发人员通过社区扩展或自定义代码来实现。
指纹识别和封锁
不幸的是,现代网络浏览器提供了太多关于它们自身的信息,以至于它们很容易被识别并可能被阻止访问网站。 对于网络抓取,需要加强自动浏览器以防止指纹识别,这可以通过应用各种隐藏常见漏洞的补丁来实现。
在上面的截图中,我们可以看到一个流行的分析工具creepjs成功地识别了一个 playwright-powered headless 浏览器。
幸运的是,网络抓取社区多年来一直致力于解决这个问题,并且现有的工具可以自动修补主要的指纹识别漏洞: Playwright:
- Playwright Extra for Javascript包含各种抓取插件和浏览器隐形扩展
- Python 的隐身
Puppeteer:
Selenium:
然而,这些工具并不完美,可以很容易地被更高级的指纹识别工具检测到。
使用异步客户端进行扩展
Web 浏览器非常复杂,因此很难使用和扩展。 扩展浏览器自动化的最简单方法之一是并行运行浏览器的多个实例,最好的方法是使用允许同时控制多个浏览器选项卡的异步客户端。换句话说,当一个浏览器选项卡正在加载时,我们可以切换到另一个。
在上面,我们看到同步抓取器如何等待浏览器完成页面加载,然后才能继续。另一方面,异步抓取器同时使用 4 个不同的浏览器选项卡,可以跳过所有等待。 在这个想象的场景中,我们的异步抓取器可以执行 4 个请求,而同步只管理一个请求,但在现实生活中这个数字可能要高得多! 例如,我们将使用 Python 和 Playwright 并安排 3 个不同的 URL 被异步抓取:
import asyncio from asyncio import gather from playwright.async_api import async_playwright from playwright.async_api._generated import Page from typing import Tuple async def scrape_3_pages_concurrently(): async with async_playwright() as pw: # launch 3 browsers browsers = await gather(*(pw.chromium.launch() for _ in range(3))) # start 1 tab each on every browser pages = await gather(*(browser.new_page() for browser in browsers)) async def get_loaded_html(page: Page, url: str) -> Tuple[str, str]: """go to url, wait for DOM to load and return url and return page content""" await page.goto(url) await page.wait_for_load_state("domcontentloaded") return url, await page.content() # scrape 3 pages asynchronously on 3 different pages urls = [ "http://url1.com", "http://url2.com", "http://url3.com", ] htmls = await gather(*( get_loaded_html(page, url) for page, url in zip(pages, urls) )) return htmls if __name__ == "__main__": asyncio.run(scrape_3_pages_concurrently())
在这个简短的示例中,我们启动了三个 Web 浏览器实例,然后我们可以异步使用它们来检索多个页面。
禁用不必要的负载
Web 浏览器处理的大部分是渲染页面显示,这对于 Web 抓取来说不是必需的,因为抓取器没有眼睛。 我们可以通过禁用图像/样式加载和其他不必要的资源来显着提高性能和速度。 例如,在 Playwright 和 Python 中,我们可以实现这些简单的路由选项来阻止图像渲染:
page = await browser.new_page() # block requests to png, jpg and jpeg files await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort()) await page.goto("https://example.com") await browser.close()
这不仅可以加快加载速度,还可以节省带宽,这在使用代理进行网络抓取时非常重要。
常问问题
为了总结这篇文章,让我们来看看一些关于使用无头浏览器进行网络抓取的常见问题,这些问题我们无法完全融入本文:
如何判断它是否是动态网站?
确定网页上是否存在任何动态内容的最简单方法是在浏览器中禁用 javascript并查看是否缺少数据。有时数据可能在浏览器中不可见,但仍存在于页面源代码中——我们可以单击“查看页面源代码”并在那里查找数据。通常,动态数据位于<script>
HTML 标签下的 javascript 变量中。
我应该使用浏览器解析 HTML 还是在我的爬虫代码中解析?
虽然浏览器具有非常强大的 javascript 环境,但通常使用 HTML 解析库(例如Python 中的beautifulsoup)会导致更快、更易于维护的爬虫代码。 一个流行的抓取惯用语是等待动态数据加载,然后将整个呈现的页面源(HTML 代码)拉入抓取代码并在那里解析数据。
我可以使用浏览器自动化来抓取 Web 应用程序或 SPA 吗?
是的,Web 应用程序或单页应用程序 (SPA) 的功能与任何其他动态网站相同。使用浏览器自动化工具包,我们可以点击、滚动和复制普通浏览器可以执行的所有用户交互!
什么是静态页面网站?
静态网站本质上与动态网站相反——所有内容始终存在于页面源代码(HTML 源代码)中。然而,静态页面网站仍然可以使用 javascript 在页面加载时解压或转换此数据,因此浏览器自动化仍然是有益的。
我可以在不使用浏览器自动化的情况下使用 python 抓取 javascript 网站吗?
当谈到在网络抓取动态内容中使用 python 时,我们有两种解决方案:对网站的行为进行逆向工程或使用浏览器自动化。 话虽这么说,中间有很多空间用于利基、创造性的解决方案。例如,Web 抓取中常用的工具是Js2Py,可用于在 python 中执行 javascript。使用此工具,我们可以快速复制一些关键的 javascript 功能,而无需在 Python 中重新创建它。
什么是无头浏览器?
无头浏览器是没有可见 GUI 元素的浏览器实例。这意味着无头浏览器可以在没有显示器的服务器上运行。与有头的同类相比,无头的 chrome 和无头的 firefox 运行速度也快得多,这使它们成为网络抓取的理想选择。
无头浏览器抓取总结
在这篇概述文章中,我们了解了网络抓取上下文中最流行的浏览器自动化库的功能:经典的 Selenium 客户端、更新的 Google 方法 – Puppeteer 和 Microsoft 的 Playwright。 我们为这些工具中的每一个编写了一个简单的 airbnb 体验爬虫,并比较了它们的性能和可用性。最后,我们介绍了常见的挑战,例如无头浏览器阻塞、抓取速度和资源优化。