被考研毒打后,我用Node.js爬了招聘网站
去年十二月查完成绩那天,我瘫在宿舍床上刷了一整晚BOSS直聘。358分,离目标线差12分。第二天我就删了所有考研资料,转头投简历——毕竟房租不会因为“二战”就自动减免。
入职这家电商小厂三个月,每天都在给前端同事擦屁股(别打我,他们其实人挺好的)。上周产品经理甩过来一个需求:“咱们得监控竞品的价格变动,做个自动化爬虫,每天凌晨跑一次。” 我心里一万个MMP:这不就是典型的“又想马儿跑,又想马儿不吃草”吗?但想到下个月要交的房租,还是默默打开了VS Code。
好在我之前考研期间为了调剂信息写过几个Python爬虫,但这次团队要求用Node.js——理由是“技术栈统一”。行吧,谁让我现在是个卑微的应届打工人呢?
为什么选Node.js做后端爬虫?
说实话,一开始我是抗拒的。JavaScript不是应该待在浏览器里吗?但真上手后发现,Node.js搞爬虫居然比Python还爽:
- 异步非阻塞:抓100个页面不用开100个线程,内存占用低到感人
- 生态丰富:npm上各种HTTP客户端、HTML解析器,装完就能用
- 全栈打通:爬下来的数据直接喂给前端图表,不用跨语言传参
特别是我们公司用K8s部署服务,Node.js镜像才100多MB,运维大哥看了都说好(虽然他上次把我的Pod调度到故障节点导致爬虫挂了三天,这事咱先不提)。
从零搭建你的第一个爬虫服务
初始化项目
mkdir price-tracker && cd price-tracker
npm init -y
安装核心依赖:
npm install axios cheerio node-cron express
axios:发HTTP请求(比原生fetch好用多了)cheerio:服务器版jQuery,解析HTML一把好手node-cron:定时任务,替代Linux crontabexpress:轻量Web框架,方便后续加API接口
写个能跑起来的Hello World
创建 server.js:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: "爬虫服务已启动!" });
});
app.listen(PORT, () => {
console.log(`🚀 服务运行在 http://localhost:${PORT}`);
});
执行 node server.js,浏览器打开 http://localhost:3000 看到JSON就说明环境搭好了。这时候我掏出手机给前考研室友发消息:“看!老子也能写后端了!”(他回了个狗头)
实战:抓取某宝商品价格
⚠️ 注意:遵守robots.txt,别搞DDoS,否则法务函会比offer先到
假设我们要抓这个URL:https://item.taobao.com/item.htm?id=123456789
// crawler.js
const axios = require('axios');
const cheerio = require('cheerio');
async function scrapePrice(itemId) {
try {
const url = `https://item.taobao.com/item.htm?id=${itemId}`;
// 模拟浏览器请求头(防反爬基础操作)
const headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
};
const response = await axios.get(url, { headers });
const $ = cheerio.load(response.data);
// 淘宝的价格结构...咳咳,这里简化了
// 实际项目中可能需要处理动态加载、加密参数等
const price = $('.price').text().trim();
return { itemId, price, timestamp: new Date() };
} catch (error) {
console.error(`抓取失败 [${itemId}]:`, error.message);
// 真实场景应该记录到日志系统,而不是只console
return null;
}
}
module.exports = { scrapePrice };
血泪教训:第一次跑的时候没加User-Agent,直接被返回验证码页面。当时真的想砸电脑——但想起房贷计算器上的数字,深呼吸三次继续debug。
定时任务+数据持久化
光抓数据不够,得存起来。我们用SQLite(轻量,适合小项目):
npm install sqlite3
// db.js
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./prices.db');
// 创建表(如果不存在)
db.serialize(() => {
db.run(`CREATE TABLE IF NOT EXISTS prices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id TEXT,
price TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
});
function savePrice(data) {
const stmt = db.prepare('INSERT INTO prices (item_id, price) VALUES (?, ?)');
stmt.run(data.itemId, data.price, (err) => {
if (err) console.error('数据库写入失败:', err);
});
stmt.finalize();
}
module.exports = { savePrice };
整合到定时任务:
// scheduler.js
const cron = require('node-cron');
const { scrapePrice } = require('./crawler');
const { savePrice } = require('./db');
// 监控的商品ID列表(实际应该从配置文件读取)
const ITEM_IDS = ['123456789', '987654321'];
// 每天凌晨2点执行(避开业务高峰)
cron.schedule('0 2 * * *', async () => {
console.log('⏰ 开始执行价格监控任务...');
for (const id of ITEM_IDS) {
const data = await scrapePrice(id);
if (data) {
savePrice(data);
console.log(`✅ 已保存 [${id}] 价格: ${data.price}`);
}
// 加个延迟避免被封IP
await new Promise(resolve => setTimeout(resolve, 2000));
}
console.log('🔚 今日任务完成');
});
最后在 server.js 里引入定时器:
// ...前面的express代码
require('./scheduler'); // 启动定时任务
app.listen(...);
部署上线:从本地到云原生
本地跑通只是开始,真正的挑战是上线。我们公司用GitLab CI + K8s,但作为新手,我先用最简单的方案:
- 代码托管到GitHub(私有仓库,别让竞对看到)
- Docker容器化(运维说这样他少背锅)
Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
构建镜像:
docker build -t price-tracker .
docker run -d -p 3000:3000 --name tracker price-tracker
线上事故复盘:第一次部署时忘了把SQLite数据库挂载到持久卷,结果Pod重启后数据全丢。运维在群里@我:“兄弟,你这监控数据比薛定谔的猫还不稳定啊?” 现在每次写数据服务,我都会默念三遍:“持久化!持久化!持久化!”
给跳槽人的建议:别只学语法
现在我边工作边刷LeetCode准备跳槽,但深刻体会到:企业要的不是会写Hello World的人,而是能解决实际问题的人。
如果你也在学Node.js,别死磕官方文档。试试这些实战方向:
| 场景 | 推荐学习点 | 能力提升 |
|---|---|---|
| 做爬虫 | 请求重试、代理池、验证码识别 | 异常处理 & 反爬对抗 |
| 写API | JWT鉴权、输入校验、错误中间件 | 安全意识 & 架构思维 |
| 部署服务 | Docker、健康检查、日志收集 | DevOps协作能力 |
上周面试一家大厂,面试官问:“如果爬虫被封了怎么办?” 我直接掏出手机展示自己用Redis实现的分布式代理池方案(其实是Claude帮我写的,但别告诉他)。最后拿了口头offer,虽然薪资还没达到预期...
结语:失败是成功之母,但房租不是
从考研落榜到能独立开发后端服务,Node.js给了我第二条路。它或许不是最高效的爬虫工具,但对于想快速验证想法、打通前后端的开发者来说,真的是神器。
如果你也是被生活毒打的应届生,记住:技术没有高低贵贱,能解决问题的就是好技术。我现在每天下班回家还会刷两道算法题,但白天的工作让我明白——真实世界的代码,远比LeetCode复杂得多。
对了,我把完整代码放GitHub上了(脱敏版),链接就不贴了,免得被说打广告。Star数破100的话,我就更新用Puppeteer处理动态渲染的方案!
最后吐槽一句:产品经理刚又提新需求,“能不能顺便把评论情感分析也加上?” —— 我微笑着打开招聘软件...

评论 0