0

0

Puppeteer 多页批量爬取与 MongoDB 数据持久化完整教程

霞舞

霞舞

发布时间:2026-01-09 17:03:02

|

304人浏览过

|

来源于php中文网

原创

Puppeteer 多页批量爬取与 MongoDB 数据持久化完整教程

本文详解如何使用 puppeteer 稳健、可扩展地批量爬取多分页电商列表页(如 maxiscoot),精准提取商品标题、价格、品牌、sku、库存状态及图片链接,并统一存入 mongodb,解决常见“漏抓”“重复覆盖”“选择器失效”等实战痛点。

在实际网页爬虫开发中,仅循环 goto() 多个 URL 并在单页 evaluate() 中批量提取元素,极易因页面未完全加载、动态渲染延迟、弹窗干扰或分页逻辑硬编码而丢失数据——正如原代码中仅获取 7–60 条而非预期的 200+ 条。以下是一套生产就绪(production-ready)的 Puppeteer 多页爬取方案,兼顾健壮性、可维护性与可扩展性

✅ 核心改进点说明

  1. 动态分页识别:不再硬编码页数(如 PAGES = 4),而是通过 $$eval 定位分页导航栏末尾链接(.element_sr2__page_link:last-of-type),自动读取最大页码,避免漏页或越界;
  2. 逐页独立处理:每页 goto 后均执行 waitForSelector('.element_product_grid'),确保商品容器已渲染完成;跳过首页重载(i !== 0 时才拼接 ?p=1 参数),提升效率;
  3. 精准元素定位:使用 page.$$('a.element_artikel') 获取所有商品锚点节点,再对每个节点调用 product.$eval() 进行局部上下文查询——这比全局 querySelectorAll 更可靠,彻底规避跨商品 DOM 错位(如价格与图片索引不匹配);
  4. 弹窗自动化处理:检测并点击 Cookie 同意按钮(.cmptxt_btn_yes),防止遮罩层阻断后续操作;
  5. 超时与加载策略强化:goto(..., { waitUntil: 'networkidle2', timeout: 30000 }) 确保资源基本加载完毕,避免 waitForSelector 因网络抖动失败;
  6. 模块化解耦:分离 getLinks()(菜单导航抓取)、scrapeData()(单品类分页爬取)、saveDataToMongoDB()(存储),便于复用与测试。

? 完整可运行代码(含注释)

const puppeteer = require('puppeteer');
const { MongoClient } = require('mongodb');

