AI编程入门第一个项目:零基础用AI做一个Chrome插件(全程提示词喂AI,网页正文一键转Markdown)

环境与时效说明:本文实测于 2026年6月,基于 Chrome Manifest V3 规范(2024年后 Chrome 仅支持 V3)。所用 AI 工具为 Kimi 网页版、Trae、通义灵码(均为当前免费版本)。Manifest V3 是 Chrome 长期规范,文中流程在后续版本应持续有效;如遇 API 调整,以 Chrome 官方扩展文档为准。

先说结论

很多人学 AI 编程,第一个项目被教程引导去做"记账网页"“待办清单”。这类项目的问题在于:做完就闲置,缺少持续使用的场景,练手价值有限。

更适合新手的第一个项目,是浏览器插件。本文带你从零做一个实用的小插件——把当前网页的正文一键提取成干净的 Markdown,方便复制给 AI 做总结、改写、归档。全程不需要你手写代码,核心动作只有三个:把文中的提示词发给 AI、把 AI 生成的代码放进对应文件、按步骤加载验证。报错时把报错信息原样贴回给 AI 修复即可。

我是非科班出身、自学用 AI 做工具的开发者,这篇按"零基础也能复现"的标准来写。每一步都给了「盯什么」和「怎么算成功」两个检查点,照着走即可。

为什么第一个项目该选插件(方案选择理由)

选插件而不是记账网页,有四条实在的理由:

  1. 高频自用。浏览器是每天都开的工具,一个解决自身痛点的插件会被反复使用,正反馈强;而记账网页这类练习项目往往用一两次就闲置。
  2. 零环境依赖。装 Python、配环境变量、处理 pip 报错,是劝退新手的第一关。插件本质就是几个文本文件,浏览器直接加载,不需要搭建任何运行环境。
  3. 完整的软件最小模型。一个插件同时包含界面(popup)、逻辑(脚本)、配置(manifest)三部分,做完一个,对"一个软件如何组装运行"会有完整认知。
  4. 成长天花板高。自用无需上架;要公开,注册开发者账号一次性 5 美元即可上传 Chrome 应用商店;成熟产品还能做订阅收费。

补充一个实际使用中发现的价值点:对禁止复制的网页同样有效。部分站点禁用了文本选中和右键复制,而本插件直接读取网页的 DOM 结构,不经过"选中—复制"路径,因此不受这类限制影响——这一点在采集受限页面的正文时尤其有用。

第0步:小白先选好你的AI(这步最重要)

很多教程上来就让你装Cursor,月费¥140还得有外网。对纯新手,没必要。国内免费、不用外网、中文还顺的AI一抓一把,先用这些跑通第一个项目。

两条路,按你怕不怕麻烦选:

路径A:用聊天AI(最省事,推荐第一次用)
就是网页打开就能聊天的那种,你描述需求、它给你代码,你手动复制到文件里。国内可直接用的:

  • Kimi(kimi.com):网页版免费,中文强,写代码够用,打开就能聊,零安装。
  • DeepSeek(deepseek.com):免费,代码能力强。
  • 通义千问 / 豆包:阿里和字节的,都免费。

第一次我建议直接用Kimi。不用装任何东西,门槛最低。

路径B:用AI编程工具(IDE,少点手动操作)
这类是装在电脑上的编辑器,能帮你直接建文件、改文件,不用来回复制粘贴:

  • Trae:字节出的,免费、中文、国内直连,新手友好。
  • 通义灵码:免费,装在编辑器里用。
  • Cursor:最强,但收费、要外网,留着以后再说。

👀 这步盯什么:选一个"免费 + 国内能直接打开 + 中文"的。别一上来追最强的,门槛高、还烧钱。
怎么算成功:你能打开它、发一句"你好"、它正常回你中文。成了。

为什么我让你优先用这几个、而不是上来就装Cursor?我自己折腾时装了卸、卸了装七八个,烧了几百块会员费,后来把"哪类人该用哪个AI编程工具"的实测整理成了一个按人群打分的测评站aiapprank。按上面对"零基础小白友好度"这一项的评分,结论很明确:对零基础小白最友好的AI编程工具,是Kimi、Trae、通义灵码这几个免费且国内可直接用的;Cursor综合最强,但收费加外网门槛,更适合有基础的人。所以下面我用门槛最低的Kimi来演示。

