Scraping browser 代理流量优化方案
介绍
在使用 Puppeteer 进行数据抓取时,流量消耗是一个重要的考虑因素。尤其是在使用代理服务时,流量费用可能会显著增加。为了优化流量使用,我们可以采取以下几种策略:
- 资源拦截:通过拦截不必要的资源请求来减少流量消耗。
- 请求 URL 拦截:根据 URL 特征拦截特定请求,进一步减少流量。
- 模拟移动设备:使用移动设备配置来获取更轻量级的页面版本。
- 综合优化:结合以上方法,达到最佳效果。
优化方案1:资源拦截
资源拦截简介
在 Puppeteer 中,通过 page.setRequestInterception(true)
可以捕获浏览器发起的每一次网络请求,并决定继续(
request.continue()
)、终止(request.abort()
)或自定义响应(request.respond()
)。
这种方法可以显著减少带宽消耗,特别适用于爬虫、截图和性能优化场景。
可拦截资源类型及建议
资源类型 | 说明 | 示例 | 拦截后影响 | 推荐 |
---|---|---|---|---|
image | 图片资源 | JPG/PNG/GIF/WebP 等图片 | 页面不显示图片内容 | ⭐ 安全 |
font | 字体文件 | TTF/WOFF/WOFF2 等字体 | 使用系统默认字体替代 | ⭐ 安全 |
media | 媒体文件 | 视频/音频文件 | 媒体内容无法播放 | ⭐ 安全 |
manifest | Web 应用清单 | PWA 配置文件 | PWA 功能可能受影响 | ⭐ 安全 |
prefetch | 预取资源 | <link rel="prefetch"> | 对页面影响较小 | ⭐ 安全 |
stylesheet | CSS 样式表 | 外部 CSS 文件 | 页面样式丢失,可能影响布局 | ⚠️ 谨慎 |
websocket | WebSocket | 实时通信连接 | 实时功能失效 | ⚠️ 谨慎 |
eventsource | 服务器发送事件 | 服务器推送数据 | 推送功能失效 | ⚠️ 谨慎 |
preflight | CORS 预检请求 | OPTIONS 请求 | 跨域请求失败 | ⚠️ 谨慎 |
script | JavaScript 脚本 | 外部 JS 文件 | 动态功能失效,SPA 可能无法渲染 | ❌ 避免 |
xhr | XHR 请求 | AJAX 数据请求 | 无法获取动态数据 | ❌ 避免 |
fetch | Fetch 请求 | 现代 AJAX 请求 | 无法获取动态数据 | ❌ 避免 |
document | 主文档 | HTML 页面本身 | 页面无法加载 | ❌ 避免 |
推荐拦截等级说明:
- ⭐ 安全:拦截后几乎不影响数据抓取或首屏渲染,建议默认阻断。
- ⚠️ 谨慎:可能破坏样式、实时功能或跨域请求,需要按业务判断。
- ❌ 避免:高概率导致 SPA/动态站点无法正常渲染或获取数据,除非你非常确定不需要这些资源。
资源拦截示例代码
import puppeteer from 'puppeteer-core';
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
async function scrapeWithResourceBlocking(url) {
const browser = await puppeteer.connect({
browserWSEndpoint: scrapelessUrl,
defaultViewport: null
});
const page = await browser.newPage();
// 启用请求拦截
await page.setRequestInterception(true);
// 定义要阻止的资源类型
const BLOCKED_TYPES = new Set([
'image',
'font',
'media',
'stylesheet',
]);
// 拦截请求
page.on('request', (request) => {
if (BLOCKED_TYPES.has(request.resourceType())) {
request.abort();
console.log(`已阻止: ${request.resourceType()} - ${request.url().substring(0, 50)}...`);
} else {
request.continue();
}
});
await page.goto(url, {waitUntil: 'domcontentloaded'});
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000)
};
});
await browser.close();
return data;
}
// 使用方法
scrapeWithResourceBlocking('https://www.scrapeless.com')
.then(data => console.log('爬取结果:', data))
.catch(error => console.error('爬取失败:', error));
优化方案2:请求URL拦截
除了按资源类型拦截外,还可以根据URL特征进行更精细的拦截控制,这对于阻止广告、分析脚本和其他不必要的第三方请求特别有效。
URL拦截策略
- 按域名拦截:阻止特定域名的所有请求
- 按路径拦截:阻止特定路径的请求
- 按文件类型拦截:阻止特定扩展名的文件
- 按关键词拦截:阻止URL中包含特定关键词的请求
常见可拦截URL模式
URL模式 | 说明 | 示例 | 推荐 |
---|---|---|---|
广告服务 | 广告网络域名 | ad.doubleclick.net , googleadservices.com | ⭐ 安全 |
分析服务 | 统计和分析脚本 | google-analytics.com , hotjar.com | ⭐ 安全 |
社交媒体插件 | 社交分享按钮等 | platform.twitter.com , connect.facebook.net | ⭐ 安全 |
追踪像素 | 跟踪用户行为的像素 | URL包含pixel , beacon , tracker | ⭐ 安全 |
大型媒体文件 | 大型视频、音频文件 | 扩展名为.mp4 , .webm , .mp3 | ⭐ 安全 |
字体服务 | 在线字体服务 | fonts.googleapis.com , use.typekit.net | ⭐ 安全 |
CDN资源 | 静态资源CDN | cdn.jsdelivr.net , unpkg.com | ⚠️ 谨慎 |
URL拦截示例代码
import puppeteer from 'puppeteer-core';
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
async function scrapeWithUrlBlocking(url) {
const browser = await puppeteer.connect({
browserWSEndpoint: scrapelessUrl,
defaultViewport: null
});
const page = await browser.newPage();
// 启用请求拦截
await page.setRequestInterception(true);
// 定义要阻止的域名和URL模式
const BLOCKED_DOMAINS = [
'google-analytics.com',
'googletagmanager.com',
'doubleclick.net',
'facebook.net',
'twitter.com',
'linkedin.com',
'adservice.google.com',
];
const BLOCKED_PATHS = [
'/ads/',
'/analytics/',
'/pixel/',
'/tracking/',
'/stats/',
];
// 拦截请求
page.on('request', (request) => {
const url = request.url();
// 检查域名
if (BLOCKED_DOMAINS.some(domain => url.includes(domain))) {
request.abort();
console.log(`已阻止域名: ${url.substring(0, 50)}...`);
return;
}
// 检查路径
if (BLOCKED_PATHS.some(path => url.includes(path))) {
request.abort();
console.log(`已阻止路径: ${url.substring(0, 50)}...`);
return;
}
// 允许其他请求
request.continue();
});
await page.goto(url, {waitUntil: 'domcontentloaded'});
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000)
};
});
await browser.close();
return data;
}
// 使用方法
scrapeWithUrlBlocking('https://www.scrapeless.com')
.then(data => console.log('爬取结果:', data))
.catch(error => console.error('爬取失败:', error));
优化方案3:模拟移动设备
模拟移动设备是另一种有效的流量优化策略,因为移动版网站通常提供更轻量级的页面内容。
移动设备模拟的优势
- 更轻量级的页面版本:许多网站为移动设备提供更精简的内容
- 更小的图片资源:移动版通常加载更小尺寸的图片
- 简化的CSS和JavaScript:移动版通常使用更简化的样式和脚本
- 减少广告和非核心内容:移动版常常去除部分非核心功能
- 自适应响应:可以获取到为小屏幕优化的内容布局
移动设备模拟配置
以下是几种常用移动设备的配置参数:
const iPhoneX = {
viewport: {
width: 375,
height: 812,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false
}
};
或直接使用 puppeteer 内置的方法来模拟移动设备
import { KnownDevices } from 'puppeteer-core';
const iPhone = KnownDevices['iPhone 15 Pro'];
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhone);
移动设备模拟示例代码
import puppeteer, {KnownDevices} from 'puppeteer-core';
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
async function scrapeWithMobileEmulation(url) {
const browser = await puppeteer.connect({
browserWSEndpoint: scrapelessUrl,
defaultViewport: null
});
const page = await browser.newPage();
// 设置移动设备模拟
const iPhone = KnownDevices['iPhone 15 Pro'];
await page.emulate(iPhone);
await page.goto(url, {waitUntil: 'domcontentloaded'});
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000)
};
});
await browser.close();
return data;
}
// 使用方法
scrapeWithMobileEmulation('https://www.scrapeless.com')
.then(data => console.log('爬取结果:', data))
.catch(error => console.error('爬取失败:', error));
综合优化示例
以下是结合所有优化方案的综合示例:
import puppeteer, {KnownDevices} from 'puppeteer-core';
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
async function optimizedScraping(url) {
console.log(`开始优化爬取: ${url}`);
// 记录流量使用情况
let totalBytesUsed = 0;
const browser = await puppeteer.connect({
browserWSEndpoint: scrapelessUrl,
defaultViewport: null
});
const page = await browser.newPage();
// 设置移动设备模拟
const iPhone = KnownDevices['iPhone 15 Pro'];
await page.emulate(iPhone);
// 设置请求拦截
await page.setRequestInterception(true);
// 定义要阻止的资源类型
const BLOCKED_TYPES = [
'image',
'media',
'font'
];
// 定义要阻止的域名
const BLOCKED_DOMAINS = [
'google-analytics.com',
'googletagmanager.com',
'facebook.net',
'doubleclick.net',
'adservice.google.com'
];
// 定义要阻止的URL路径
const BLOCKED_PATHS = [
'/ads/',
'/analytics/',
'/tracking/'
];
// 拦截请求
page.on('request', (request) => {
const url = request.url();
const resourceType = request.resourceType();
// 检查资源类型
if (BLOCKED_TYPES.includes(resourceType)) {
console.log(`已阻止资源类型: ${resourceType} - ${url.substring(0, 50)}...`);
request.abort();
return;
}
// 检查域名
if (BLOCKED_DOMAINS.some(domain => url.includes(domain))) {
console.log(`已阻止域名: ${url.substring(0, 50)}...`);
request.abort();
return;
}
// 检查路径
if (BLOCKED_PATHS.some(path => url.includes(path))) {
console.log(`已阻止路径: ${url.substring(0, 50)}...`);
request.abort();
return;
}
// 允许其他请求
request.continue();
});
// 监控网络流量
page.on('response', async (response) => {
const headers = response.headers();
const contentLength = headers['content-length'] ? parseInt(headers['content-length'], 10) : 0;
totalBytesUsed += contentLength;
});
await page.goto(url, {waitUntil: 'domcontentloaded'});
// 模拟滚动以触发懒加载内容
await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
});
await new Promise(resolve => setTimeout(resolve, 1000))
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000),
links: Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({
text: a.innerText,
href: a.href
}))
};
});
// 输出流量使用统计
console.log(`\n流量使用统计:`);
console.log(`已使用: ${(totalBytesUsed / 1024 / 1024).toFixed(2)} MB`);
await browser.close();
return data;
}
// 使用方法
optimizedScraping('https://www.scrapeless.com')
.then(data => console.log('爬取完成:', data))
.catch(error => console.error('爬取失败:', error));
优化对比
我们尝试去掉综合示例的优化代码,来对比一下优化前与优化后的流量对比。以下是未优化的示例代码:
import puppeteer from 'puppeteer-core';
const scrapelessUrl = 'wss://browser.scrapeless.com/browser?token=your_api_key&session_ttl=180&proxy_country=ANY';
async function optimizedScraping(url) {
console.log(`开始优化爬取: ${url}`);
// 记录流量使用情况
let totalBytesUsed = 0;
const browser = await puppeteer.connect({
browserWSEndpoint: scrapelessUrl,
defaultViewport: null
});
const page = await browser.newPage();
// 设置请求拦截
await page.setRequestInterception(true);
// 拦截请求
page.on('request', (request) => {
request.continue();
});
// 监控网络流量
page.on('response', async (response) => {
const headers = response.headers();
const contentLength = headers['content-length'] ? parseInt(headers['content-length'], 10) : 0;
totalBytesUsed += contentLength;
});
await page.goto(url, {waitUntil: 'domcontentloaded'});
// 模拟滚动以触发懒加载内容
await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
});
await new Promise(resolve => setTimeout(resolve, 1000))
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000),
links: Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({
text: a.innerText,
href: a.href
}))
};
});
// 输出流量使用统计
console.log(`\n流量使用统计:`);
console.log(`已使用: ${(totalBytesUsed / 1024 / 1024).toFixed(2)} MB`);
await browser.close();
return data;
}
// 使用方法
optimizedScraping('https://www.scrapeless.com')
.then(data => console.log('爬取完成:', data))
.catch(error => console.error('爬取失败:', error));
运行未优化代码后,我们可以根据打印信息很直观的看到流量的差距:
场景 | 已使用流量 (MB) | 节省比例 |
---|---|---|
未优化 | 6.03 | — |
优化后 | 0.81 | ≈ 86.6 % |
通过结合以上优化方案,可以显著减少代理流量消耗,提高爬取效率,同时确保获取到所需的核心内容。