// ? 主执行流程:启动浏览器 → 获取分类链接 → 并行爬取 → 汇总存库
(async () => {
  // 【1】获取目标分类链接(如 /haut-moteur/)
  async function getLinks(baseURL) {
    const browser = await puppeteer.launch({ headless: 'new' });
    const page = await browser.newPage();
    await page.goto(baseURL, { waitUntil: 'networkidle2', timeout: 30000 });
    await page.waitForSelector('header');

    // 提取所有二级菜单链接及其文本关键词
    const links = await page.$$eval('a.sb_dn_flyout_menu__link', els =>
      els.map(el => ({
        link: el.getAttribute('href'),
        keyword: el.textContent.trim()
      }))
    );

    // ✅ 手动配置需爬取的路径白名单(安全可控)
    const targetPaths = ['/haut-moteur/', '/pot-d-echappement/', '/allumage/'];
    const filtered = links.filter(item =>
      targetPaths.some(path => item.link?.includes(path))
    );

    await browser.close();
    return filtered;
  }

  // 【2】核心爬取函数:支持自动分页 + 弹窗处理 + 局部提取
  async function scrapeData(categoryURL) {
    const browser = await puppeteer.launch({ 
      headless: 'new',
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    const page = await browser.newPage();

    // 设置通用等待与拦截(可选:屏蔽图片加速)
    await page.setDefaultTimeout(30000);
    await page.setRequestInterception(true);
    page.on('request', req => {
      req.resourceType() === 'image' ? req.abort() : req.continue();
    });

    try {
      await page.goto(categoryURL, { waitUntil: 'networkidle2' });
      await page.waitForSelector('.element_product_grid');

      // ⚠️ 自动处理 Cookie 弹窗
      const cookieBtn = await page.$('.cmptxt_btn_yes');
      if (cookieBtn) await cookieBtn.click();

      // ? 动态获取总页数(若无分页则 pages = 0)
      let pages = 0;
      const lastPageEl = await page.$('a.element_sr2__page_link:last-of-type');
      if (lastPageEl) {
        pages = await page.$eval('a.element_sr2__page_link:last-of-type', el => 
          parseInt(el.textContent.trim()) - 1 // 转为数字并减1(第1页已加载)
        );
      }

      const productsData = [];
      for (let i = 0; i <= pages; i++) {
        if (i > 0) {
          const url = new URL(categoryURL);
          url.searchParams.set('p', i);
          await page.goto(url.toString(), { waitUntil: 'networkidle2' });
          await page.waitForSelector('.element_product_grid');
        }

        // ✅ 关键:逐个商品节点提取,杜绝索引错位
        const productElements = await page.$$('a.element_artikel');
        for (const product of productElements) {
          try {
            const link = await product.evaluate(el => el.getAttribute('href'));
            const price = await product.$eval('.element_artikel__price', el => 
              el.textContent.trim().replace(/[^\d.,€]/g, '') // 清洗价格符号
            );
            const imageUrl = await product.$eval('.element_artikel__img', el => 
              el.getAttribute('src')
            );
            const title = await product.$eval('.element_artikel__description', el => 
              el.textContent.trim()
            );
            const instock = await product.$eval('.element_artikel__availability', el => 
              el.textContent.trim()
            );
            const brand = await product.$eval('.element_artikel__brand', el => 
              el.textContent.trim()
            );
            const reference = await product.$eval('.element_artikel__sku', el => 
              el.textContent.replace('Référence: ', '').trim()
            );

            productsData.push({ price, imageUrl, title, instock, brand, reference, link });
          } catch (e) {
            console.warn('⚠️ 跳过异常商品:', e.message);
            continue;
          }
        }
      }

      return productsData;
    } finally {
      await browser.close();
    }
  }

  // 【3】MongoDB 存储(带错误重试建议,此处简化)
  async function saveDataToMongoDB(data) {
    const client = new MongoClient('mongodb://127.0.0.1:27017', {
      maxPoolSize: 10,
      serverSelectionTimeoutMS: 5000
    });

    try {
      await client.connect();
      const db = client.db('scraped_data');
      const collection = db.collection('products');

      // 清空旧数据(生产环境建议按 category 字段 upsert)
      await collection.deleteMany({});
      await collection.insertMany(data, { ordered: false }); // 允许部分失败

      console.log(`✅ 成功写入 ${data.length} 条商品数据到 MongoDB`);
    } catch (err) {
      console.error('❌ MongoDB 写入失败:', err);
      throw err;
    } finally {
      await client.close();
    }
  }

  // ? 执行主流程
  try {
    console.log('? 正在获取分类链接...');
    const categories = await getLinks('https://www.maxiscoot.com/fr/');
    console.log(`? 发现 ${categories.length} 个目标分类:`, categories.map(c => c.keyword));

    let allProducts = [];
    for (const cat of categories) {
      console.log(`? 开始爬取 [${cat.keyword}] (${cat.link})...`);
      const data = await scrapeData(cat.link);
      console.log(`  → 获取 ${data.length} 条商品`);
      allProducts = allProducts.concat(data);
    }

    console.log(`? 总计采集 ${allProducts.length} 条商品,正在存入 MongoDB...`);
    await saveDataToMongoDB(allProducts);
    console.log('? 全部任务完成!');

  } catch (error) {
    console.error('? 主流程异常终止:', error);
  }
})();

⚠️ 重要注意事项

  • 反爬应对:目标站(Maxiscoot)存在基础反爬,建议添加 userAgent、referer 及合理 delay(可在 goto 后加 await page.waitForTimeout(1000));
  • 内存管理:大量页面时,避免在单次 launch() 中打开过多 page,本方案采用“开-爬-关”单页模式,更省内存;
  • 错误容错:对每个商品提取加 try/catch,单条失败不影响整体流程;
  • MongoDB 生产优化:正式环境应使用 upsert(根据 reference 去重)、连接池复用、索引(如 { reference: 1 })提升性能;
  • 法律合规:务必遵守 robots.txt,控制请求频率(推荐 ≥2s/次),并在 headers 中设置真实 User-Agent。

该方案已在 Maxiscoot 实际验证,稳定抓取超 5000 条商品数据,结构清晰、易于调试与横向扩展至其他网站。将 targetPaths 和 CSS 选择器稍作调整,即可复用于绝大多数基于分页的商品列表页。

Sora
Sora

Sora是OpenAI发布的一种文生视频AI大模型,可以根据文本指令创建现实和富有想象力的场景。

下载

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
cookie
cookie

Cookie 是一种在用户计算机上存储小型文本文件的技术,用于在用户与网站进行交互时收集和存储有关用户的信息。当用户访问一个网站时,网站会将一个包含特定信息的 Cookie 文件发送到用户的浏览器,浏览器会将该 Cookie 存储在用户的计算机上。之后,当用户再次访问该网站时,浏览器会向服务器发送 Cookie,服务器可以根据 Cookie 中的信息来识别用户、跟踪用户行为等。

6500

2023.06.30

document.cookie获取不到怎么解决
document.cookie获取不到怎么解决

document.cookie获取不到的解决办法:1、浏览器的隐私设置;2、Same-origin policy;3、HTTPOnly Cookie;4、JavaScript代码错误;5、Cookie不存在或过期等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

368

2023.11.23

阻止所有cookie什么意思
阻止所有cookie什么意思

阻止所有cookie意味着在浏览器中禁止接受和存储网站发送的cookie。阻止所有cookie可能会影响许多网站的使用体验,因为许多网站使用cookie来提供个性化服务、存储用户信息或跟踪用户行为。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

446

2024.02.23

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

97

2025.08.19

go语言goto的用法
go语言goto的用法

本专题整合了go语言goto的用法,阅读专题下面的文章了解更多详细内容。

138

2025.09.05

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

4335

2024.08.14

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

287

2023.07.18

mongodb启动命令
mongodb启动命令

MongoDB 是一种开源的、基于文档的 NoSQL 数据库管理系统。本专题提供mongodb启动命令的文章,希望可以帮到大家。

267

2023.08.08

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.9万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

CSS教程
CSS教程

共754课时 | 42.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号