ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于 Plasmo 构建 SurfSense 跨浏览器扩展:浏览历史采集插件的开发、构建与发布全指南

基于 Plasmo 构建 SurfSense 跨浏览器扩展:浏览历史采集插件的开发、构建与发布全指南 基于 Plasmo 构建 SurfSense 跨浏览器扩展浏览历史采集插件的开发、构建与发布全指南【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSenseSurfSense 浏览器扩展surfsense_browser_extension是一个基于 Plasmo 框架构建的跨浏览器扩展其核心职责是为 SurfSense 平台采集用户的浏览历史把网页正文转化为结构化文档并回传后端供后续检索、问答与研究使用。本文以该扩展的官方 README 为主线结合仓库内真实的源码实现完整讲解从环境准备、本地开发、源码剖析到生产构建、商店提交的端到端流程读完后你将能够独立复现该扩展的开发环境理解其“标签页监听 → HTML 抓取 → Markdown 转换 → 队列缓存 → 批量上传”的完整链路并掌握 Plasmo 扩展的构建与发布套路。扩展的定位为 SurfSense 收集可检索的浏览记忆从 package.json 的描述可以看出该扩展的定位是 Extension to collect Browsing History for SurfSense。它并非普通意义上的“历史记录查看器”而是 SurfSense 平台的数据采集入口用户在浏览器中访问网页时扩展在后台记录访问过的 URL、标题、停留时长、来源页等元信息并把渲染后的页面 HTML 转换成 Markdown最终以上传文档的方式写入 SurfSense 后端成为个人知识库中可被检索的内容。后端侧也有与之对应的专门类型在 surfsense_backend/app/db.py 中定义了EXTENSION EXTENSION这一文档类型documents_routes.py 中的POST /api/v1/documents接口在收到document_type EXTENSION的请求时会把每一条浏览记录派发给 Celery 任务process_extension_document_task定义于 document_tasks.py做后台处理。也就是说扩展负责“采”后端负责“存与加工”两者通过一个 JSON API 契约衔接。技术栈与环境准备该扩展是一个标准的 Plasmo 项目通过plasmo init初始化使用 Manifest V3 规范并引入了大量现代前端工具链类别技术/依赖说明扩展框架plasmo0.90.5负责 manifest 生成、多浏览器/多版本构建构建工具pnpm≥8.0.0仓库使用 pnpm 工作区见 pnpm-workspace.yaml运行时Node.js18.0.0 23.0.0见 package.json 的 engines 约束UI 框架React 18.2 Tailwind CSS 3.4popup 界面由 React 渲染通信plasmohq/messaging、plasmohq/storage页面与 background 消息通信、本地存储HTML→Markdowndom-to-semantic-markdownlinkedom在 background/service worker 中做语义化 HTML 转换组件库Radix UIdialog/popover/toast 等popup 内的交互组件环境准备仓库根目录与扩展目录均使用 pnpm 作为包管理器建议先全局安装 pnpm版本不低于 8并确保本机 Node.js 处于18.0.0 23.0.0区间当前 VERSION 为0.0.35的扩展在 Node 20 上开发验证最为稳妥。启动本地开发服务器进入扩展目录后运行开发服务器pnpm dev # 或使用 npm npm run devpnpm dev对应 package.json 中的plasmo dev脚本。Plasmo 会启动一个监听进程实时监听源码变更并重新构建。随后打开浏览器加载对应的开发构建产物。例如在 ChromeManifest V3下加载build/chrome-mv3-dev目录即可打开chrome://extensions开启右上角“开发者模式”点击“加载已解压的扩展程序”选择扩展根目录下的build/chrome-mv3-dev文件夹。加载完成后浏览器工具栏会出现 SurfSense 扩展图标。由于开发构建是未打包的你可以在 popup 打开时直接编辑源码页面会自动热更新无需反复手动重载。理解扩展目录结构与核心入口Plasmo 的约定是“文件即路由”根目录下的入口文件会按约定被编译为对应的扩展组件。以本仓库为例完整结构见 surfsense_browser_extension文件作用popup.tsxpopup 入口渲染Routing /与全局Toaster /整体包裹在MemoryRouter中用 React Router 管理/主页与/login登录两个页面content.tscontent script 配置matches: [all_urls]、all_frames: true、world: MAIN即在所有页面以 MAIN world 注入background/index.ts后台 service worker监听标签页生命周期事件routes/index.tsxpopup 内的路由表utils/commons.ts采集核心工具函数队列初始化、渲染 HTML 抓取、LangChain 文档转换utils/backend-url.ts后端地址解析与自定义后端配置background/messages/Plasmo 消息处理器savedata、savesnapshot关于 content script 需要注意当前 content.ts 只导出了PlasmoCSConfig配置all_frames与MAINworld并未注入具体的 DOM 操作逻辑。实际的内容采集是通过chrome.scripting.executeScript在后台动态注入函数完成的详见下一节这样的设计使得采集逻辑集中在 background 中便于统一管理。源码级解析浏览历史采集链路这一节对应 README 中“Getting Started”隐含的扩展能力也是该扩展最核心的实现部分。整个采集链路分布在三个文件里1. 标签页生命周期监听background/index.tsbackground/index.ts 注册了三个chrome.tabs事件onCreated标签页新建时调用initWebHistory(tab.id)和initQueues(tab.id)为该标签页初始化历史会话与 URL/时间队列onUpdated当页面加载完成changeInfo.status complete且存在 URL 时先初始化会话与队列再通过chrome.scripting.executeScript注入getRenderedHtml抓取当前页面的渲染 HTML、标题、URL 与进入时间随后把url与entryTime分别压入该标签页对应的urlQueueList与timeQueueListonRemoved标签页关闭时从urlQueueList与timeQueueList中剔除对应tabsessionId的会话避免残留脏数据。2. 采集与队列工具函数utils/commons.tsutils/commons.ts 定义了数据链路的关键函数getRenderedHtml()在页面上下文执行的函数返回{ url, entryTime, title, renderedHtml }其中renderedHtml是document.documentElement.outerHTML——注意它抓取的是渲染后的 DOM而非原始 HTML 源码因此能够捕获 JS 动态渲染出的内容initQueues(tabId)以tabsessionId为键维护urlQueueList与timeQueueList两个队列队列不存在时创建已存在但缺少当前标签页时追加initWebHistory(tabId)初始化webhistory结构为每个标签页维护一个tabHistory数组toIsoString(date)把时间戳格式化为带时区偏移的 ISO 8601 字符串webhistoryToLangChainDocument(tabId, tabHistory)把每条浏览记录转换成 LangChain 风格的{ metadata, pageContent }文档其中 metadata 包含BrowsingSessionId、VisitedWebPageURL、VisitedWebPageTitle、VisitedWebPageDateWithTimeInISOString、VisitedWebPageReffererURL、VisitedWebPageVisitDurationInMilliseconds六个字段pageContent为网页的 Markdown 正文。对应的数据结构WebHistory定义在 utils/interfaces.ts仅含tabsessionId与tabHistory两个字段。3. 本地存储设计扩展使用plasmohq/storage的local 存储区new Storage({ area: local })保存四类数据webhistory每个标签页的访问历史含正文 MarkdownurlQueueList/timeQueueList用于计算停留时长与来源页的队列token用户的 Personal Access TokenPAT作为后续上传的 Bearer 凭证workspace/workspace_id当前选中的工作区注意 HomePage.tsx 中有一段一次性迁移逻辑会把旧的search_space/search_space_id键复制到新的workspace/workspace_id键上兼容历史版本数据。package.json 声明的权限storage、scripting、unlimitedStorage、activeTab以及host_permissions: [all_urls]正是为这套采集链路服务的storage用于持久化队列scripting用于向页面注入抓取函数unlimitedStorage用于规避大容量历史数据的配额限制all_urls允许在所有站点上采集。从 popup 到后端的保存流程扩展 popup 的主页 HomePage.tsx 提供三个核心操作Save Current Page保存当前页调用saveCurrSnapShot()对当前活动标签页执行executeScript抓取渲染 HTML用dom-to-semantic-markdown的convertHtmlToMarkdown启用extractMainContent: true、enableTableColumnTracking: true转换为 Markdown删除原始renderedHtml后依据 URL/时间队列计算出duration停留时长与reffererUrl来源页若队列长度为 1 则为START最后推入tabHistory并写回webhistoryClear Inactive History清理无效历史通过chrome.tabs.query({})拿到所有存活标签页 ID仅保留仍在活动会话中的数据清除已关闭标签页的历史Save to SurfSense批量保存到 SurfSense调用sendToBackground({ name: savedata })触发消息处理器 background/messages/savedata.ts。savedata处理器是批量上传的枢纽其流程为读取webhistory用webhistoryToLangChainDocument把全部浏览记录转换成文档列表随后将本地tabHistory清空先清空、后上传通过clearMemory()只保留活动标签页的会话框架构造请求体{ document_type: EXTENSION, content: [ { metadata: { BrowsingSessionId: 123, VisitedWebPageURL: https://example.com/article, VisitedWebPageTitle: Example Article, VisitedWebPageDateWithTimeInISOString: 2026-09-14T03:01:4800:00, VisitedWebPageReffererURL: https://example.com, VisitedWebPageVisitDurationInMilliseconds: 42000 }, pageContent: # 网页的 Markdown 正文... } ], workspace_id: 1 }从 storage 读取token与workspace_id以Authorization: Bearer token请求POST /api/v1/documentsURL 由buildBackendUrl拼接见下节收到响应后清空本地已上传的历史并向 popup 返回Save Job Started。对应的后端处理在 documents_routes.pyPOST /api/v1/documents会先校验DOCUMENTS_CREATE权限若document_type EXTENSION则遍历content把每个文档保留上述六个 metadata 字段与pageContent作为 dict 通过process_extension_document_task.delay(...)投递给 Celery 异步处理接口立即返回{message: Documents queued for background processing, status: queued}。任务侧document_tasks.py会用 Pydantic 模型DocumentMetadata重建对象然后写入数据库并进入后续的索引/检索流水线。savesnapshot处理器background/messages/savesnapshot.ts则用于单页快照它查询当前活动标签页抓取渲染 HTML 后立即在 background 侧完成convertHtmlToMarkdown转换使用linkedom的DOMParser覆盖默认解析器并在开头手动补齐 Node 常量以兼容 Service Worker 环境计算停留时长与来源页随后以同样的请求体格式上传POST /api/v1/documents返回Snapshot Saved Successfully。该 handler 与 popup 内的saveCurrSnapShot分别覆盖“后台快速保存”与“带 UI 反馈的保存”两种场景。连接配置与认证扩展与后端的连接由 utils/backend-url.ts 统一管理默认后端地址取process.env.PLASMO_PUBLIC_BACKEND_URL未设置时回退到https://www.surfsense.comFALLBACK_BACKEND_BASE_URL用户可在连接设置中自定义后端地址存储在 local 区域的backend_base_url键中getBackendBaseUrl()优先返回自定义地址否则返回默认地址buildBackendUrl(path)负责拼接出完整请求 URL并保证 path 以/开头、base URL 去除末尾斜杠normalizeBackendBaseUrl。认证方式是Personal Access TokenPAT登录页 routes/pages/ApiKeyForm.tsx 让用户输入 PAT前端先请求GET /verify-token携带Authorization: Bearer token验证有效性验证通过后把 token 写入 storage 的token键并跳转主页后续所有上传请求都携带该 token 作为 Bearer 凭证。主页加载时会先用 token 请求GET /api/v1/workspaces拉取工作区列表token 失效则自动清除本地凭证并跳回登录页。生产构建本地开发验证完成后执行生产构建pnpm build # 或 npm run buildpnpm build对应plasmo build。这会为扩展生成一份生产级 bundle区别于开发构建的chrome-mv3-dev生产产物在build目录下按浏览器/版本区分如chrome-mv3-prod产物已做压缩与优化可直接打包 zip 后提交到各浏览器应用商店。构建前请确认 package.json 中 manifest 字段扩展名SurfSense、描述、版本号等已按发布需求调整。提交到浏览器应用商店Plasmo 官方提供的最简部署方式是使用 bpp 平台内置的 GitHub Action 工作流把“构建 上传 发布”自动化。官方推荐的流程分两步首版手动上架先用pnpm build生成生产产物并打包手动到各商店Chrome Web Store、Edge Add-ons、Firefox Add-ons 等创建开发者账号、完成首版上传以建立基本的商店凭据配置自动化提交在仓库中接入 bpp 的 GitHub Action并按其提交工作流对应 README 引用的 Plasmo Submit 文档配置商店凭据如CLIENT_ID、CLIENT_SECRET、REFRESH_TOKEN、EXTENSION_ID等此后每次发布只需打 tag 或手动触发 workflow即可自动完成构建、压缩与多商店提交。开发提示与注意事项结合源码整理几个实战中容易踩坑的点开发目录与生产目录不同开发加载的是build/chrome-mv3-dev生产发布的是build下的 prod 产物二者不要混淆不同浏览器Chrome/Edge/Firefox/Safari对应不同的 mv3 产物目录。Node 版本约束package.json 明确要求 Node18.0.0 23.0.0使用过新或过旧的 Node 可能导致plasmo构建异常。Service Worker 环境差异background 是有限生命周期的 Service Workersavesnapshot.ts 中手动补齐globalThis.Node常量并用linkedom替换 DOM 解析器正是为了在无 DOM 的 worker 环境中跑通dom-to-semantic-markdown若自行扩展解析逻辑需同样注意环境兼容。MAIN world 注入content script 配置为world: MAIN会与页面共享 DOM 上下文实际抓取统一走chrome.scripting.executeScript不要同时混用两种注入方式以免产生冲突。数据持久化语义webhistory在savedata成功后会被清空避免重复上传且clearMemory只保留活动标签页的会话因此关闭标签页后再触发保存该标签页的未上传历史可能已随onRemoved清理流程丢失——批量保存应尽量在标签页仍打开时进行。小结SurfSense 浏览器扩展是一个典型的“数据采集型” Plasmo 扩展开发阶段用pnpm dev热更新调试生产阶段用pnpm build产出多浏览器 bundle发布阶段借助 bpp 的 GitHub Action 自动化上架。其价值并不局限于扩展本身而是与后端EXTENSION文档类型、POST /api/v1/documents接口和 Celery 异步加工任务共同构成了 SurfSense“研究开放网络”能力的个人数据入口——每一次浏览、每一个停留都通过这条链路沉淀为可检索的知识资产。理解这一架构后无论是二次开发采集逻辑、接入自定义后端还是为其他平台复用这套“HTML → Markdown → 文档上传”管线都有了清晰的实现范式。【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表