跳到内容

使用 Playwright 抓取单页应用程序:深入指南

单页应用程序(SPA)已成为现代 Web 开发的规范。与传统的多页面网站不同,SPA 使用 JavaScript 动态更新内容并呈现页面,而无需重新加载整个页面。这为用户创造了流畅的、类似应用程序的体验。

然而,对客户端 JavaScript 和异步数据加载的日益依赖给从单页应用程序中抓取数据带来了独特的挑战。传统的抓取工具存在不足,因为它们是为静态站点和 HTML 解析而设计的。

在这本超过 3200 字的综合指南中,您将学习经过验证的技术来解决使用 Playwright 抓取现代 SPA 时面临的常见障碍。

为什么刮擦 SPA 具有挑战性

在我们深入研究解决方案之前,首先了解单页应用程序难以抓取的原因非常重要。

大量使用客户端 JavaScript

服务器最初提供的 HTML 本质上是页面的静态外壳。实际内容是通过 JavaScript 在客户端动态生成和呈现的。这意味着大部分数据仅存在于 JavaScript 对象和 DOM 元素中,而不是存在于初始 HTML 源中。

异步数据加载

SPA 经常在后台异步获取新内容并更新页面,而无需完全重新加载。当页面首次加载时,数据通常无法预先获得。

根据 Radware 的指标,网页在渲染时平均会向外部资源发出 100 多个请求。

平均页面请求数
201133
201656
2019105

由于大量使用 AJAX 等技术,当您尝试提取数据时,您需要的数据可能仍在后台加载。这会导致不完整的数据被抓取。

动态 DOM 操作

SPA 上呈现的组件和元素可以根据用户输入快速更改。当用户与应用程序交互时,内容会动态生成、添加、删除或更新。

尝试通过元素的初始 DOM 位置来定位元素是很脆弱的,因为它变化如此频繁。

对 API 和 AJAX 请求的依赖

SPA 广泛使用 REST API、GraphQL、WebSocket 和 AJAX 请求从后端服务器获取数据。然后内容在客户端呈现。

对于只能看到初始 HTML 响应的传统抓取方法来说,客户端和服务器之间的数据交换是不可见的。

经过身份验证的会话和状态

复杂的 SPA 经常要求用户在访问私人内容和数据之前登录。需要在抓取脚本中正确维护此身份验证状态。

必须处理存储会话 ID、用户 ID 和令牌的 Cookie,以模拟经过身份验证的用户会话。

JavaScript 执行的必要性

与静态站点不同,单纯解析 HTML 对于 SPA 来说是不够的。页面必须通过在类似浏览器的环境中执行 JavaScript 来呈现,以生成最终的数据结构。

像 Playwright 这样的无头浏览器提供了这种功能,可以产生抓取 SPA 所需的真实最终用户体验。

这些挑战使得有效的 SPA 抓取与传统的网页抓取截然不同。现在让我们看看 Playwright 如何帮助您克服这些障碍。

为什么使用 Playwright 来抓取 SPA?

Playwright 是一个 Node.js 库,用于自动化流行的 Web 浏览器(如 Chromium、Firefox 和 WebKit)。与 SPA 抓取相关的关键功能包括:

无头浏览器自动化

Playwright 可以在不渲染可见 UI 的情况下驱动浏览器,称为无头模式。这允许执行 JavaScript 密集页面来填充数据。

等待元素和条件

智能内置等待机制通过在交互之前等待元素或函数达到所需状态来防止抓取错误。

模拟 API 请求

Playwright 允许拦截请求并使用模拟数据进行响应,而不是调用真实的 API。这可以抓取 AJAX 数据。

响应式测试

模拟移动设备、地理位置和 CPU 节流来处理响应式设计测试需求。

跟踪查看器

可视化 Playwright 脚本以了解确切的浏览器交互并诊断问题。

自动处理弹出窗口、对话框

Playwright 自动处理警报、确认、提示、验证请求和下载,从而简化脚本逻辑。

选择器和 DOM API

丰富的 API 可通过 CSS 选择器提取数据或像常规网页一样直接遍历 DOM 元素。

这些功能使 Playwright 非常适合应对单页 Web 应用程序带来的挑战。 Puppeteer、Selenium 和 HtmlUnit 等主要替代方案虽然对于一般浏览器测试很有用,但缺乏 Playwright 用于有效 SPA 抓取的强大功能集。

接下来,让我们通过一些代码示例来演示使用 Playwright 的关键抓取模式。

使用 Playwright 抓取 SPA 的模式

下面我们将探讨一些常见的抓取技术,以克服特定的 SPA 挑战。

等待内容加载

SPA 抓取的最基本挑战之一是在提取之前留出时间加载内容。

