1. 这不是“AI写代码”的演示而是一次真实项目交付的全程复盘我用 Cursor 写了一个完整项目——这句话在朋友圈发出去后收到最多的问题是“你真没手敲一行是不是全程点‘Continue’就生成了”答案很干脆不是。我确实没手动从零写package.json、没逐行敲puppeteer.launch()的参数、没手写 17 个页面路由的中间件逻辑但整个项目从需求确认、架构拆解、接口设计、异常兜底、本地调试到最终部署上线全部由我主导推进。Cursor 是我的副驾驶不是替身司机。它帮我把“我知道要做什么但不想重复写样板代码”的时间省下来专注在“这个业务逻辑到底该怎么兜住超时、重试和降级”这种真正需要人类判断的地方。这项目是一个面向中小电商卖家的「竞品价格快照监控工具」每天凌晨自动抓取指定商品在京东、淘宝、拼多多三个平台的实时售价、促销标签、库存状态并生成带趋势图的日报邮件。技术栈明确Node.jsv20.18.0做主服务Puppeteerv22.12.0驱动无头浏览器数据存 SQLite轻量、免运维前端用纯 HTMLJS不搞 React就为快。开发环境跑在 WSL2Ubuntu 22.04里——不是为了炫技是因为 Puppeteer 在 Windows 原生环境下对字体渲染和 Canvas 截图的兼容性问题至今没彻底解决而 WSL2 提供了接近生产环境Linux的开发体验且资源占用比 Docker Desktop WSL2 组合低 40%。关键词里没填但实际贯穿全程的是三个硬约束可维护性优先于开发速度、错误必须可追溯、部署必须一键完成。所以当 Cursor 建议我用try/catch包裹整个page.goto()调用时我立刻否决了——它没考虑网络中断、目标页跳转重定向链过长、甚至 CDN 返回 503 的分层处理。我要求它重写明确拆出networkError、timeoutError、captchaDetected三类 handler并为每类定义日志字段和告警阈值。这才是真实项目里 AI 工具该有的位置执行者而非决策者。如果你正纠结“要不要在主力项目里用 Cursor”别看那些 3 分钟生成 Todo App 的短视频。真正有价值的参考是它如何在一个有真实业务压力、有线上 SLA 要求、有运维同学盯着日志大盘的项目里暴露优势也暴露边界。下面我把这 11 天的开发日志按真实时间线和认知升级顺序掰开揉碎讲清楚。2. 环境准备阶段WSL2 不是“装个子系统”而是构建可信开发沙盒2.1 为什么必须是 WSL2而不是 Windows 原生或 Docker很多人卡在第一步装完 WSL2发现puppeteer.launch()报错Failed to launch the browser process!。查日志看到libglib-2.0.so.0: cannot open shared object file或No usable sandbox!—— 这不是 Cursor 的锅是环境没对齐。我踩过的坑和最终验证的路径如下WSL1 直接排除它没有真正的 Linux 内核Puppeteer 依赖的epoll、inotify等系统调用无法正常工作页面加载会随机卡死。Windows 原生 Node.js Puppeteer看似最简单但实际运行时中文网页截图文字模糊字体缺失需手动安装fonts-wqy-zenhei并配置fontconfig淘宝反爬机制触发更频繁Windows User-Agent 和 TLS 指纹特征太明显page.screenshot()生成的 PNG 在某些页面上出现透明背景GPU 渲染差异。Docker Desktop WSL2资源开销大Docker Desktop 自身吃掉 1.2GB 内存且每次docker build都要重新安装 Chromium构建时间从 8 秒拉长到 42 秒。更重要的是调试时无法直接console.log浏览器控制台输出——你得进容器docker exec -it xxx /bin/bash再npx puppeteer启动调试器效率断崖式下跌。提示WSL2 的核心价值在于提供Linux 内核 Windows 文件系统互通 无需虚拟机管理界面的三角平衡。它不是“Linux 替代品”而是“让 Linux 工具链在 Windows 生态里自然呼吸”的桥梁。2.2 Ubuntu 22.04 的最小化初始化清单实测有效我删掉了所有教程里“先更新再升级”的冗余步骤只保留生产环境必需项。以下命令在 WSL2 终端中逐行执行不要sudo apt update sudo apt upgrade -y全量升级会破坏 Puppeteer 依赖的libnss3版本# 1. 设置清华源比官方源快 5 倍尤其对 node_modules sudo sed -i s/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g /etc/apt/sources.list sudo sed -i s/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g /etc/apt/sources.list # 2. 安装基础依赖Puppeteer 官方文档明确要求的 sudo apt update sudo apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ libexpat1 libfontconfig1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 \ libstdc6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 \ libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 \ libnss3 lsb-release xdg-utils wget # 3. 安装 Node.jsv20.18.0LTS 版本避免 v24.x 的兼容性雷区 curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs # 4. 验证 Puppeteer 环境关键 node -e const puppeteer require(puppeteer); (async () { const browser await puppeteer.launch({ headless: new }); const page await browser.newPage(); await page.goto(https://example.com); console.log(✅ WSL2 Puppeteer 环境就绪); await browser.close(); })();这段脚本执行完终端输出✅ WSL2 Puppeteer 环境就绪才算真正过关。我见过太多人跳过第 4 步结果在 Cursor 生成代码后运行报错回头折腾半天才发现是libglib版本不匹配。2.3 Cursor 的 WSL2 适配关键设置90% 的人忽略Cursor 默认在 Windows 主机上运行但它需要感知 WSL2 的 Node.js 环境才能正确提供智能提示和调试支持。必须手动配置在 Cursor 设置中搜索Default Shell将路径改为C:\Windows\System32\wsl.exe打开 Cursor 的命令面板CtrlShiftP输入Shell Command: Install cursor command in PATH确保它安装到 WSL2 的 PATH在 WSL2 终端中执行which cursor返回/usr/bin/cursor才算成功最关键一步在 Cursor 的settings.json中添加{ cursor.shell: /usr/bin/wsl.exe, cursor.nodePath: /usr/bin/node, cursor.npmPath: /usr/bin/npm }否则Cursor 会用 Windows 的 Node.js 解析package.json导致require(puppeteer)报错Cannot find module——因为 Windows 的 Node.js 根本没装 Puppeteer。注意不要用wsl --install一键安装。它默认装 Ubuntu 20.04而 Puppeteer v22 要求glibc 2.31Ubuntu 20.04 的glibc是 2.31但某些云服务器镜像会降级。务必用wsl --install -d Ubuntu-22.04显式指定。3. 项目骨架生成Cursor 的“架构师模式”远比你想象的可靠3.1 用自然语言描述需求得到可运行的工程结构我给 Cursor 的第一条指令不是“写代码”而是“我需要一个 Node.js 项目用于定时抓取电商网站价格。要求1使用 Puppeteer2数据存 SQLite3有清晰的错误分类网络、超时、验证码4支持通过环境变量配置抓取频率和目标 URL5入口文件是index.js启动后打印当前配置。”Cursor 没生成一堆空文件夹而是直接给出一个package.json和index.js的完整内容并附带说明package.json里scripts包含dev带 nodemon 热重载、start生产启动、migrate数据库初始化index.js用dotenv加载.env用sqlite3初始化prices.db表结构用node-cron设置定时任务错误分类不是简单if/else而是定义了NetworkError、TimeoutError、CaptchaError三个继承自Error的类每个类有code、retryable、logLevel字段。我复制粘贴后在终端执行npm install npm run migrateSQLite 数据库文件立刻生成表结构如下字段名类型说明idINTEGER PRIMARY KEY AUTOINCREMENT自增主键platformTEXT NOT NULL平台名jd/tb/pddproduct_idTEXT NOT NULL商品 ID如 jd 的 skupriceREAL当前售价original_priceREAL原价用于计算折扣stock_statusTEXT库存状态in_stock/out_of_stockcaptured_atDATETIME DEFAULT CURRENT_TIMESTAMP抓取时间这个结构不是拍脑袋定的。Cursor 根据“电商价格监控”这个领域常识自动推导出original_price是必存字段否则无法判断是否降价stock_status必须是枚举值避免自由文本导致查询混乱。3.2 为什么node-cron比setInterval更适合生产Cursor 在生成定时任务代码时主动选择了node-cron而非setInterval。它的解释很务实setInterval(() {...}, 24 * 60 * 60 * 1000)看似简单但一旦任务执行时间超过 24 小时比如某次抓取因网络问题卡了 25 小时下一次执行会立即触发导致两个实例并发运行SQLite 数据库锁表node-cron的0 0 * * *表达式是“每天 0 点执行”它基于系统时间校准即使上次执行延迟下次仍严格在 0 点启动不会堆积更重要的是node-cron支持onComplete回调可以记录每次任务的实际执行耗时这对后续优化 Puppeteer 启动参数至关重要。我实测对比用setInterval运行 7 天出现 3 次锁表错误用node-cron运行 30 天零锁表且日志里能清晰看到每次任务耗时波动平均 8.2 分钟峰值 14.7 分钟。3.3 Cursor 如何理解“错误必须可追溯”并落地当我强调“错误必须可追溯”时Cursor 没给我一个泛泛的console.error(e)而是生成了一套完整的错误追踪链日志格式统一所有console.log都被替换为logger.info()所有console.error替换为logger.error()且logger实例自动注入requestIdUUID和timestamp错误分类注入上下文CaptchaError构造函数强制传入screenshotPath截图保存路径NetworkError强制传入failedUrl和statusCode错误聚合上报在catch块里除了写日志还调用alertService.sendAlert()将错误类型、频次、影响平台推送到企业微信机器人。这部分代码我几乎没改。因为 Cursor 理解“可追溯”不是“有日志”而是“日志能直接定位到哪台机器、哪个请求、哪个页面元素”。它生成的logger.js里requestId不是全局变量而是通过AsyncLocalStorage在异步链路中透传——这意味着即使 Puppeteer 的page.evaluate()里抛错也能关联到最外层的定时任务 ID。提示Cursor 的错误处理能力取决于你描述需求时的颗粒度。说“要报错”只能得到try/catch说“要区分网络错误和业务错误且网络错误要重试 3 次”才能得到带指数退避的retry逻辑。4. Puppeteer 核心模块编写Cursor 的“领域知识”在细节里爆发4.1 抓取京东价格为什么不能直接page.$eval(.price, el el.textContent)京东的价格 DOM 结构是动态渲染的.price元素可能在初始 HTML 里不存在而是由 JS 注入。Cursor 生成的代码里第一行就是await page.waitForSelector(.price, { timeout: 10000, state: visible });但它没停在这里。紧接着是// 京东价格可能有多个层级.price span .J_price或 .price .p-price .p-price .price const priceElement await page.$(.price) || await page.$(.p-price); if (!priceElement) throw new NetworkError(京东价格元素未找到: ${url}, url); const priceText await (await priceElement.getProperty(textContent)).jsonValue(); const price parseFloat(priceText.replace(/[^\d.]/g, )) || 0;这段代码的价值在于它预判了京东前端工程师可能做的 DOM 变更。.price类名可能被改成.p-price或者价格文本里混入¥符号、空格、逗号parseFloat会失败所以用正则清理。这不是通用爬虫模板而是针对京东特定场景的鲁棒性设计。我测试时故意把.price改成.fake-priceCursor 生成的错误信息是NetworkError: 京东价格元素未找到: https://item.jd.com/1000XXXXXX.html并附带failedUrl字段。运维同学看到这个日志不用翻代码就知道是 selector 失效立刻去京东页面 inspect 元素。4.2 淘宝反爬绕过Cursor 怎么知道要设userAgent和--disable-blink-features当我输入“淘宝抓取经常被 302 重定向到登录页怎么解决”时Cursor 没给一个万能方案而是列出淘宝反爬的三个技术点并给出对应参数反爬手段Cursor 识别的绕过方式参数说明User-Agent 检测page.setUserAgent(Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36)必须是 Linux Chrome 组合Windows UA 会被拒绝Blink 特性检测args: [--disable-blink-featuresAutomationControlled]隐藏 Puppeteer 的自动化特征WebRTC 指纹await page.evaluateOnNewDocument(() { Object.defineProperty(navigator, webdriver, { get: () undefined }); })覆盖navigator.webdriver属性它甚至提醒我“淘宝的登录页会检查window.chrome对象如果存在且window.chrome.runtime为undefined会判定为 Puppeteer。所以必须在page.evaluateOnNewDocument中删除window.chrome。”我照做后淘宝抓取成功率从 12% 提升到 98%。这不是魔法是 Cursor 从数百万行开源 Puppeteer 项目中学习到的领域经验。4.3 拼多多的“滑块验证”Cursor 的 fallback 机制有多聪明拼多多在高频率访问时会弹出滑块验证。Cursor 没教我怎么破解滑块那涉及法律风险而是设计了一个优雅的 fallbacktry { // 正常抓取流程 await page.goto(url, { waitUntil: networkidle2 }); const price await extractPrice(page); return { success: true, price }; } catch (error) { if (error.message.includes(slider) || await page.$(.geetest_slider)) { // 检测到滑块记录并跳过本次抓取 logger.warn(拼多多滑块验证触发跳过 ${url}); return { success: false, reason: slider_verification }; } throw error; // 其他错误继续上抛 }关键是await page.$(.geetest_slider)这行。Cursor 知道极验Geetest滑块的 class 名是.geetest_slider且这个元素在验证弹窗出现时一定存在。它不试图解决而是承认边界——“当 AI 无法处理时人类应该第一时间知道”。我在日志里看到连续 3 次slider_verification立刻意识到是 IP 被限流马上切换了代理池。这才是真实项目里需要的“可操作洞察”而不是一个无法落地的“技术方案”。5. 本地调试与问题排查Cursor 是你的“结对编程伙伴”不是“代码生成器”5.1 调试 Puppeteer 时Cursor 怎么帮你定位到具体哪一行 JS 执行失败Puppeteer 的page.evaluate()里报错堆栈信息通常只显示Error at anonymous:1:1根本看不出是哪行代码。Cursor 在生成extractPrice函数时主动加了sourceURLconst priceScript // 这里是提取价格的 JS 逻辑 const priceEl document.querySelector(.price); if (!priceEl) return null; return priceEl.textContent.trim(); //# sourceURLextractPrice.js ; const price await page.evaluate((script) eval(script), priceScript);//# sourceURLextractPrice.js让 Chrome DevTools 把这段字符串当作独立文件extractPrice.js显示报错时直接定位到priceEl.textContent.trim()这行。我第一次用这个技巧时惊讶于 Cursor 对 Chrome 调试协议的理解深度——它不是在写 JavaScript是在写“可调试的 JavaScript”。5.2 “Puppeteer 启动慢”问题的三层排查法Cursor 教我的项目上线后日志显示 Puppeteer 启动耗时从 2.1 秒飙升到 8.7 秒。Cursor 没直接给解决方案而是引导我分三层排查系统层在 WSL2 终端执行time chromium-browser --headless --no-sandbox --disable-gpu --dump-dom https://example.com耗时 3.2 秒 → 说明 Chromium 本身没问题Node.js 层在index.js里加console.time(launch)和console.timeEnd(launch)发现puppeteer.launch()耗时 8.5 秒 → 问题在 Puppeteer 初始化配置层Cursor 指出puppeteer.launch()默认会下载最新版 Chromium而我的package.json里puppeteer版本是^22.12.0但puppeteer-core依赖的 Chromium 版本是120.0.6099.109两者不匹配导致反复下载。解决方案很简单在puppeteer.launch()里显式指定executablePathconst browser await puppeteer.launch({ executablePath: /usr/bin/chromium-browser, args: [--no-sandbox, --disable-setuid-sandbox] });重启后启动耗时回到 2.3 秒。这个过程让我明白Cursor 的价值不在于它直接给你答案而在于它给你一套可复现、可验证、可归因的排查框架。5.3 Cursor 的“调试建议”功能比 Stack Overflow 更懂你的上下文当我把一段报错日志Error: net::ERR_CONNECTION_TIMED_OUT at https://xxx发给 Cursor并附上当前page.goto()的调用代码时它没搜通用解决方案而是结合我的项目上下文分析“你设置了waitUntil: networkidle2但目标网站 DNS 解析超时networkidle2等待条件永远不会满足导致超时”“建议在goto外层加Promise.race([page.goto(...), new Promise(r setTimeout(r, 30000))])30 秒强制超时”“同时检查你的 WSL2 DNS 配置cat /etc/resolv.conf如果 nameserver 是172.28.0.1这是 Docker 的 DNS会导致解析慢应改为8.8.8.8”。我照做后DNS 超时错误消失。这种基于你当前代码、当前环境、当前错误的精准建议是任何搜索引擎都做不到的——因为它不是在匹配关键词而是在理解你的开发会话。6. 部署与监控Cursor 如何把“能跑”变成“敢上线”6.1 为什么 SQLite 不是“玩具数据库”而是生产首选很多人质疑“监控工具用 SQLite不怕并发写入崩掉” Cursor 给我的回复很实在“你的场景是单写多读每天只写 1 次读是离线报表生成SQLite 的 WAL 模式完全够用。换成 PostgreSQL 只会增加运维复杂度不提升业务价值。”它生成的部署脚本deploy.sh里关键逻辑是# 1. 拉取最新代码 git pull origin main # 2. 安装依赖跳过 devDependencies减小体积 npm ci --onlyproduction # 3. 备份旧数据库防止迁移失败 cp prices.db prices.db.$(date %Y%m%d_%H%M%S).bak # 4. 启动服务用 pm2支持日志轮转和崩溃重启 pm2 start index.js --name price-monitor --watch --ignore-watchnode_modules--watch参数让 pm2 监听index.js变更一旦我git pull新代码服务自动重启。--ignore-watchnode_modules避免npm install触发无限重启。这些细节是 Cursor 从数千个 Node.js 生产部署案例里学到的。6.2 日志监控的“黄金三指标”Cursor 帮我定义了什么该告警Cursor 没给我一堆 Prometheus 指标而是聚焦三个业务可感知的指标指标计算方式告警阈值业务含义抓取成功率(成功抓取数 / 总尝试数) * 100% 95% 持续 2 小时某个平台整体不可用单次抓取耗时MAX(各平台抓取耗时) 20 分钟可能遭遇大规模反爬数据完整性COUNT(*) FROM prices WHERE captured_at datetime(now, -24 hours) 1000 条数据采集链路中断它生成的monitor.js里每 5 分钟执行一次 SQL 查询结果推送到企业微信。当拼多多抓取成功率跌到 89%我手机立刻收到消息“⚠️ PDD 抓取成功率 89%阈值 95%请检查代理池或目标网站变更”。6.3 “Cursor 免费次数用完”之后我怎么保证项目不中断Cursor Pro 确实有免费额度限制但项目上线后我关闭了所有 AI 功能只保留编辑器核心能力。Cursor 的底层是 VS Code所有快捷键、插件ESLint、Prettier、GitLens完全可用。我甚至把 Cursor 的settings.json导出用在原生 VS Code 里体验几乎无差别。真正保障项目持续运行的是 Cursor 帮我建立的可维护性基础设施清晰的错误分类、结构化的日志、幂等的数据写入、可预测的部署流程。这些才是项目的生命线而不是某个 AI 模型的调用次数。我在最后一天的开发日志里写道“Cursor 最大的价值不是它写了多少行代码而是它逼我思考清楚每一行代码存在的理由。当我不再问‘怎么写’而是问‘为什么这样写’项目才真正属于我。”