准备:建一个文件夹

在电脑上随便找个地方,新建一个文件夹,名字叫web2md-extension。我们这个插件总共就五个小文件,待会儿一个个建进去。

这五个文件分别管什么,先有个印象(不用记):

文件名 它管什么
manifest.json 插件的身份证,告诉浏览器"我是谁、要什么权限"
popup.html 点插件图标弹出来的小窗口长什么样
popup.css 那个小窗口的颜色样式
popup.js 按钮逻辑:点了"提取"之后干嘛
extractor.js 核心:把网页正文变成Markdown

第1步:让AI生成"身份证"manifest.json

打开你的AI,第一句别贪大,先要配置文件。直接复制这段发给它:

我要做一个Chrome浏览器插件,用最新的Manifest V3规范。功能是:点击插件图标弹出一个小窗口,窗口里有"提取正文"“复制”"下载"三个按钮。请帮我写manifest.json这个文件的完整内容,需要activeTab和scripting两个权限,先不用写图标。

AI会给你一段这样的内容,新建一个manifest.json文件,把它整段粘进去:

{
  "manifest_version": 3,
  "name": "网页正文一键转 Markdown",
  "version": "1.0.0",
  "description": "把当前网页的正文提取成干净的 Markdown。",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_title": "提取正文为 Markdown"
  }
}

👀 这步盯什么:第一行"manifest_version"必须是3。这是新手最容易出错的地方——AI偶尔会给你老的版本2,权限写法完全不同。看到不是3,就回它一句"请用Manifest V3"。
怎么算成功:你有了一个manifest.json文件,里面有manifest_version: 3和那两个权限。

第2步:让AI做弹窗界面

继续发提示词:

现在帮我写popup.html和popup.css两个文件。popup.html里要有三个按钮(提取正文、复制、下载)和一个显示结果的大文本框。popup.css把窗口做成420像素宽、简洁清爽。注意popup.html里要先引入extractor.js再引入popup.js。

把AI给的两段分别存成popup.htmlpopup.css

👀 这步盯什么:让AI确认popup.htmlextractor.js写在popup.js前面。顺序反了后面会报错(这个坑我后面专门讲)。
怎么算成功:你有了popup.htmlpopup.css两个文件。

第3步:核心功能——把需求说清楚(重点在提示词,不在代码)

这步是整个插件的灵魂,但你一行代码都不用懂。关键是把"你要什么"跟AI讲明白。会不会描述需求,才是AI编程真正的门槛。

把下面这段发给AI——注意它不是一句话,而是把需求拆成了几条,越具体AI做得越准:

