深入理解技术探索与实践:一个准“前司人”的爬虫踩坑实录
大家好,我是小张,普通一本 CS 专业大四狗,目前手握 offer 等待入职。虽然简历上写着“在校生”,但其实已经在某家电商中厂摸爬滚打了三年多——没错,就是那种“实习转正再续签”的经典路径。现在每天一边改 Bug 一边刷 BOSS 直聘,琢磨着换个环境,毕竟 K8s 都快被我玩出包浆了,再不跳槽怕是要和公司的 CI/CD 流水线结拜兄弟。
上周五晚上十一点半,我瘫在工位上啃泡面,突然想起去年双11期间那个让我掉头发的“综合数据采集”项目。今天就借这个机会,把当时用 JavaScript + 爬虫 技术栈搞的“综合型”数据采集系统复盘一下。不是教程,也不是炫技,纯属一个打工人被产品经理逼到墙角后,硬着头皮搞出来的最佳实践(或者说“求生指南”)。
起因:产品经理说“我们要全网比价”
事情得从去年 Q3 说起。我们团队负责商品价格监控,原本是靠合作方 API 拿数据。结果某天产品大佬在站会上轻描淡写一句:“现在友商都在做实时比价,咱们也得搞个‘全网综合价格指数’,覆盖主流电商平台。”
我当场瞳孔地震:全网?主流?这词儿听着就危险。更骚的是,deadline 定在双11前两周——也就是三周后上线 MVP。运维老哥在旁边冷笑:“你是不是以为爬虫是 Ctrl+C/V 就完事了?”
事实证明,他太乐观了。
为什么选 JavaScript?别笑,真有原因
很多人一听爬虫就想到 Python + Scrapy,但这次我们团队技术栈全是 Node.js,且已有大量 Puppeteer 自动化测试脚本。领导拍板:“能复用就别造轮子。”于是,JavaScript 成了唯一选项。
但这不是最头疼的。真正麻烦的是“综合”二字——不同平台反爬策略天差地别:
- 淘宝:登录+滑块验证+动态渲染
- 京东:基础 HTML 渲染,但有频率限制
- 拼多多:大量加密参数,URL 带 token
- 抖音电商:完全 SPA,数据全走 WebSocket
一句话总结:没有通用解法,只有见招拆招。
实战:从“Hello World”到生产级爬虫
第一阶段:天真如我,以为 fetch 就够了
刚开始我写了个简单脚本,用 axios 请求商品页,正则匹配价格。跑本地测试 OK,一部署到测试环境直接 403。
// 初版:Too young, too simple
const getPrice = async (url) => {
const res = await axios.get(url);
return res.data.match(/¥(\d+\.\d+)/)?.[1];
};
结果?淘宝返回 <script>location.href='/login';</script>,京东返回“请求过于频繁”,拼多多直接 502。我当时真的想砸电脑。
第二阶段:上 Puppeteer,模拟真人操作
痛定思痛,决定上无头浏览器。用 Puppeteer 加载页面,等 JS 执行完再取 DOM。核心代码如下:
const puppeteer = require('puppeteer');
const scrapeWithPuppeteer = async (url) => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// 设置合理 UA 和 viewport,避免被识破是 bot
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...');
await page.setViewport({ width: 1366, height: 768 });
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 15000 });
// 等待价格元素出现(防加载延迟)
await page.waitForSelector('.price', { timeout: 8000 });
const price = await page.$eval('.price', el => el.textContent);
return price.trim();
} catch (e) {
console.error('Scraping failed:', e.message);
return null;
} finally {
await browser.close();
}
};
效果:能拿到数据了!但新问题来了——资源消耗爆炸。一台 4C8G 的服务器,同时跑 10 个实例就 OOM。而且每次启动浏览器都要 2~3 秒,QPS 低得可怜。
第三阶段:池化 + 智能调度,向工程化迈进
这时候就得上 浏览器池(Browser Pool) 了。参考了 Puppeteer-cluster 的思路,自己撸了个简易版:
- 预启 5 个浏览器实例,每个实例开多个 Page
- 请求按域名分组,同域名复用 Page(减少冷启动)
- 失败自动重试 + 代理 IP 轮换
关键配置如下:
| 参数 | 值 | 说明 |
|---|---|---|
| maxConcurrency | 10 | 最大并发请求数 |
| browserInstances | 5 | 浏览器实例数 |
| reusePage | true | 同域名复用 Page |
| proxyRotation | every 50 requests | 每 50 次请求换代理 |
| retryTimes | 3 | 失败重试次数 |
配合公司内部的 K8s Job 调度,把爬虫任务拆成小批次,每批 100 个 URL,失败自动重入队列。这样即使某个平台临时封 IP,也不影响整体进度。
反爬对抗:猫鼠游戏的真实战场
最刺激的还是和反爬斗智斗勇。举几个典型场景:
场景 1:淘宝滑块验证码
第一次遇到时,我以为要上 CV 或打码平台。后来发现,只要 模拟人类鼠标轨迹 + 延迟操作,成功率能到 80%。用了 puppeteer-extra-plugin-stealth 插件隐藏自动化特征,再加一段随机移动代码:
await page.mouse.move(100, 200, { steps: 10 }); // steps 模拟平滑移动
await page.waitForTimeout(Math.random() * 1000 + 500); // 随机停顿
场景 2:拼多多的加密 URL
他们的商品链接带 sign 参数,由前端 JS 动态生成。我一度想放弃,直到翻了下他们 bundle.js,发现加密逻辑在 window.__INITIAL_STATE__ 附近。最终方案:注入 JS Hook,拦截请求前的加密函数。
await page.evaluateOnNewDocument(() => {
// Hook 加密函数,打印出 sign 值
const originalFn = window.encryptUrl;
window.encryptUrl = function(...args) {
const result = originalFn.apply(this, args);
console.log('Generated sign:', result.sign);
return result;
};
});
然后通过监听 console 事件捕获 sign,拼接真实 URL。虽然 hacky,但有效。
场景 3:抖音电商的 WebSocket 数据流
页面本身没价格,数据通过 WS 推送。解决方案:拦截 WebSocket 消息。
await page.evaluate(() => {
const originalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(data) {
// 记录发送的数据(用于分析协议)
console.log('WS Send:', data);
return originalSend.call(this, data);
};
const originalOnMessage = WebSocket.prototype.onmessage;
WebSocket.prototype.onmessage = function(event) {
// 捕获价格相关消息
if (event.data.includes('price')) {
window.priceData = event.data;
}
return originalOnMessage?.call(this, event);
};
});
// 等待数据出现
await page.waitForFunction(() => window.priceData);
const price = await page.evaluate(() => JSON.parse(window.priceData).price);
效果与反思:值不值得这么折腾?
双11当天,系统成功采集了 50W+ 商品价格,准确率 92%(人工抽样),支撑了“全网最低价”标签上线。老板在复盘会上夸了一句:“这波数据很稳。”
但代价呢?
- 开发周期:2.5 周(含踩坑)
- 运维成本:需要维护代理池、浏览器集群、失败告警
- 法律风险:虽未被起诉,但心里总有点虚
所以我的结论是:除非业务强依赖且无 API 可用,否则尽量别碰爬虫。如果非要搞,记住三条铁律:
- 尊重 robots.txt,别把别人服务器干崩了;
- 控制频率,加随机 delay,别当网络暴徒;
- 做好降级,一旦被封,要有备用数据源。
给后来者的建议:别重复造轮子
如果你也在搞类似项目,强烈推荐这几个工具:
- Playwright:比 Puppeteer 更现代,跨浏览器支持更好
- Apify:云原生爬虫平台,省去运维烦恼
- Bright Data:合规代理服务(贵但省心)
另外,别一个人硬扛。我们后来拉上了运维一起优化 K8s 资源配额,测试同学帮忙写 mock 数据验证逻辑,连产品都主动缩减了首批采集范围——团队协作才是 TPS 提升的关键。
写在最后:技术探索的意义
回看这段经历,最大的收获不是写了多少行代码,而是理解了“综合”二字的重量。真正的工程问题,从来不是单一技术能解决的。它需要你:
- 在 JavaScript 的灵活性和性能之间权衡;
- 在爬虫的“黑”与合规的“白”之间找平衡;
- 在 deadline 的压迫和代码质量之间做取舍。
现在我即将入职新公司,听说他们也在搞数据采集平台。我已经准备好把这套“血泪经验”打包带走——当然,还有那颗被产品经理磨练得无比坚韧的程序员心脏。
共勉吧,打工人。下次双11,希望我们都在摸鱼,而不是在 debug 爬虫。
P.S. 本文所有代码均已脱敏,如有雷同,纯属行业通病。

评论 0