我们需要等待异步 JavaScript 渲染完成填充页面,而不是尝试立即提取数据。

剧作家的 page.waitForSelector() 方法允许在执行进一步命令之前等待特定选择器出现:

// Navigate to SPA
await page.goto(‘https://spa.com‘);

// Wait for content to load
await page.waitForSelector(‘.content‘);

// Extract data now that .content exists
const data = await page.$eval(‘.content‘, elem => elem.textContent); 

这会等到具有类的元素 content 在提取文本内容之前在 DOM 中可用。

没有这个等待, .content 如果仍然异步加载导致错误,则可能尚不存在。这种简单的延迟使 SPA 有时间获取和呈现新数据,从而实现后续提取。

等待函数

在某些情况下,我们可能需要等待更复杂的 JavaScript 条件成立,而不是等待简单的选择器。这里我们可以使用 page.waitForFunction():

// Wait for data to load
await page.waitForFunction(() => {
  return window.store.articles.length > 0 ;
});

// Store now has loaded articles
const articles = await page.evaluate(() => {
  return window.store.articles; 
});

这会轮询页面,直到自定义 window.store.articles 读取数据之前条件返回 true。

智能等待选择器和条件可防止由于异步加载页面数据而导致抓取失败。

处理动态内容更新

单页面应用程序可以动态更新内容以响应用户输入和事件,而无需重新加载页面。

一个常见的例子是无限滚动,当用户向下滚动时会附加新元素。

为了处理动态添加的元素,我们可以使用突变观察器监听 DOM 更改:

// Monitor mutations
await page.evaluate(() => {

  const observer = new MutationObserver(mutations => {
    console.log(‘Added nodes:‘, mutations[0].addedNodes);
  });

  observer.observe(document, { 
    childList: true,
    subtree: true
  });

});

observer 每当新元素添加到页面主体时都会收到通知。然后我们可以触发我们的抓取逻辑来响应这些突变。

这允许适应内容更新,而不仅仅是处理初始页面加载。

模拟 API 请求

SPA 广泛使用 REST 和 GraphQL API 来获取客户端数据。

为了拦截这些请求,我们可以在 Playwright 中定义路由来模拟响应:

await page.route(‘/api/articles‘, route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify([
      {title: ‘Article 1‘},
      {title: ‘Article 2‘}  
    ])
  }); 
});

// Mock response will be returned from /api/articles
await page.goto(‘/page-that-calls-api‘) 

当 SPA 尝试呼叫时 /api/articles,我们的处理程序将使用定义的假响应进行响应,而不是调用真正的 API。

这允许抓取 API 数据而不会产生副作用。我们可以构建强大的响应来处理 SPA 代码可能期望的不同场景。

验证会话

抓取 SPA 中的私人帐户区域需要正确处理身份验证。

一个简单的方法是在抓取之前通过 UI 正常登录:

// Navigate to login page
await page.goto(‘/login‘);

