马克沁机枪上阵(一):Claude Code 如何用 AI 碾压研发战场

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

1893 年马塔贝莱战争,700 名英军凭 4 挺马克沁机枪,击溃了 5000 名马塔贝莱战士的冲锋,对方伤亡超 2000 人,英军仅阵亡 4 人。这不是勇气的胜负,是技术代差的绝对碾压。

当我第一次用 Claude Code 开发 BaikePublisher(百度百科自动发布系统)时,我脑海里浮现的正是这幅画面。

那些曾经让开发者抓狂的研发任务——Playwright 选择器调试、Spring Boot 循环依赖、Hibernate 类型校验、多线程 Cookie 并发——就像潮水一般涌来的马塔贝莱战士,凶悍、密集、且不断变化阵型。

而 Claude Code,就是那挺马克沁机枪


一、战场:BaikePublisher 是什么

在正式开打之前,先交代清楚战场地形。

BaikePublisher 是一个 Spring Boot 3.3 + Playwright 1.44 的全栈浏览器自动化系统。它的核心使命是:接收一个词条名称和内容,然后全程无人值守地完成百度百科的发布流程——从登录、搜索、创建词条,到填写摘要、提交审核,一气呵成。

最终实现的自动化流程共 8个原子步骤

登录(Cookie优先/QR码降级)
  ↓
搜索词条(判断新建 or 编辑)
  ↓
进入创建/编辑页面
  ↓
选择词条类型(如"科技/软件")
  ↓
关闭编辑器新手引导弹窗
  ↓
填写义项名
  ↓
填写概述(至少10字)
  ↓
填写正文 → 提交审核 → 选择"词条未收录"原因 → 确认

每一步都有截图存档,每一步的耗时和状态写入 task_log 表,任意步骤失败后支持按 task_no 重跑。

系统交付物一览:

类型

数量

Java 核心类

17 个

REST API 端点

8 个

Playwright 页面动作方法

14 个

数据表

3 张(账号、任务、日志)

参考 HTML 文件

14 个

这是一场需要同时精通 Spring 生态、Playwright 自动化、百度前端逆向、并发安全设计、数据库迁移的多线战役。

放在以往,这样的系统从零到可运行,少说 3~5 天,每个技术坑都可能卡你大半天。

Claude Code 用了不到 4 小时


二、第一轮齐射:编译错误与循环依赖

战役刚开始,敌军就冲来了第一波——编译无法通过

Spring 经典的循环依赖:

TaskService ← BaikeService ← TaskService

这种问题传统解法要么加 @Lazy,要么费心重新拆分依赖关系,往往需要你从头梳理调用链,光是理清楚谁依赖谁就要花不少时间。

我只对 Claude Code 说了一句话:

"修复 TaskService 和 BaikeService 之间的循环依赖"

它扫描两个类的完整依赖链,精准识别根因:TaskService.submit() 原本返回 String,TaskController 里同时注入了 TaskService 和 BaikeService,后者又回调前者。解法干净利落:

  1. TaskService.submit() 改为返回 PublishTask 实体,让 Controller 拿到 ID
  2. baikeService.execute(task.getId()) 调用迁移到 TaskController,彻底切断环
  3. BaikeService 只依赖 TaskService 的状态更新方法(updateRunning/updateSuccess/updateFailed)

修改 3 个文件,0 误改,一次编译通过。

紧接着第二波:Hibernate 类型校验异常轰炸上来:

Error creating bean 'entityManagerFactory'
found [text (Types#LONGVARCHAR)], but expecting [tinytext (Types#CLOB)]

这是 Hibernate 6 的一个典型陷阱——@Lob String 在新版本映射为 TINYTEXT,而数据库建的是 TEXT,类型对不上就直接拒绝启动。Claude Code 不需要你解释这个历史包袱,它直接给出正解:

// 删除 @Lob,用 columnDefinition 明确指定
@Column(columnDefinition = "MEDIUMTEXT") private String content;
@Column(columnDefinition = "TEXT")       private String errorMsg;
@Column(columnDefinition = "TEXT")       private String message;  // TaskLog

不需要翻 Hibernate 6 迁移文档,不需要 Stack Overflow。机枪就是这么扫的。


三、第二轮:Playwright 选择器攻坚战——敌军最凶猛的一波

这才是真正的马塔贝莱战士冲锋。凶猛、密集,而且每次换一个阵型。

百度的前端是 React SPA,元素动态渲染,TANGRAM ID 随机生成,class 名带 hash 后缀。靠猜是没有出路的。我采用的战术是:每次报错,把那个页面的 HTML 另存下来,连同报错栈一起喂给 Claude Code。

第一刀:QR 码登录页选择器

TimeoutError: waiting for '#TANGRAM__PSP_11__qrcodeImg' to be visible

原来 TANGRAM ID 不是 PSP_11,查 HTML 发现是 PSP_3,而且这个 ID 下次还会变。Claude Code 分析 登录百度账号.html 后,提出根本解法——彻底放弃 TANGRAM ID,改用稳定的语义 class:

private static final String QRCODE_SECTION     = ".tang-pass-qrcode";
private static final String QRCODE_WRAPPER     = ".tang-pass-qrcode-imgWrapper";
private static final String QRCODE_IMG         = ".tang-pass-qrcode-img";
private static final String QRCODE_REFRESH_BTN = ".Qrcode-refresh-btn";

同时,QR 码图片 wrapper 初始是 display:none,刷新后变 block,不能用 :visible 等待。Claude Code 给出:

page.waitForFunction(
    "() => { const el = document.querySelector('.tang-pass-qrcode-imgWrapper'); " +
    "return el && getComputedStyle(el).display !== 'none'; }",
    null, new Page.WaitForFunctionOptions().setTimeout(15000));

第二刀:创建词条选择器解析崩溃

PlaywrightException: Unexpected token "=" while parsing selector
"a[href*='create'], .create-lemma-btn, text=创建词条"

CSS 属性选择器里的单引号与 text= 引擎不能混写在同一个逗号列表里,这是 Playwright 的解析规则。初学者很容易掉进这个坑。Claude Code 直接给出正确拆分方式:

page.locator("a[href*=\"create\"], .create-lemma-btn")
    .or(page.getByText("创建词条"))
    .first().click();

第三刀:进入编辑器后点了企业词条入口

百度的创建词条页有两个按钮:普通词条 #create-lemma-btn 和企业词条 #enterprise-create-btn。原来通过搜索结果页跳转,a[href*="create"] 会同时匹配到企业词条链接,导致走错流程。

Claude Code 分析 创建词条_百度百科.html 后,提出直接导航到创建页面,绕开搜索结果的歧义:

private static final String CREATE_INDEX_URL = "https://baike.baidu.com/page/createindex";

// clickCreate() 直接导航,不再从搜索结果点击
page.navigate(CREATE_INDEX_URL);
page.locator("#create-lemma-ipt").fill(entryName);
page.locator("#create-lemma-btn").first().click();  // 精确点普通词条按钮

第四刀:React 编辑器拒绝 fill()

在 contenteditable 的富文本区域调用 Locator.fill() 没有任何反应——React 编辑器根本不是 <input>,也不监听 Playwright 的原生填充事件。Claude Code 给出双层降级方案,先 JS 注入,失败再键盘模拟:

// 第一层:JavaScript 直接写入,触发 React 的 input 事件
Boolean filled = (Boolean) page.evaluate("""
        (content) => {
            const el = document.querySelector('.eeditor__editor--context');
            if (el) {
                el.focus();
                el.innerText = content;
                el.dispatchEvent(new Event('input', {bubbles: true}));
                return true;
            }
            return false;
        }""", content);

// 第二层:键盘模拟兜底
if (Boolean.FALSE.equals(filled)) {
    Locator editor = page.locator(".eeditor__editor--context").first();
    editor.waitFor();
    editor.click();
    page.keyboard().press("Control+A");
    page.keyboard().type(content);
}

第五刀:提交后 URL 永远超时

// 原来的判断条件
page.waitForURL(url -> url.contains("/item/") || url.contains("/view/"),
        new Page.WaitForURLOptions().setTimeout(timeout));

词条提交后页面一直等,等了 30 秒超时。把 百度百科——版本提交成功.html 拖进目录,Claude Code 秒查出真相:词条提交后进入审核队列,页面跳转到 submitSuccessful,根本不跳 /item/。修复:

page.waitForURL(
    url -> url.contains("submitSuccessful")
           || url.contains("/item/")
           || url.contains("/view/"),
    new Page.WaitForURLOptions().setTimeout(timeout));

这五把刀,每一个放在过去都能让人卡半天。Claude Code 平均每个不超过 3 分钟。


四、Claude Code 的核心战术:HTML 作为弹药库

在这次开发中,我摸索出了一个关键战术,也是整个项目效率最高的方法论:

把实际页面的 HTML 下载到项目目录,作为 Claude Code 的"弹药补给"。

每次 Playwright 超时或报错,我就把那个百度页面在浏览器里"另存为完整网页",丢进 src/main/resources/baidu/,然后告诉 Claude Code:

"根据 baidu 文件夹下的 html 文件修改这个 bug"

Claude Code 会用 grep 把所有关键选择器、class 名、data 属性挖出来,精准定位问题,给出有实际 HTML 作为依据的修复方案,而不是凭空猜测。

整个项目累计下载了 14 个页面:

HTML 文件

解决的问题

登录百度账号.html

QR 码登录 class 选择器

创建词条_百度百科.html

普通/企业词条按钮区分

创建引导页_百度百科.html

引导页跳过按钮 [data-option="ignore"]

词条类型.html

类型选择对话框 .select-type-dialog 结构

编辑器新手引导.html

引导弹窗跳过按钮 .guide-wv-btn-cancel

词条创建原因.html

提交原因弹窗结构 .bke__modal-save

百度百科——版本提交成功.html

提交后真实跳转 URL submitSuccessful

这就是马克沁机枪的弹药补给机制:给 AI 真实的战场情报,它就能精准射击,而不是朝着雾中的影子乱打。

没有这些 HTML,Claude Code 给出的选择器全是猜测;有了它们,每一个选择器都有据可查,都能在实际 DOM 中找到对应节点。


五、第三轮:功能扩展的连续火力

核心流程跑通之后,需求开始像追加弹药一样不断涌来。Claude Code 展现出了不间断的持久火力,每一波需求都被快速压制。

需求 1:词条类型选择没有被触发

用户反馈:"selectType 没有被调用"。

根因一查即明:task.getLemmaType() 为 null,条件判断 != null && contains("/") 直接跳过了。修复只需两行,并顺手设置默认值:

String lemmaType = (task.getLemmaType() != null && task.getLemmaType().contains("/"))
        ? task.getLemmaType() : "科技/软件";
String[] typeParts = lemmaType.split("/", 2);
step(page, taskId, "SELECT_TYPE", start, () ->
        baikePageAction.selectType(page, typeParts[0].trim(), typeParts[1].trim()));

不再有条件判断,无论如何都会执行,默认值兜底。

需求 2:义项名必须填写

一句话需求,Claude Code 完成了完整的全链路改动

PublishRequest 新增 lemmaDesc 字段(@NotBlank 校验)
  → PublishTask 实体新增 lemmaDesc 列
  → V3__add_lemma_desc.sql 数据库迁移
  → BaikePageAction.fillDesc() 方法(定位 #J-lemma-desc input)
  → BaikeService 插入 FILL_DESC 步骤
  → 编译通过

从 DTO 到数据库迁移到 Playwright 动作,一条链路没有遗漏

需求 3:概述至少 10 字 + 提交弹窗选择原因

两个需求同时来。Claude Code 分析 词条创建原因.html 找到弹窗结构:

<div class="bke__modal-save">
  <button class="reason-item active">
    <span class="reason-item__content">词条未收录</span>
  </button>
  <button class="bke__modal-save__footer__btn active">提交</button>
</div>

精准写出提交流程:

// 1. 点击编辑器提交
page.locator(".feat-save__action__btn--active").first().click();

// 2. 等待原因选择弹窗
page.waitForSelector(".bke__modal-save");

// 3. 选择"词条未收录"
page.locator(".reason-item")
    .filter(new Locator.FilterOptions().setHasText("词条未收录"))
    .first().click();

// 4. 点击弹窗内的提交按钮
page.locator(".bke__modal-save__footer__btn.active").first().click();

需求 4:概述填写前先要处理引导弹窗顺序问题

用户反馈:"先选词条类型,确定后才弹出新手引导"。当时 DISMISS_GUIDE 步骤放在了 SELECT_TYPE 之前,顺序错误。

// 调整前(错误顺序)
DISMISS_GUIDE → SELECT_TYPE → FILL_DESC

// 调整后(正确顺序)
SELECT_TYPE → DISMISS_GUIDE → FILL_DESC

一行注释,两行代码移位,重新编译,问题消灭。

需求 5:按 task_no 直接运行已有任务

新增一个 REST endpoint,5行代码:

@PostMapping("/{taskNo}/run")
public ResponseEntity<Map<String, String>> run(@PathVariable String taskNo) {
    PublishTask task = taskService.rerun(taskNo);   // 重置为 PENDING
    baikeService.execute(task.getId());              // 异步触发执行
    return ResponseEntity.accepted()
           .body(Map.of("taskNo", task.getTaskNo(), "status", "PENDING"));
}

taskService.rerun() 方法之前已经写好只是没暴露出来,Claude Code 直接复用,不重复造轮子。


六、代码清洁度:马克沁不扫平民

有一点值得专门说:Claude Code 不是无差别轰炸,它有工程师的自律。

整个开发过程中,它始终遵守几条不成文的规矩:

只改必要的代码。修 Playwright 选择器,它不会顺手重构 Service 层;加一个字段,它不会把整个 DTO 翻新一遍。改动范围精准到行级别。

每次改完自动验证。每一处修改后,Claude Code 都会主动跑 mvn compile,把编译结果作为下一轮的输入。从不假设"应该能编译过"。

选择器加注释,标注来源。每个 CSS 选择器旁边都有注释,说明它来自哪个 HTML 文件、对应什么 DOM 元素:

/** 编辑器顶部工具栏,出现即表示 React 编辑器已渲染完毕
 * (来源:蓝师傅AI智能客服_百度百科.html  id=J-editor-top-bar)*/
private static final String EDITOR_TOOLBAR_SELECTOR = "#J-editor-top-bar";

/** 词条创建原因弹窗确认按钮
 * (来源:词条创建原因.html  class=bke__modal-save__footer__btn active)*/

数据库迁移保持整洁。开发过程中产生了 V2、V3、V4 三个增量迁移文件,Claude Code 在收尾时主动将它们合并回 V1,整个建表语句一目了然,没有零散补丁:

CREATE TABLE publish_task (
    ...
    lemma_type      VARCHAR(64)  COMMENT '词条类型,如"科技/软件"',
    lemma_desc      VARCHAR(255) COMMENT '义项名',
    lemma_abstract  TEXT         COMMENT '概述内容,至少10个字',
    ...
)

这是精准火力,不是地毯式轰炸。


七、最终战果复盘

战役结束,清点战场:

维度

数据

核心 Java 类

17 个

REST API 端点

8 个

Playwright 页面动作方法

14 个

自动化步骤(每次任务)

8 步

数据库迁移文件

1 个(含全部字段)

参考 HTML 文件

14 个

消灭的 Bug 类型

循环依赖、Lob 类型校验、选择器解析、企业词条误点、contenteditable 填写、QR 码超时、URL 匹配、弹窗顺序

预计人工耗时

3~5 天

实际耗时

< 4 小时


八、给下一场战役的战术手册

从这次开发中提炼出 5 条可复用的实战原则:

原则 1:把错误原文喂给它,不要自己翻译

完整的异常栈 + 原始报错信息,比你"用自己的话描述问题"准确 10 倍。Playwright 的 TimeoutError 直接粘贴,Spring 的启动异常完整复制,不要删减,不要改写。AI 读机器语言比读人类描述更准。

原则 2:把真实 HTML 放进项目目录

前端自动化最大的陷阱是"我以为页面长这样"。真实 HTML 是弹药。把要自动化操作的每一个页面存下来,放进项目,让 AI 自己去 grep 选择器。猜测出来的选择器,十有八九是错的。

原则 3:说结果,不说方案

"提交后页面一直在等,超时了"比"把 waitForURL 的条件改成包含 submitSuccessful"更好。你是指挥官,描述目标和问题现象;AI 是机枪手,它自己知道怎么扫。

原则 4:一次一个清晰任务

Claude Code 的精准来自清晰的任务定义。把多个问题拆开一个一个来,而不是一次性抛出"帮我把所有 bug 都修了"。每次任务的边界越清晰,火力越集中,误伤越少。

原则 5:用编译器做闭环,不要靠感觉

"应该对了吧"是最危险的想法。每次修改后看编译结果,报错继续喂给 Claude Code,形成"修改 → mvn compile → 再修改"的快速闭环。只有编译器通过才算这一波结束,然后继续下一波。


尾声:骑兵时代的终结

1893年之后,没有任何一支骑兵部队愿意正面冲击马克沁阵地。不是因为马塔贝莱战士不够勇猛,更不是因为他们胆小——他们是当时世界上最精锐的骑兵之一。而是因为时代变了,勇猛本身不再是决定胜负的变量。

AI 辅助编程的到来,不是说开发者不再重要。指挥官永远不可或缺。 你需要理解架构,你需要看懂 AI 给出的代码,你需要判断什么时候它的方案是错的,你需要知道整场战役往哪里打。

但那些曾经让人痛苦的"消耗战"——反复调试 CSS 选择器、追循环依赖调用链、翻 Hibernate 迁移文档、写样板式的 CRUD 代码——正在被 Claude Code 这挺马克沁机枪以惊人的速度和精度扫平。

你可以把节省出来的时间,用在真正值得思考的事情上:系统如何扩展到多账号并发?如何接入 AI 生成词条内容?如何监控每日发布成功率?这些才是指挥官该操心的战略问题。

战场还在,但打法已经彻底变了。


本文基于 BaikePublisher 项目的真实开发过程撰写。该项目使用 Spring Boot 3.3 + Playwright 1.44 实现百度百科词条的全自动发布,包含浏览器自动化、Cookie 管理、并发控制、步骤截图存档与完整 REST API。文中所有代码均来自实际生产代码,所有 Bug 均为开发过程中真实遭遇。

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

本数据集来源于 2024 年 7 月在江西省中东部余干县、贵溪市、金溪县丘陵林地采集的千枚岩、红砂岩、花岗岩母质发育红壤关键带剖面土壤实测数据,空间覆盖 3 个县域不同岩性风化壳林地,采样点位经纬度分别为千枚岩剖面 P10(116.8316°E,28.5269°N)、红砂岩剖面 P08(117.1048°E,28.3492°N)、花岗岩剖面 P04(116.6883°E,27.9963°N);垂直空间采样深度存在差异,千枚岩与花岗岩剖面采样深度 0~600 cm,红砂岩剖面采样深度 0~450 cm,垂直分层采样分辨率为 0~50 cm 区间分 0~20 cm、20~50 cm 两层,50 cm 以下土层以 50 cm 为固定间隔分层,整套数据集共包含 36 条土壤剖面分层记录,其中 P10 千枚岩剖面 13 条、P08 红砂岩剖面 11 条、P04 花岗岩剖面 13 条。数据采集时间为 2024 年 7 月,实验室理化指标、矿物测试、酸碱滴定及统计建模工作于 2024 年 7 月 —2026 年 5 月完成,无时间序列连续监测数据,仅为单次野外剖面采样静态数据集。 数据集包含野外剖面基础信息、土壤酸碱滴定原始数据、土壤酸度指标、交换性盐基与交换性酸、土壤机械组成、有机质、黏土与原生矿物半定量 XRD 数据、无定形 / 晶形铁铝氧化物含量。全量理化指标计量单位统规范:酸缓冲容量 pHBC 单位为 cmol・kg⁻¹・pH⁻¹,交换性酸、交换性盐基离子单位为 cmol・kg⁻¹,矿物以质量百分比(%)表示,、黏粒 / 粉粒 / 砂粒、有机质、铁铝氧化物单位均为g/kg,pH 为无量纲数值。 覆盖范围: 中位纬度: 28.2616 中位经度: 116.89654999999999 南界纬度: 27.9963 西界经度: 116.6883 北界纬度: 28.5269 东界经
【内容概要】 基于 Vite 6 与 TypeScript 5 严格模式构建的企业级前端工程化脚手架模板,开箱集成代码规范、单元测试、持续集成与容器化部署的完整链路。模板将 ESLint 9 扁平化配置、typescript-eslint 类型感知规则、Prettier 3 格式化、Vitest 2 单元测试(含 V8 覆盖率 80% 阈值)、Husky v9 + lint-staged 提交前钩子,以及 GitHub Actions 多版本 Node 矩阵流水线打通到位,另附多阶段 Dockerfile 与 nginx 静态托管配置,可在本地 pnpm install 或 docker compose up 直接启动。源码层面提供分级日志器 Logger、强类型事件总线 EventBus(基于 mitt)、Rust 风格 Result 类型、数字与字节时长格式化工具、可复用 Counter 组件等示例,并配套 32 个 Vitest 用例,演示如何在严格类型约束下编写可测试、可维护的工程化代码。 【适合人群】 1. 准备搭建中大型前端项目,需要份可直接落地的工程化基线模板的全栈工程师; 2. 希望系统理解 Vite 构建配置、ESLint 9 扁平配置、Vitest 覆盖率门槛与 GitHub Actions 流水线如何串联的中级前端开发者; 3. 在团队中负责制定前端规范、CI 流程与 Docker 部署方案的技术负责人; 4. 学习 TypeScript 严格模式下编写类型安全工具库、组件、事件系统的实战示范的学习者。 【能学到什么】 1. Vite 6 + TypeScript 5 严格模式(strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes)下的工程结构组织方式; 2. ESLint 9 Fl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值