当前位置: 首页 > news >正文

Puppeteer 在爬取电商 JavaScript 页面的使用

一、引言:电商 JavaScript 页面与爬取痛点

随着前端技术发展,主流电商平台(如淘宝、京东、拼多多)普遍采用JavaScript 动态渲染技术构建页面。这类页面的核心数据(商品价格、库存、评价、销量)并非直接嵌入 HTML 源码,而是通过 AJAX、Fetch 等方式异步加载,或依赖 Vue、React 等框架动态生成 DOM。

传统爬虫(如 Requests+BeautifulSoup)仅能获取初始 HTML 文本,无法执行 JavaScript 代码,导致无法抓取动态加载的关键数据。而 Puppeteer 作为 Chrome 官方推出的无头浏览器工具,可完全模拟浏览器环境,支持执行 JS、渲染 DOM、模拟用户操作,成为解决电商动态页面爬取的核心方案。

二、基础准备:Puppeteer 环境搭建与核心概念

1. 环境依赖

  • Node.js:需 v14 及以上版本(Puppeteer 对 Node 版本有最低要求),可通过node -v验证安装。
  • Puppeteer:通过 npm 安装,默认会自动下载匹配版本的 Chromium(无头浏览器内核):
# 基础安装(含Chromium)npm install puppeteer# 若需自定义浏览器路径(如使用本地Chrome),可安装轻量版npm install puppeteer-core

2. 核心概念与 Hello World 示例

Puppeteer 的核心对象包括:

  • browser:浏览器实例(可理解为打开一个 Chrome 窗口)。
  • page:标签页实例(对应浏览器中的一个标签)。
  • frame:框架实例(处理页面中的 iframe,电商页面常用来加载评价、商品规格等)。

以下是最小化示例,实现 “打开京东首页并截图”,验证环境可用性:

const puppeteer = require('puppeteer');async function initPuppeteer() {// 1. 启动浏览器(headless: 'new'表示无头模式,不显示界面)const browser = await puppeteer.launch({headless: 'new',args: ['--no-sandbox'] // 避免Linux环境下的权限问题});// 2. 新建标签页const page = await browser.newPage();// 3. 设置页面视口(模拟1080P屏幕)await page.setViewport({ width: 1920, height: 1080 });// 4. 访问京东首页(等待页面加载完成,waitUntil: 'networkidle2'表示网络空闲)await page.goto('https://www.jd.com', { waitUntil: 'networkidle2' });// 5. 截图(验证页面渲染成功)await page.screenshot({ path: 'jd-home.png', fullPage: true });// 6. 关闭浏览器await browser.close();console.log('Puppeteer基础示例执行完成');
}initPuppeteer();

三、核心场景实战:电商数据爬取

以 “京东商品列表 + 详情 + 评价” 为例,讲解关键数据爬取逻辑。

1. 场景 1:商品列表爬取(含滚动加载)

电商列表常采用 “滚动加载更多”(如京东搜索结果页),需模拟滚动并等待数据加载。

需求:爬取 “笔记本电脑” 搜索结果的前 3 页商品(名称、价格、链接)。

async function crawlJdList() {const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] });const page = await browser.newPage();await page.setViewport({ width: 1920, height: 1080 });// 1. 访问京东搜索页(关键词:笔记本电脑)const searchUrl = 'https://search.jd.com/Search?keyword=笔记本电脑&enc=utf-8';await page.goto(searchUrl, { waitUntil: 'networkidle2' });// 2. 模拟滚动加载(滚动3次,每次间隔1秒,等待数据加载)for (let i = 0; i < 3; i++) {await page.evaluate(() => {// 执行JS代码,滚动到页面底部window.scrollTo(0, document.body.scrollHeight);});await page.waitForTimeout(1000); // 等待1秒,确保数据加载}// 3. 提取商品数据(通过page.evaluate在浏览器上下文执行JS)const goodsList = await page.evaluate(() => {const items = document.querySelectorAll('.gl-item'); // 商品项选择器(需通过浏览器F12确认)const result = [];items.forEach(item => {result.push({name: item.querySelector('.p-name em')?.innerText.trim() || '未知名称', // 商品名称price: item.querySelector('.p-price i')?.innerText.trim() || '0', // 商品价格link: item.querySelector('.p-img a')?.href || '无链接' // 商品详情页链接});});return result;});// 4. 输出结果(或存入文件/数据库)console.log('京东商品列表数据:', goodsList.slice(0, 5)); // 打印前5条示例await browser.close();
}crawlJdList();

2. 场景 2:商品详情页爬取(含动态标签)

商品详情页常包含 “规格参数”“售后保障” 等动态切换标签,需模拟点击标签后提取数据。

需求:从商品详情页爬取 “规格参数” 和 “库存状态”。

