以下是一个使用Playwright进行数据采集并进行基本处理的示例代码。这个示例将展示如何从一个假设的博客列表页面抓取文章标题和链接,并将这些数据保存到本地的JSON文件中。请注意,实际应用时应确保遵守目标网站的robots.txt
规则和使用条款。
Javascript
1const { chromium } = require('playwright');
2const fs = require('fs');
3
4(async () => {
5 // 启动Chromium浏览器
6 const browser = await chromium.launch();
7 const context = await browser.newContext();
8 const page = await context.newPage();
9
10 // 目标网站URL,这里以一个虚构的博客列表页为例
11 const targetUrl = 'https://example-blog-list.com';
12
13 try {
14 // 导航至目标页面
15 await page.goto(targetUrl);
16
17 // 等待文章列表加载完成,这里假设文章标题位于特定的类名下
18 await page.waitForSelector('.post-title');
19
20 // 提取所有文章标题和链接
21 const articles = await page.$$eval('.post-title a', links => {
22 return links.map(link => ({
23 title: link.textContent.trim(),
24 href: link.href
25 }));
26 });
27
28 console.log('Collected articles:', articles);
29
30 // 将数据保存到本地JSON文件
31 fs.writeFileSync('articles.json', JSON.stringify(articles, null, 2));
32
33 console.log('Data has been saved to articles.json');
34 } catch (error) {
35 console.error('An error occurred during scraping:', error);
36 } finally {
37 // 关闭浏览器
38 await browser.close();
39 }
40})();
这段代码首先使用Playwright启动Chromium浏览器,然后导航至一个假设的博客列表页面。接着,通过CSS选择器等待并提取所有的文章标题和链接。这些数据通过$$eval
方法被收集到JavaScript对象数组中,每个对象包含了文章的标题和链接。最后,这些数据被写入到名为articles.json
的本地文件中。
请确保在使用此代码前安装了所需的依赖,即playwright
和处理文件的fs
模块(Node.js自带)。此外,根据实际目标网站的结构调整CSS选择器,以正确匹配和提取数据。
在前面的示例中,我们学习了如何使用Playwright进行基本的数据采集并将其保存为JSON文件。为了进一步提升爬虫的功能和效率,接下来我们将探索如何添加额外的特性,包括:
- 处理分页或无限滚动:许多网站使用分页或无限滚动来展示大量数据。学会处理这些情况,可以让你的爬虫收集到更多页面上的数据。
- 动态数据加载的处理:有些网站的数据是通过Ajax或其他异步方式加载的,需要特定的策略来等待和提取这些数据。
- 并发控制:当爬取大量页面或数据时,合理利用并发可以显著提升效率,但同时也需要注意避免对目标网站造成过大压力。
- 异常处理和重试机制:增强爬虫的健壮性,确保在遇到错误时能够自动恢复或至少记录下来,以便分析和调整策略。
处理分页或无限滚动
假设目标网站使用分页显示数据,每页有10篇文章,下面是如何实现自动翻页抓取数据的例子:
在前面的示例中,我们学习了如何使用Playwright进行基本的数据采集并将其保存为JSON文件。为了进一步提升爬虫的功能和效率,接下来我们将探索如何添加额外的特性,包括:
- 处理分页或无限滚动:许多网站使用分页或无限滚动来展示大量数据。学会处理这些情况,可以让你的爬虫收集到更多页面上的数据。
- 动态数据加载的处理:有些网站的数据是通过Ajax或其他异步方式加载的,需要特定的策略来等待和提取这些数据。
- 并发控制:当爬取大量页面或数据时,合理利用并发可以显著提升效率,但同时也需要注意避免对目标网站造成过大压力。
- 异常处理和重试机制:增强爬虫的健壮性,确保在遇到错误时能够自动恢复或至少记录下来,以便分析和调整策略。
处理分页或无限滚动
假设目标网站使用分页显示数据,每页有10篇文章,下面是如何实现自动翻页抓取数据的例子:
Javascript
1async function scrapePages(page, totalPages) {
2 let allArticles = [];
3 for (let i = 1; i <= totalPages; i++) {
4 const url = `https://example-blog-list.com/page/${i}`;
5 await page.goto(url);
6 await page.waitForSelector('.post-title'); // 确保当前页面数据加载完成
7
8 const articles = await page.$$eval('.post-title a', links => {
9 return links.map(link => ({
10 title: link.textContent.trim(),
11 href: link.href
12 }));
13 });
14
15 allArticles = allArticles.concat(articles);
16 console.log(`Page ${i} scraped.`);
17 }
18 return allArticles;
19}
20
21// 调用该函数,假设我们知道总共有10页
22scrapePages(page, 10).then(articles => {
23 fs.writeFileSync('all_articles.json', JSON.stringify(articles, null, 2));
24});
动态数据加载
对于动态加载的数据,可以监听网络请求,找到加载更多数据的请求,并模拟该请求获取数据,或者等待数据加载的指示元素消失。
Javascript
1await page.waitForFunction(() =>
2 document.querySelector('.loading-indicator').offsetParent === null
3);
并发控制
利用Playwright的上下文和页面管理,可以实现简单的并发抓取。下面是一个简单的并发示例:
Javascript
1const contexts = await Promise.all(Array.from({ length: 9 }, () => browser.newContext()));
2const pages = await Promise.all(contexts.map(context => context.newPage()));
3
4await Promise.all(pages.map((page, index) => page.goto(`https://example-blog-list.com/page/${index + 1}`)));
5
6// 在这里进行数据抓取操作...
7
8await Promise.all(contexts.map(context => context.close()));
异常处理和重试
在爬虫中加入重试逻辑,当遇到网络错误或页面加载失败时,可以尝试重新执行操作。
Javascript
1async function safeGoto(page, url, retries = 3) {
2 while (retries > 0) {
3 try {
4 await page.goto(url, { waitUntil: 'networkidle' });
5 return;
6 } catch (error) {
7 console.error(`Failed to load ${url}, retrying...`);
8 retries--;
9 if (retries === 0) throw error;
10 await new Promise(resolve => setTimeout(resolve, 5000)); // 重试间隔
11 }
12 }
13}
通过这些高级特性的添加,你的爬虫将变得更加灵活、高效和健壮,能够应对更多复杂场景下的数据采集需求。不过,始终记得在开发和使用爬虫时遵守法律法规,尊重网站的使用条款,合理安排爬取频率,避免给目标网站带来不必要的负担。
Javascript
1async function scrapePages(page, totalPages) {
2 let allArticles = [];
3 for (let i = 1; i <= totalPages; i++) {
4 const url = `https://example-blog-list.com/page/${i}`;
5 await page.goto(url);
6 await page.waitForSelector('.post-title'); // 确保当前页面数据加载完成
7
8 const articles = await page.$$eval('.post-title a', links => {
9 return links.map(link => ({
10 title: link.textContent.trim(),
11 href: link.href
12 }));
13 });
14
15 allArticles = allArticles.concat(articles);
16 console.log(`Page ${i} scraped.`);
17 }
18 return allArticles;
19}
20
21// 调用该函数,假设我们知道总共有10页
22scrapePages(page, 10).then(articles => {
23 fs.writeFileSync('all_articles.json', JSON.stringify(articles, null, 2));
24});
动态数据加载
对于动态加载的数据,可以监听网络请求,找到加载更多数据的请求,并模拟该请求获取数据,或者等待数据加载的指示元素消失。
Javascript
1await page.waitForFunction(() =>
2 document.querySelector('.loading-indicator').offsetParent === null
3);
并发控制
利用Playwright的上下文和页面管理,可以实现简单的并发抓取。下面是一个简单的并发示例:
Javascript
1const contexts = await Promise.all(Array.from({ length: 9 }, () => browser.newContext()));
2const pages = await Promise.all(contexts.map(context => context.newPage()));
3
4await Promise.all(pages.map((page, index) => page.goto(`https://example-blog-list.com/page/${index + 1}`)));
5
6// 在这里进行数据抓取操作...
7
8await Promise.all(contexts.map(context => context.close()));
异常处理和重试
在爬虫中加入重试逻辑,当遇到网络错误或页面加载失败时,可以尝试重新执行操作。
Javascript
1async function safeGoto(page, url, retries = 3) {
2 while (retries > 0) {
3 try {
4 await page.goto(url, { waitUntil: 'networkidle' });
5 return;
6 } catch (error) {
7 console.error(`Failed to load ${url}, retrying...`);
8 retries--;
9 if (retries === 0) throw error;
10 await new Promise(resolve => setTimeout(resolve, 5000)); // 重试间隔
11 }
12 }
13}
通过这些高级特性的添加,你的爬虫将变得更加灵活、高效和健壮,能够应对更多复杂场景下的数据采集需求。不过,始终记得在开发和使用爬虫时遵守法律法规,尊重网站的使用条款,合理安排爬取频率,避免给目标网站带来不必要的负担。