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');
六、合规性与注意事项
- 遵守 Robots 协议:电商平台通常在/robots.txt定义爬取规则(如京东https://www.jd.com/robots.txt),避免爬取禁止的路径。
- 尊重网站版权与隐私:
-
- 不爬取用户隐私数据(如手机号、地址)。
-
- 不用于商业竞争或恶意攻击,避免给平台服务器造成过大负载(建议控制并发≤5)。
- 应对验证码:若触发验证码(如频繁登录),可集成打码平台(如云打码),或手动登录后导出 Cookie 复用。
七、总结与展望
Puppeteer 凭借 “完全模拟浏览器” 的核心能力,完美解决了电商 JavaScript 页面的爬取难题,但其应用需平衡 “效率” 与 “合规”。未来,随着电商平台反爬技术的升级(如指纹识别、行为分析),需进一步结合 “浏览器指纹伪装”“AI 行为模拟” 等技术,同时始终以合法合规为前提,避免法律风险。
对于开发者而言,建议从 “小范围测试” 开始(如爬取少量公开商品数据),逐步优化爬取策略,最终实现稳定、高效的电商数据采集。