从爬虫到前端:一个研二老码农的技术探索实录
上周五晚上十一点半,实验室的灯还亮着。我盯着 VSCode 里一堆红红的 ESLint 报错,一边调试一个用 Puppeteer 写的爬虫脚本,一边在微信上回复导师“进度还行”。其实心里慌得一批——这个 AI 相关的课题项目下周就要中期汇报了,而我连数据都没爬全。
没错,我是某 211 高校软件工程专业的研二学生,但别被“研究生”仨字骗了。我在互联网公司干了三年多前端,去年才辞职回炉重造。现在白天在实验室调模型、写论文,晚上还得操心怎么把 JS 代码跑通。说白了,就是个“学术打工人+技术民工”的缝合怪。
今天想和大家聊聊:在身份撕裂、时间碎片、需求模糊的情况下,如何高效地做技术探索与实践? 尤其是当你要同时搞 JavaScript、爬虫、前端,甚至还要蹭点 AI 热度的时候。
起因:一个“不可能完成”的任务
事情要从一个月前说起。导师让我做一个“基于用户行为分析的前端性能优化建议系统”。听起来高大上,翻译过来就是:爬大量网站的真实用户交互数据(比如点击、滚动、停留时长),然后用 AI 模型判断哪些页面体验差,再给出改进建议。
问题来了:
- 数据从哪来?公开数据集太干净,不真实;
- 真实用户行为没法直接拿(隐私问题);
- 唯一可行方案:模拟用户行为 + 爬取页面性能指标。
于是,我被迫重拾老本行——用 JavaScript 写爬虫。
但这次不一样。以前在公司写爬虫,目标明确:抓商品价格、评论、库存。现在要的是“前端性能数据”,比如 FCP、LCP、CLS、TTI……这些 Web Vitals 指标,普通 HTTP 请求根本拿不到,必须在浏览器环境里跑。
这就逼我用上了 Puppeteer —— 一个由 Google 开发的 Node.js 库,能控制无头 Chrome,完美执行前端 JS 并采集运行时指标。
踩坑实录:你以为的简单,其实是地狱
坑一:你以为 Puppeteer 很稳?
第一次写脚本,信心满满:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const metrics = await page.metrics();
console.log(metrics);
await browser.close();
})();
结果跑起来内存爆了,CPU 占满,还经常卡死。后来才知道:
- 默认启动的是完整 Chrome,不是轻量版;
- 没加
--no-sandbox和--disable-dev-shm-usage参数,在 Linux 服务器上容易崩; - 每次请求都开新浏览器实例,资源没回收。
解决方案:复用浏览器实例 + 合理配置启动参数。
// utils/browser.js
let browserInstance = null;
async function getBrowser() {
if (!browserInstance) {
browserInstance = await puppeteeer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--single-process' // 降低内存占用(仅限测试环境)
]
});
}
return browserInstance;
}
注:
--single-process在生产环境慎用,但实验室机器配置低,只能妥协。
坑二:前端性能指标怎么拿?
光有 page.metrics() 不够,它返回的是底层 Chromium 的性能计数器,不是 Web Vitals。真正要的是 Lighthouse 或 web-vitals 库提供的标准指标。
于是我引入了 Google 官方的 web-vitals:
// 在页面中注入 web-vitals
await page.addScriptTag({ url: 'https://unpkg.com/web-vitals@3/dist/web-vitals.iife.js' });
// 然后通过 evaluate 执行并返回结果
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
let result = {};
webVitals.getCLS(console.log); // 这样不行!
// 正确姿势:收集所有指标
const metrics = ['FCP', 'LCP', 'CLS', 'FID', 'TTFB'];
metrics.forEach(metric => {
webVitals[`get${metric}`]((val) => {
result[metric] = val.value;
if (Object.keys(result).length === metrics.length) {
resolve(result);
}
}, { reportAllChanges: false });
});
});
});
这段代码折磨了我整整两天。原因?web-vitals 的回调是异步的,而且有些指标(比如 CLS)要等页面稳定后才触发。你不能简单 return,必须用 Promise 包裹,还得处理超时。
最后我封装了一个 captureWebVitals 函数,加了 10 秒超时兜底:
async function captureWebVitals(page, timeout = 10000) {
return page.evaluate((to) => {
return new Promise((resolve) => {
const result = {};
const required = ['FCP', 'LCP', 'CLS'];
let collected = 0;
const done = () => {
collected++;
if (collected === required.length) resolve(result);
};
required.forEach(name => {
window.webVitals[`get${name}`]((metric) => {
result[name] = metric.value;
done();
}, { reportAllChanges: false });
});
// 超时兜底
setTimeout(() => resolve(result), to);
});
}, timeout);
}
坑三:反爬机制让你怀疑人生
你以为只是加载页面?Too young。
很多网站:
- 检测 Headless Chrome(通过
navigator.webdriver); - 动态加载内容(需要滚动触发);
- 有验证码或 Cloudflare 防护;
- 甚至用 JS 混淆阻止自动化。
我一度想放弃,直到想起自己当年在公司对付电商反爬的经验。
对策清单:
- 伪装浏览器指纹:
await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); }); - 模拟人类操作:
await page.mouse.move(100, 100); await page.waitForTimeout(500); await page.keyboard.press('PageDown'); - 用代理池轮换 IP(实验室经费有限,只买了最便宜的);
- 失败重试 + 队列控制,避免被封。
前端部分:不止是展示,更是交互闭环
爬完数据,总得给人看吧?于是我搭了个简单的前端面板,用 Vue3 + TypeScript 写的(毕竟公司那三年都是 Vue,肌肉记忆了)。
但这次我想玩点新的——把 AI 分析结果可视化。
比如,当某个页面的 CLS(累积布局偏移)超过 0.25,就在界面上标红,并显示“可能原因:图片未设宽高、广告动态插入”。
为了实现这个,我做了两件事:
- 用 Express 写了个后端 API,提供爬虫任务调度和结果查询;
- 前端用 ECharts 画性能趋势图,用 Ant Design Vue 做交互。
关键代码片段:
<template>
<a-card v-for="report in reports" :key="report.url">
<h3>{{ report.url }}</h3>
<div v-if="report.CLS > 0.25" class="alert-high-cls">
⚠️ 布局抖动严重!建议检查图片/广告加载逻辑。
</div>
<v-chart :option="chartOption(report)" />
</a-card>
</template>
<script setup>
function chartOption(report) {
return {
tooltip: {},
xAxis: { type: 'category', data: ['FCP', 'LCP', 'CLS'] },
yAxis: {},
series: [{ type: 'bar', data: [report.FCP, report.LCP, report.CLS] }]
};
}
</script>
说实话,这部分反而最顺。因为前端框架成熟,生态完善。真正的挑战永远在数据获取那一端。
技术选型背后的权衡
整个项目涉及多个技术栈,每一步都有取舍:
| 技术点 | 备选方案 | 最终选择 | 原因 |
|---|---|---|---|
| 浏览器自动化 | Selenium / Playwright | Puppeteer | 团队熟悉 JS,且 Puppeteer 对 Web Vitals 支持更好 |
| 前端框架 | React / Svelte | Vue3 | 快速开发,TSX 写法不如 Composition API 顺手 |
| 后端语言 | Python / Go | Node.js | 全栈 JS,减少上下文切换 |
| 性能指标采集 | Lighthouse CLI / 自研 | web-vitals + Puppeteer | 更贴近真实用户,而非模拟加载 |
特别想吐槽的是:很多人以为爬虫就是发个 GET 请求,其实现代爬虫=前端+后端+运维+逆向的混合体。尤其当你需要执行 JS 时,本质上你是在“远程控制一个浏览器”,这成本可不低。
开发心得:老程序员的几点感悟
不要为了新技术而新技术
我一开始想用 Playwright,因为它支持多浏览器。但发现团队没人会,文档也不如 Puppeteer 丰富。最后还是选了熟悉的。生产力 > 新鲜感,尤其在 deadline 压顶时。日志和监控是救命稻草
我给爬虫加了详细日志:logger.info(`[START] Crawling ${url}`); try { // ... logger.info(`[SUCCESS] ${url} - LCP: ${lcp}`); } catch (err) { logger.error(`[FAIL] ${url} - ${err.message}`); }没有这些,半夜排查问题真的会疯。
本地开发 VS 生产环境差异巨大
在 MacBook 上跑得好好的脚本,放到实验室的 Ubuntu 服务器就崩。原因?字体缺失、GPU 驱动、共享内存不足……尽早用 Docker 容器化,能省下无数头发。前端不只是“切图仔”
这个项目让我重新认识到:现代前端工程师必须懂运行时、懂性能、懂网络协议。否则连一个“为什么页面加载慢”都说不清楚。
效果与反思
目前系统已爬取 500+ 真实网站,生成了初步的性能报告。导师看了直呼“有潜力”,虽然我知道离真正可用还差得远——比如样本偏差、指标噪声、模型准确率等问题还没解决。
但对我个人而言,这次探索最大的收获不是技术本身,而是重建了“动手验证”的习惯。在学校容易陷入“只读论文不动手”的陷阱,而在公司又容易“只搬砖不思考”。这次项目,算是找到了中间的平衡点。
顺便,我也在悄悄为跳槽做准备。最近面试了几家做 AI Infra 的公司,他们对“前端 + 爬虫 + 数据 pipeline”的复合能力很感兴趣。看来,跨界折腾,有时候真能变成简历亮点。
最后的小建议
如果你也在做类似的技术探索,记住三点:
- 从小目标开始:先跑通一个 URL,再扩展到 100 个;
- 善用工具链:VSCode 插件(比如 Prettier、ESLint、REST Client)能极大提升效率;
- 别怕“重复造轮子”:理解原理比调用 API 重要得多。
写这篇文章的时候,我的爬虫还在后台跑着。屏幕上不断刷出新的日志,像极了当年在公司加班上线的样子。只不过现在,我不再是为了 KPI,而是为了搞清楚一个问题的答案。
这种感觉,还挺爽的。
(完)
作者:某 211 软件工程研二,前三年前端开发,现 AI 探索者。
GitHub:匿了(代码太丑不敢放)
下期预告:《用 TensorFlow.js 在浏览器里跑 LLM?我的翻车实录》

评论 0