// Enter credentials and submit form 
await page.type(‘#email‘, ‘[email protected]‘);
await page.type(‘#password‘, ‘secret‘);
await page.click(‘#submit‘);

// Session now authenticated
// Crawl member pages 

这利用了 Playwright 的功能来自动填写表单并单击创建经过身份验证的浏览器会话。

为了获得最佳结果,请在 beforeAll 挂钩并重复使用 browserpage 在整个测试中共享 cookie 的上下文。

响应式设计处理

SPA 经常根据不同的设备尺寸调整其布局和内容。为了测试这些响应场景,我们可以使用以下命令来模拟移动浏览器 page.emulate():

await page.emulate({
  viewport: {
    width: 400,  
    height: 800
  },
  userAgent: ‘...‘,
});

设置 iPhone 视口和用户代理允许像移动设备一样渲染页面。

将仿真与 waitForSelector 并且您可以可靠地处理响应式设计。

模拟不同的环境有助于确保您的抓取工具适应桌面和移动设备上的 SPA。

刮刀助手库

类似的服务 阿皮菲蜜蜂 提供基于 Playwright 的库,可智能处理等待内容、自动滚动动态页面更新、限制请求等。

这些工具可以简化您自己编写强大的 SPA 抓取脚本的过程。

实用剧作家Scraper脚本

现在让我们将这些方法整合到一个现实世界中的假设 SPA 的爬虫中:

const { chromium } = require(‘playwright‘);

(async () => {

  const browser = await chromium.launch();
  const page = await browser.newPage();  

  // Login to scrape private content
  await page.goto(‘/login‘);
  await page.type(‘#email‘, ‘[email protected]‘);
  await page.type(‘#password‘, ‘secret‘); 
  await page.click(‘#submit‘);

  await page.waitForNavigation();

  // Navigate to SPA
  await page.goto(‘/app‘);

  // Wait for content to load
  await page.waitForSelector(‘.content‘);

  // Monitor mutations
  page.evaluate(() => {
    new MutationObserver().observe(document, {
      childList: true 
    });    
  });

  // Mock API response
  page.route(‘/api/articles‘, route => {
    route.fulfill({ /*...mock response...*/ }); 
  });

  // Extract content 
  const data = await page.evaluate(() => {
    const content = document.querySelector(‘.content‘);
    return content.innerText;
  });

  console.log(data);

  await browser.close();

})();

该脚本登录私有应用程序,等待加载经过身份验证的内容,处理动态突变,模拟 API 响应并将数据提取到 const data.

这些技术可用于为现实世界的 SPA 开发强大的刮刀。

大规模取消 SPA

对于大型 SPA,仅手动抓取几页可能就足够容易了。然而,在爬行数千或数百万个页面时,需要智能解决方案。

抓取 API 服务

网页抓取 API 如下 爬虫API 大规模处理浏览器自动化、cookie、代理和轮换。这简化了抓取 JavaScript 密集型网站(包括 SPA)的过程。

无头浏览器农场

类似的服务 无浏览器深信服云浏览器 提供可通过 API 访问的大型 Playwright 和 Puppeteer 实例集群。这些并行实例允许大规模分布式抓取 SPA。

托管爬虫

托管爬虫无需运行您自己的抓取基础设施,例如 爬虫代理爬取 处理编排浏览器、代理和自动化以爬行复杂的网站。

网页抓取机器人

像工具一样 幻影破坏者, 德西解析中心 无需编码即可为 SPA 提供抓取工具的点击式配置。这些机器人自动检测页面内容、等待、点击等,从而实现无代码设置。

根据您的使用案例,利用这些企业级服务之一可能比构建您自己的大规模 SPA 抓取抓取基础设施更有效。

更简单的选择:Crawlee

克劳利 为 JavaScript 呈现的网站提供创新的网络爬虫即服务。

它自动处理常见的抓取挑战,例如:

  • 在提取之前等待元素或 URL 加载
  • 验证会话并存储 cookie
  • 拦截API请求并处理AJAX数据
  • 滚动无限滚动页面
  • 重新运行失败的提取以提高弹性

Crawlee 可以开箱即用地抓取复杂的 SPA,而无需编写用于等待、身份验证、AJAX 处理等的 Playwright 脚本。

主要功能:

  • 通过可视化界面而不是编码进行配置
  • 在提取数据之前自动等待 URL 和选择器
  • 有状态爬行跨页面传递 cookie
  • API请求拦截以处理XHR、Fetch和JSON数据
  • 默认情况下无头 Chrome 渲染
  • 用于检查和调试爬行的可视化工具
  • 水平可扩展的分布式爬虫后端

这甚至可以简化复杂的 JavaScript Web 应用程序的抓取,而无需 Playwright 编码。 Crawlee 的爬虫即服务非常适合不想管理自己的爬虫基础设施的用户。

支持的应用程序包括:

  • React 和 Next.js 应用程序
  • Angular SPA
  • Vue.js 页面
  • Webpack 站点
  • AJAX 重型页面
  • PWA 和 Electron 应用程序
  • 动态和响应式设计

为等待条件、经过身份验证的会话和动态内容更改等抓取挑战提供统包支持,使 Crawlee 成为 SPA 抓取的引人注目的选择,而无需编写复杂的脚本。

结论

抓取现代单页应用程序需要模拟用户交互并等待异步 JavaScript 活动。 Playwright 提供了出色的浏览器自动化功能来克服这些挑战。

本指南涵盖的关键策略包括:

  • 在提取之前等待初始内容和动态更新加载
  • 监听 DOM 更改以检测正在渲染的新内容
  • 拦截REST API和GraphQL请求以访问后端数据
  • 模拟移动设备和节流来处理响应式设计
  • 验证会话并管理 cookie 以访问私人用户数据

遵循这些模式将帮助您为严重依赖客户端 JavaScript 和 API 的复杂 SPA 开发可维护的 Playwright scraper。

大规模地利用抓取 API 服务、无头浏览器农场和托管爬虫可能比构建自己的 Playwright 基础设施更有效。

虽然编写 Playwright 脚本可以提供最大的灵活性,但像 Crawlee 这样的工具可以为 SPA 提供更简单的统包抓取服务,而无需自己编写浏览器自动化脚本。

我希望本指南能让您牢牢掌握使用 Playwright 抓取具有挑战性的单页应用程序的技术。如果您还有其他问题,请告诉我!

加入谈话

您的电邮地址不会被公开。 必填带 *