async function crawlJdDetail(goodsUrl) {const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] });const page = await browser.newPage();await page.setViewport({ width: 1920, height: 1080 });try {// 1. 访问商品详情页(设置超时时间为30秒,避免页面加载过慢)await page.goto(goodsUrl, { waitUntil: 'networkidle2', timeout: 30000 });// 2. 模拟点击“规格参数”标签(等待标签加载完成)await page.waitForSelector('.parameter2', { timeout: 10000 }); // 标签选择器await page.click('.parameter2'); // 点击切换标签// 3. 提取详情数据const detailData = await page.evaluate(() => {// 提取规格参数(表格数据)const params = [];document.querySelectorAll('.parameter2 .parameter-table tr').forEach(tr => {const key = tr.querySelector('th')?.innerText.trim();const value = tr.querySelector('td')?.innerText.trim();if (key && value) params.push({ key, value });});// 提取库存状态const stock = document.querySelector('.stock .stock-num')?.innerText.trim() || '库存未知';return {goodsId: window.location.href.match(/item.jd.com\/(\d+).html/)?.[1] || '无ID', // 从URL提取商品IDparams,stock};});console.log('商品详情数据:', detailData);} catch (error) {console.error('详情页爬取失败:', error.message);} finally {await browser.close();}
}// 调用:传入一个京东商品详情页链接
crawlJdDetail('https://item.jd.com/100069368866.html');

3. 场景 3:商品评价爬取(含分页)

电商评价多采用分页加载,需模拟点击 “下一页” 并处理分页边界(如最后一页无下一页按钮)。

需求:爬取某商品前 2 页评价(用户昵称、评价内容、评分)。

async function crawlJdComments(goodsId) {const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] });const page = await browser.newPage();await page.setViewport({ width: 1920, height: 1080 });const comments = [];const targetPage = 2; // 目标爬取页数try {// 1. 访问商品评价页(京东评价页URL格式:https://item.jd.com/{goodsId}.html#comment)await page.goto(`https://item.jd.com/${goodsId}.html#comment`, { waitUntil: 'networkidle2' });for (let pageNum = 1; pageNum <= targetPage; pageNum++) {console.log(`正在爬取第${pageNum}页评价`);// 2. 等待评价列表加载完成await page.waitForSelector('.comment-item', { timeout: 10000 });// 3. 提取当前页评价const currentPageComments = await page.evaluate(() => {const items = document.querySelectorAll('.comment-item');return items.map(item => ({user: item.querySelector('.user-info .user-name')?.innerText.trim() || '匿名用户',content: item.querySelector('.comment-con .comment-words')?.innerText.trim() || '无评价内容',score: item.querySelector('.comment-star .star')?.className.match(/star(\d+)/)?.[1] || '0' // 提取评分(1-5)}));});comments.push(...currentPageComments);// 4. 处理分页:若未到目标页,点击“下一页”if (pageNum < targetPage) {const nextBtn = await page.$('.ui-pager-next'); // 下一页按钮选择器if (!nextBtn) {console.log('已无下一页,停止爬取');break;}// 检查下一页按钮是否可用(若禁用则停止)const isDisabled = await page.evaluate(btn => btn.classList.contains('disabled'), nextBtn);if (isDisabled) {console.log('下一页按钮已禁用,停止爬取');break;}await nextBtn.click();await page.waitForTimeout(1500); // 等待页面切换}}console.log(`共爬取${comments.length}条评价:`, comments.slice(0, 3)); // 打印前3条示例} catch (error) {console.error('评价爬取失败:', error.message);} finally {await browser.close();}
}// 调用:传入商品ID(从详情页URL提取)
crawlJdComments('100069368866');

四、反爬策略应对:避免被电商平台封禁

电商平台有严格的反爬机制,直接爬取易导致 IP 封禁、账号限制,需针对性优化:

1. 模拟真实浏览器环境

  • 设置 User-Agent:伪装成真实浏览器(避免默认 Headless 标识):
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36');
  • 加载 Cookie:登录后导出浏览器 Cookie,传入 Puppeteer(模拟已登录状态,避免验证码):
// 示例:导入京东Cookie(需先从浏览器F12的Application->Cookies复制)
const cookies = [{ name: 'pt_key', value: '你的pt_key', domain: '.jd.com' },{ name: 'pt_pin', value: '你的pt_pin', domain: '.jd.com' }
];
await page.setCookie(...cookies);

2. 控制爬取频率与行为

  • 随机延迟:避免固定间隔,模拟人类操作节奏:
// 随机延迟1-3秒const randomDelay = Math.floor(Math.random() * 2000) + 1000;await page.waitForTimeout(randomDelay);
  • 模拟鼠标操作:加入随机点击、移动(避免机械性爬取):
// 模拟鼠标移动到商品项await page.hover('.gl-item:nth-child(5)'); // hover第5个商品await page.waitForTimeout(500);

3. 分布式与 IP 代理

  • IP 代理:通过args配置代理服务器,避免单 IP 频繁请求被封:
const browser = await puppeteer.launch({args: ['--no-sandbox','--proxy-server=http://你的代理IP:端口' // 如:http://123.45.67.89:8888]
});

五、进阶优化:提升爬取效率与稳定性

1. 无头模式优化

Puppeteer 默认的headless: 'new'模式已比旧版更高效,若需进一步提速,可关闭不必要的功能:

await puppeteer.launch({headless: 'new',args: ['--no-sandbox','--disable-gpu', // 禁用GPU加速'--disable-dev-shm-usage', // 避免/dev/shm内存不足'--disable-extensions' // 禁用扩展]
});

2. 并发控制

单页面爬取效率低,可通过puppeteer-cluster(第三方库)实现多页面并发:

npm install puppeteer-cluster

示例(并发爬取 3 个商品详情页):

const { Cluster } = require('puppeteer-cluster');async function concurrentCrawl() {const cluster = await Cluster.launch({concurrency: Cluster.CONCURRENCY_PAGE, // 按页面并发maxConcurrency: 3, // 最大并发数(避免过多导致被封)puppeteerOptions: { headless: 'new', args: ['--no-sandbox'] }});// 定义爬取任务await cluster.task(async ({ page, data: goodsUrl }) => {await page.goto(goodsUrl, { waitUntil: 'networkidle2' });const goodsId = await page.evaluate(() => window.location.href.match(/item.jd.com\/(\d+).html/)?.[1]);console.log(`并发爬取完成:商品ID=${goodsId}`);});// 加入任务队列const goodsUrls = ['https://item.jd.com/100069368866.html','https://item.jd.com/100055645555.html','https://item.jd.com/100044888888.html'];goodsUrls.forEach(url => cluster.queue(url));await cluster.idle();await cluster.close();
}concurrentCrawl();

3. 数据存储

爬取后的数据可存入 JSON 文件、MySQL 或 MongoDB,示例存入 JSON:

const fs = require('fs').promises;// 爬取完成后保存数据
await fs.writeFile('jd-goods.json',JSON.stringify(goodsList, null, 2), // 格式化JSON'utf8'
);
console.log('数据已保存到jd-goods.json');

六、合规性与注意事项

  1. 遵守 Robots 协议:电商平台通常在/robots.txt定义爬取规则(如京东https://www.jd.com/robots.txt),避免爬取禁止的路径。
  1. 尊重网站版权与隐私
    • 不爬取用户隐私数据(如手机号、地址)。
    • 不用于商业竞争或恶意攻击,避免给平台服务器造成过大负载(建议控制并发≤5)。
  1. 应对验证码:若触发验证码(如频繁登录),可集成打码平台(如云打码),或手动登录后导出 Cookie 复用。

七、总结与展望

Puppeteer 凭借 “完全模拟浏览器” 的核心能力,完美解决了电商 JavaScript 页面的爬取难题,但其应用需平衡 “效率” 与 “合规”。未来,随着电商平台反爬技术的升级(如指纹识别、行为分析),需进一步结合 “浏览器指纹伪装”“AI 行为模拟” 等技术,同时始终以合法合规为前提,避免法律风险。

对于开发者而言,建议从 “小范围测试” 开始(如爬取少量公开商品数据),逐步优化爬取策略,最终实现稳定、高效的电商数据采集。

http://www.dtcms.com/a/394350.html

相关文章:

  • 2015/12 JLPT听力原文 问题四
  • 【设计模式】备忘录模式
  • STM32_07_按键
  • 基于迁移学习和SqueezeNet的滚动轴承故障诊断(MATLAB)
  • 实战项目(十二:《AI画质增强与LED驱动控制:一场关于‘创造’与‘还原’的对话》):从LED冬奥会、奥运会及春晚等大屏,到手机小屏,快来挖一挖里面都有什么
  • 开发避坑指南(52):IDEA 2025.1.3 顶部显示类完整路径的设置方法
  • 安装Qt新之后出现两本帮助手册
  • Rust_2025:阶段1:day7.2unsafe , 链接相关
  • 【论文速递】2025年第15周(Apr-06-12)(Robotics/Embodied AI/LLM)
  • 设计模式简单说明:责任链与规则树
  • 自动备份脚本 mysql_hourly_backup.sh
  • SuperGLUE:自然语言理解的挑战与进步
  • 线程安全的单例模式、自旋锁,以及读者写者问题
  • U盘长期插在电脑上的影响
  • Windows 系统部署 PaddleOCR —— 基于 EPGF 架构
  • 数据一致性指的是什么?如何实现数据一致性?
  • 初识消息队列的世界
  • Python快速入门专业版(三十八):Python字典:键值对结构的增删改查与进阶用法
  • SpringCloudOAuth2+JWT:微服务统⼀认证方案
  • LeetCode 分类刷题:2517. 礼盒的最大甜蜜度
  • 深度学习优化器进阶:从SGD到AdamW,不同优化器的适用场景
  • C++ 之 【C++的IO流】
  • truffle学习笔记
  • 现代循环神经网络
  • vlc播放NV12原始视频数据
  • ThinkPHP8学习篇(七):数据库(三)
  • 链家租房数据爬虫与可视化项目 Python Scrapy+Django+Vue 租房数据分析可视化 机器学习 预测算法 聚类算法✅
  • MQTT协议知识点总结
  • C++ 类和对象·其一
  • TypeScript里的类型声明文件