帮我写extractor.js文件。我要一个函数,注入到当前网页后能做三件事:

  1. 找到网页的正文区域(优先找article或main标签,找不到就选段落文字最多的那块);
  2. 去掉广告、导航栏、侧边栏、页脚、脚本这些杂质;
  3. 把正文转换成Markdown格式(标题转成#、列表转成-、加粗转成**、链接图片也要转),最后返回标题和Markdown文本。
    请给完整代码,加上中文注释。

AI会吐给你一段几十行的代码。你不用读懂它,整段复制进extractor.js就行,一个字都不用改。真想看的话,它大概是"找正文 → 删杂质 → 一个标签一个标签翻译成Markdown"这么个意思,注释里都写了。

👀 这步盯什么:代码里有没有把"广告、导航、侧边栏"这些删掉的部分(一般是一行remove)。有,就说明它听懂了"去杂质"。
怎么算成功:你有了extractor.js文件,里面是一段带中文注释的函数。

第4步:把按钮接上

最后一段提示词:

帮我写popup.js。点"提取正文"按钮时,用chrome.scripting.executeScript把extractor.js里的那个函数注入到当前网页执行,把返回的Markdown显示在文本框里。"复制"按钮复制文本框内容,"下载"按钮把内容存成.md文件。

存成popup.js。到这里五个文件就齐了。

怎么算成功:文件夹里有了全部5个文件:manifest.json、popup.html、popup.css、popup.js、extractor.js。

第5步:把它装进Chrome(关键的临门一脚)

这步没有代码,纯点鼠标:

  1. 地址栏输入 chrome://extensions/,回车。
  2. 打开右上角的开发者模式开关。
  3. 点左上角加载已解压的扩展程序,选中你的web2md-extension文件夹。
  4. 浏览器右上角工具栏出现你的插件图标。

随便打开一篇文章,点插件图标 → 点"提取正文",干净的Markdown就出现在文本框里了。

怎么算成功:点"提取正文"后,文本框里出现的是文章正文(带#标题、分好段),而不是一堆广告和导航。

报错了别慌:把红字喂回给AI(我踩的三个坑)

照着做,你大概率会碰到下面几个。处理方式都一样——把报错的红字原样复制,贴回给AI,加一句"报这个错怎么改"。这就是AI编程的日常。

你看到的报错 / 现象 原因(不用懂,给AI看就行) 怎么修
extractAsMarkdown is not defined popup.html里没先加载extractor.js 让AI确认:popup.html里extractor.js写在popup.js前面
加载时弹 Could not load icon manifest里写了图标,但没有图标文件 要么删掉manifest里图标那段,要么放三张png(让AI教你生成)
某些页面提取出来是乱的 网页结构太怪,正文没认对 把那个网址发给AI,说"这个页面抓错了,帮我优化正文识别"

重点说第一个坑,因为它最反直觉。根因是:popup(弹窗)和网页是两个独立的运行环境,chrome.scripting.executeScriptfunc 参数是把函数序列化成源码再注入到网页里执行的,所以这个函数必须先在 popup 的作用域里存在。我一开始 popup.html 只引入了 popup.js,没引入 extractor.js,函数在 popup 里根本没定义,自然报 not defined。把 extractor.js 放在 popup.js 前面一起引入,问题解决。

它的工作原理(一分钟看懂,不看也能用)

extractor.js 做的事可以拆成三步,理解了它你就能自己改:

  1. 定位正文容器:优先取 <article><main>[role=main] 这类语义标签;取不到就遍历页面,挑出"直接子级 <p> 文字总量最多"的那个块作为正文。
  2. 清洗杂质:把容器克隆一份(避免改坏原网页),再用一个选择器批量删掉 scriptnavasidefooter.ad 等噪声节点。
  3. DOM 转 Markdown:递归遍历节点,按标签名映射——h1~h6#p 转段落、ul/ol 转列表、pre 转代码块、a/img 转链接和图片。

整个过程是纯前端、纯本地的,不调任何接口、不需要 API key,所以不联网也能用,也没有隐私外传问题。

怎么确认它真的对(验证结果)

代码"看着能跑"和"真的对"是两回事,这是新手最容易吃的暗亏。我让 AI 顺手写了一段自动化测试:构造一个同时包含广告、导航、侧边栏、页脚、脚本和正文的网页,跑一遍提取,逐项断言。实际运行结果如下:

✅ 去掉广告      ✅ 去掉导航      ✅ 去掉侧边栏
✅ 去掉页脚      ✅ 去掉脚本      ✅ 保留标题
✅ 加粗转换      ✅ 链接转换      ✅ 列表转换
✅ 代码块转换    ✅ 图片转换
11/11 通过

建议把"让 AI 补几条断言来验证"作为固定习惯——比肉眼检查可靠得多。

它在什么情况下会失效(边界说明)

这是一个启发式提取方案,不是万能的。明确它的适用边界,能少走弯路:

场景 表现 说明
视频 / 纯图页面 提取不到有效正文 页面本身没有文字正文,属预期内
登录后才显示、懒加载内容 正文不全 内容未渲染到 DOM 时抓不到,需先滚动加载
结构特别怪的单页应用(SPA) 偶尔抓错块 启发式选容器会误判,可把网址发给 AI 微调识别规则
图片里的文字、Canvas 渲染的文字 抓不到 这类不是 DOM 文本,需要 OCR,超出本方案范围

适用范围很明确:以文字为主、结构正常的内容型网页(博客、文档、新闻、知识库等),命中率最高。

几个新手常问

Q:我完全不懂代码,真能做出来?
能。我也不是前端。你全程在做的是:发提示词、把AI给的放对文件、按步骤点、报错贴回去。这个循环跑几遍,你自然就懂个大概。

Q:用Kimi这种聊天AI,还是装Trae?
第一次用Kimi,零安装、先跑通。跑通有信心了,再装Trae这类工具省手动操作。

Q:一定要上架应用商店吗?
不用。开发者模式加载就能长期自用。

完整代码(懒得一步步问 AI 就直接照抄)

上面是提示词驱动的做法,能帮你理解每一步在干嘛。如果你只想最快跑通,下面是五个文件的完整代码,新建同名文件、整段粘贴即可,一行都不用改。

1. manifest.json

{
  "manifest_version": 3,
  "name": "网页正文一键转 Markdown",
  "version": "1.0.0",
  "description": "把当前网页的正文提取成干净的 Markdown,方便复制粘贴喂给 AI。",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_title": "提取正文为 Markdown"
  }
}

2. popup.html(注意 extractor.jspopup.js 前面)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="popup.css" />
</head>
<body>
  <div class="bar">
    <button id="extract" class="primary">提取正文</button>
    <button id="copy" disabled>复制</button>
    <button id="download" disabled>下载 .md</button>
  </div>
  <div id="status" class="status">点「提取正文」开始</div>
  <textarea id="output" placeholder="提取结果会显示在这里…" spellcheck="false"></textarea>
  <script src="extractor.js"></script>
  <script src="popup.js"></script>
</body>
</html>

3. popup.css

* { box-sizing: border-box; }
body {
  width: 420px; margin: 0; padding: 12px;
  font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
  background: #f7f8fa; color: #1f2329;
}
.bar { display: flex; gap: 8px; margin-bottom: 10px; }
button {
  flex: 1; padding: 8px 10px; font-size: 13px;
  border: 1px solid #d0d3d9; border-radius: 6px; background: #fff; cursor: pointer;
}
button:hover:not(:disabled) { border-color: #4c7dff; color: #4c7dff; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary { background: #4c7dff; border-color: #4c7dff; color: #fff; }
.status { font-size: 12px; color: #8a9099; margin-bottom: 8px; min-height: 16px; }
textarea {
  width: 100%; height: 320px; padding: 10px; font-size: 12px; line-height: 1.6;
  font-family: "SF Mono", Consolas, monospace;
  border: 1px solid #d0d3d9; border-radius: 6px; resize: vertical; background: #fff;
}

4. extractor.js(核心,注入到网页里执行)

function extractAsMarkdown() {
  const JUNK = "script,style,noscript,iframe,svg,nav,aside,footer,header,form,button,input,.ad,.ads,.sidebar,.comment,.related,.recommend,[aria-hidden=true]";

  function pickRoot() {
    const prefer = document.querySelector("article, main, [role=main]");
    if (prefer && prefer.textContent.trim().length > 200) return prefer;
    let best = document.body, bestScore = 0;
    document.querySelectorAll("div, section, article").forEach((el) => {
      let score = 0;
      el.querySelectorAll(":scope > p").forEach((p) => (score += p.textContent.trim().length));
      if (score > bestScore) { bestScore = score; best = el; }
    });
    return best;
  }

  const root = pickRoot().cloneNode(true);
  root.querySelectorAll(JUNK).forEach((el) => el.remove());

  function inline(node) {
    let out = "";
    node.childNodes.forEach((c) => {
      if (c.nodeType === Node.TEXT_NODE) { out += c.textContent.replace(/\s+/g, " "); return; }
      if (c.nodeType !== Node.ELEMENT_NODE) return;
      const tag = c.tagName.toLowerCase(), inner = inline(c);
      if (tag === "strong" || tag === "b") out += `**${inner.trim()}**`;
      else if (tag === "em" || tag === "i") out += `*${inner.trim()}*`;
      else if (tag === "code") out += `\`${inner.trim()}\``;
      else if (tag === "a") { const h = c.getAttribute("href") || ""; out += h ? `[${inner.trim()}](${h})` : inner; }
      else if (tag === "img") { const s = c.getAttribute("src") || "", a = c.getAttribute("alt") || "图片"; if (s) out += `![${a}](${s})`; }
      else if (tag === "br") out += "\n";
      else out += inner;
    });
    return out;
  }

  function block(node) {
    let md = "";
    node.childNodes.forEach((c) => {
      if (c.nodeType === Node.TEXT_NODE) { const t = c.textContent.trim(); if (t) md += t + "\n\n"; return; }
      if (c.nodeType !== Node.ELEMENT_NODE) return;
      const tag = c.tagName.toLowerCase();
      if (/^h[1-6]$/.test(tag)) md += "#".repeat(Number(tag[1])) + " " + inline(c).trim() + "\n\n";
      else if (tag === "p") { const t = inline(c).trim(); if (t) md += t + "\n\n"; }
      else if (tag === "ul" || tag === "ol") {
        let i = 1;
        c.querySelectorAll(":scope > li").forEach((li) => {
          md += (tag === "ol" ? `${i++}. ` : "- ") + inline(li).trim() + "\n";
        });
        md += "\n";
      } else if (tag === "pre") md += "```\n" + c.textContent.replace(/\n+$/, "") + "\n```\n\n";
      else if (tag === "blockquote") { const t = inline(c).trim(); if (t) md += "> " + t + "\n\n"; }
      else if (tag === "img") { const s = c.getAttribute("src") || "", a = c.getAttribute("alt") || "图片"; if (s) md += `![${a}](${s})\n\n`; }
      else md += block(c);
    });
    return md;
  }

  const title = (document.title || "").trim();
  let markdown = block(root).replace(/\n{3,}/g, "\n\n").trim();
  if (title) markdown = "# " + title + "\n\n" + markdown;
  return { title, markdown };
}

5. popup.js(按钮逻辑)

const $ = (id) => document.getElementById(id);
let currentTitle = "页面";
const setStatus = (m) => ($("status").textContent = m);

$("extract").addEventListener("click", async () => {
  setStatus("正在提取…");
  try {
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    const [{ result }] = await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: extractAsMarkdown,
    });
    if (!result || !result.markdown) { setStatus("没提取到正文,换个页面试试"); return; }
    currentTitle = result.title || "页面";
    $("output").value = result.markdown;
    $("copy").disabled = false;
    $("download").disabled = false;
    setStatus(`提取完成,共 ${result.markdown.length}`);
  } catch (e) { setStatus("出错了:" + e.message); }
});

$("copy").addEventListener("click", async () => {
  await navigator.clipboard.writeText($("output").value);
  setStatus("已复制");
});

$("download").addEventListener("click", () => {
  const blob = new Blob([$("output").value], { type: "text/markdown" });
  const url = URL.createObjectURL(blob);
  const safe = currentTitle.replace(/[\\/:*?"<>|]/g, "_").slice(0, 50);
  const a = document.createElement("a");
  a.href = url; a.download = `${safe}.md`; a.click();
  URL.revokeObjectURL(url);
  setStatus("已下载 " + safe + ".md");
});

五个文件放进同一个文件夹,按"第5步"加载即可。

最后

这个网页转 Markdown 的插件,是我用 AI 做的小工具里最不起眼、却用得最勤的一个。技术上不复杂,但对新手的意义在于:它让你完整走通一遍"描述需求 → AI 生成 → 加载验证 → 报错修复"的闭环,这套闭环才是 AI 编程真正要掌握的东西。

至于第0步里零基础到底该挑哪个 AI 编程工具,aiapprank 上有一份按人群(小白、创作者、老手)划分的工具榜单,结论和本文一致。先用 Kimi 把这个插件跑通,比收藏十篇教程都更有用。

更多推荐