Files
store_ai_extension/s.md
2026-05-13 11:22:10 +08:00

390 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dianshan 插件爬取任务开发文档(逻辑/通信/状态)
> 目的:把“点击爬取后发生了什么、会触发哪些方法、给谁通信、改变哪些状态”说清楚,方便后续二开与排查。
>
> 范围:`dianshan/` 目录内的扩展popup + background + content/overlay + 外部网页桥接)。
>
> 注意:当前项目代码里没有 `startDianshanCrawl()` / `getDianshanCrawlState()` 这类方法名;
> 正确入口分别是 `startCrawl()``dianshan/src/background/task/crawlTask.ts:14`)与 `getCrawlTaskState()``dianshan/src/background/task/taskState.ts:10`),以及对应的消息 action`START_CRAWL` / `GET_CRAWL_STATE`)。
---
## 1. 关键文件索引(按职责)
### 1.1 Popup扩展弹窗 UI
- `dianshan/src/popup/App.vue`
- UI 展示:平台选择、开始/取消/继续按钮、步骤列表、底部语言切换/版本号
- `dianshan/src/popup/hook/use-scan.ts`
- 与 background 通信(开始/取消/继续/读取状态)
- 监听 `chrome.storage.onChanged` 同步任务状态
- 计时器:根据 `startedAt` 计算已用时
- `dianshan/src/popup/hook/use-i18n.ts`
- Popup 文案 i18n中文/英文),持久化到 `chrome.storage.local`
### 1.2 Background后台调度 + 任务状态机)
- `dianshan/src/background/index.ts`
- 统一接收 popup/content/external 的 message`chrome.runtime.onMessage`
- 监听窗口/Tab 关闭(自动暂停任务)
- 监听 storage 变化并对外广播external bridge
- `dianshan/src/background/task/crawlTask.ts`
- 任务生命周期start/cancel/pause/resume/dismiss
- 步骤执行器:按 steps 顺序打开页面、等待、抓取、处理中断/重试
- 完成后收尾:发送结果 -> 清空记录 -> 关闭窗口
- `dianshan/src/background/task/taskState.ts`
- 读写 `chrome.storage.local``crawlTaskState`
- 写入时同时 `sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state)` 推送给爬取窗口overlay 用)
- `dianshan/src/background/task/helper.ts`
- `openSingleTabWindow()` 创建 popup 类型新窗口并返回 `{windowId, tabId}`
- `waitForTabLoaded()` 等待 tab load complete
- `scrapeStepInContent()` 与 content script 通信执行抓取(`tabs.sendMessage`
- `dianshan/src/background/service/externalBridge.ts`
- 与外部网页(官网/业务系统)进行 external message / long-connect 通信
- 监听 storage 变化,广播 `DIANSHAN_CRAWL_STATE / DONE / ...` 等事件
### 1.3 Content Script爬取窗口页内逻辑
- `dianshan/src/content/pageRunner.ts`
- 接收 background 指令:
- `CHECK_INTERRUPT`:判断登录/验证码/404/未就绪等“需要人工处理”的中断
- `SCRAPE_STEP`:等待关键 selector 稳定后执行 DOM 抓取
- `dianshan/src/content/crawlOverlay.ts`
- 爬取窗口左下角悬浮 UI圆形计时菜单
- 点击圆形菜单展开后展示“与 popup 类似”的步骤进度/暂停提示/继续/取消
- 接收 background 推送的 `CRAWL_STATE_UPDATE` 状态
- 初始化时会拉取一次 `GET_CRAWL_STATE_FOR_TAB` 快照(只在爬取 tab 返回状态)
### 1.4 Shared跨模块协议
- `dianshan/src/shared/message.ts`
- popup/content -> background 的消息 action 类型(例如 `START_CRAWL`
- `dianshan/src/shared/tab.ts`
- background -> content爬取窗口 tab消息 action 类型(例如 `CRAWL_STATE_UPDATE`
- `dianshan/src/types/crawl.ts`
- `CrawlTaskState` / `CrawlProgressStep` / `CrawlPauseInfo` 等任务数据结构
### 1.5 关键方法定位(按真实代码)
> 下面列的都是当前项目里“确实存在”的方法/入口,后面章节会反复引用;每条都附带文件地址+行号,方便你 Ctrl+P 直达。
- popup 启动爬取:`handleScan()``dianshan/src/popup/hook/use-scan.ts:46`
- popup 取消爬取:`handleCancelCrawl()``dianshan/src/popup/hook/use-scan.ts:70`
- popup 继续爬取:`handleResumeCrawl()``dianshan/src/popup/hook/use-scan.ts:83`
- popup 同步状态:`handleStorageChanged()``dianshan/src/popup/hook/use-scan.ts:146`
- background 消息总入口:`chrome.runtime.onMessage.addListener(...)``dianshan/src/background/index.ts:22`
- background 路由:`case "START_CRAWL"` 等:`dianshan/src/background/index.ts:34`
- background 窗口/Tab 关闭监听:`windows.onRemoved`/`tabs.onRemoved``dianshan/src/background/index.ts:92`
- 任务启动:`startCrawl()``dianshan/src/background/task/crawlTask.ts:14`
- 任务执行器:`runCrawlSteps()``dianshan/src/background/task/crawlTask.ts:63`
- 完成收尾:`finalizeCompletedTask()``dianshan/src/background/task/crawlTask.ts:312`
- 取消任务:`cancelCrawl()``dianshan/src/background/task/crawlTask.ts:131`
- 窗口关闭自动暂停:`pauseCrawlOnWindowRemoved()``dianshan/src/background/task/crawlTask.ts:158`
- Tab 关闭自动暂停:`pauseCrawlOnTabRemoved()``dianshan/src/background/task/crawlTask.ts:188`
- 继续/恢复:`resumeCrawl()``dianshan/src/background/task/crawlTask.ts:219`
- 状态读写:`getCrawlTaskState()`/`setCrawlTaskState()``dianshan/src/background/task/taskState.ts:10` / `dianshan/src/background/task/taskState.ts:20`
- 新开爬取窗口:`openSingleTabWindow()``dianshan/src/background/task/helper.ts:8`
- 等待页面加载:`waitForTabLoaded()``dianshan/src/background/task/helper.ts:45`
- 让 content 抓取:`scrapeStepInContent()``dianshan/src/background/task/helper.ts:90`
- content 执行器入口:`setupPageRunner()``dianshan/src/content/pageRunner.ts:28`
- content 抓取处理:`handlePageRunnerMessage()``dianshan/src/content/pageRunner.ts:38`
- 爬取窗口悬浮 UI`mountCrawlOverlay()``dianshan/src/content/crawlOverlay.ts:36`
- 悬浮 UI 初始化拉取快照:`GET_CRAWL_STATE_FOR_TAB``dianshan/src/content/crawlOverlay.ts:67`
- 悬浮 UI 实时状态推送:`CRAWL_STATE_UPDATE``dianshan/src/content/crawlOverlay.ts:54`
---
## 2. 核心数据结构CrawlTaskState状态机
存储位置:`chrome.storage.local['crawlTaskState']`
关键字段(简化说明):
- `id`:任务唯一 ID`platformId-startedAt`
- `platformId/platformName`:平台信息
- `windowId/tabId`:当前爬取窗口与承载爬取的 taboverlay 只在这个 tab 渲染)
- `startedAt`:任务开始时间戳(用于 popup/overlay 计时)
- `status``running | paused | completed | failed | canceled`
- `pause`:当 `status='paused'` 时存在,包含
- `reason``reauth | shield | not_found | page_not_ready | window_closed`
- `message`:给用户看的暂停提示
- `currentStepIndex`:当前执行到的步骤索引
- `steps[]`:每个步骤的进度条记录
- `uniqueKey/name`
- `status``pending | running | success | failed`
- `result/message`:抓取结果或失败原因
状态同步策略:
- background 每次 `setCrawlTaskState()` / `updateCrawlTaskState()` 都会写入 storage
- popup监听 `chrome.storage.onChanged`,永远以 storage 为准渲染 UI
- overlay通过 `sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state)` 实时更新(并可在初始化时请求快照)
- 外部网页:`chrome.storage.onChanged` -> `externalBridge.broadcastCrawlStorageChange()` 广播结果/状态
---
## 3. “点击爬取”后的完整时序(从 UI 到抓取)
以下用“触发方法 / 通信对象 / 状态变化”描述每一步。
### 3.1 Popup 点击 “Scan now”
1) 触发方法
- popup`use-scan.ts -> handleScan()``dianshan/src/popup/hook/use-scan.ts:46`
2) 通信
- popup -> background`sendBackgroundMessage({ action:'START_CRAWL', payload:{ platformId } })`
3) background 入口
- `background/index.ts``chrome.runtime.onMessage` 收到 `START_CRAWL``dianshan/src/background/index.ts:22` / `dianshan/src/background/index.ts:34`
- 调用:`crawlTask.startCrawl(platformId)``dianshan/src/background/task/crawlTask.ts:14`
4) 状态变化startCrawl
- `openSingleTabWindow(steps[0].url)`创建爬取窗口popup 类型)得到 `{windowId, tabId}``dianshan/src/background/task/helper.ts:8`
- 构建初始 `CrawlTaskState`
- `status='running'`
- `currentStepIndex=0`
- `steps[0].status='running'`,其余 `pending`
- `setCrawlTaskState(nextState)` 写入 storage`dianshan/src/background/task/taskState.ts:20`
- 这一步会触发:
- popup 的 storage 监听更新 UI
- background 同时 `sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state)` 推送给 overlay`dianshan/src/background/task/taskState.ts:20`
- externalBridge 通过 storage.onChanged 对外广播(如果网页有连 port
5) 启动执行器
- background创建 `AbortController`,并异步执行:
- `runCrawlSteps(taskId, tabId, platform.steps, signal, startIndex=0)``dianshan/src/background/task/crawlTask.ts:63`
---
## 4. “执行一个步骤”会发生什么runCrawlSteps 的循环体)
每个 step 的处理流程如下(对每个 i 从 startIndex 到 steps.length-1
### 4.1 进入新 step先更新状态机
1) 触发方法
- background`updateCrawlTaskState(taskId, updater)`
2) 状态变化
- `currentStepIndex = i`
- `steps[i].status = 'running'`(其它步骤保持原样)
3) 通信影响
- 因为 `updateCrawlTaskState()` 内部会 `setCrawlTaskState()`
- storage 写入 -> popup UI 更新
- 同时 `sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state)` -> overlay 更新
- storage.onChanged -> external bridge 广播(如果外部网页连着)
### 4.2 跳转页面并等待加载完成
1) 触发方法
- background`chrome.tabs.update(tabId, { url: step.url, active: true })`
- background`waitForTabLoaded(tabId, signal)`
2) 通信对象
- background -> Chrome tabs API无 content 参与)
3) 状态变化
- 不改变任务状态,只是准备让 content script 进入目标页面上下文
### 4.3 让 content script 执行抓取(核心通信)
1) 触发方法
- background`scrapeStepInContent(tabId, step, signal)`
2) 通信
- background -> content爬取窗口 tab`chrome.tabs.sendMessage(tabId, { action:'SCRAPE_STEP', payload:{ fields, checkSelector } })`
3) content 入口
- `content/pageRunner.ts``chrome.runtime.onMessage` 收到 `SCRAPE_STEP`
- `detectPageInterrupt()`:先判断是否需要人工处理(登录/验证码/404/未就绪)
- `waitForStableSelector(checkSelector, timeout)`:等待关键 DOM 稳定出现
- `processFields(fields, document.body)`:按配置抓取 DOM 数据
4) 返回值约定PageRunnerResponse
- `ok: true, data: DomScrapeResult`:本步骤抓取成功
- `interrupt: CrawlPauseInfo`:需要人工处理(会进入 paused
- `ok: false, error`:未就绪/异常background 会重试
### 4.4 抓取成功:写入结果并标记 step success
1) 触发方法
- background`updateCrawlTaskState(taskId, updater)`
2) 状态变化
- `steps[i].status = 'success'`
- `steps[i].result = res.data`
3) 通信影响
- 同 4.1storage + overlay 推送 + external bridge 广播
### 4.5 抓取中断:进入 paused需要用户处理
中断来源:
- content 检测到 `shield/reauth/not_found/page_not_ready`
- 或者:爬取窗口被用户关掉(见第 6 章)
1) 触发方法(以 content 中断为例)
- background`updateCrawlTaskState(taskId, s => ({ ...s, status:'paused', pause: interrupt }))``dianshan/src/background/task/crawlTask.ts:93`
2) 状态变化
- `status='paused'`
- `pause={reason,message}`
3) 执行器行为
- background 在 `runCrawlSteps` 内部“死等恢复”:
- 循环检查 `getCrawlTaskState()?.status === 'paused'`
- 直到用户点击“继续”把状态改回 `running` 才继续
4) UI 行为
- popup / overlay 都会显示暂停提示 + “Continue”
---
## 5. “点击继续”会发生什么RESUME_CRAWL
触发来源:
- popup 的 Continue
- overlay 的 Continue
### 5.1 popup/overlay 发消息
- popup`use-scan.ts -> handleResumeCrawl() -> sendBackgroundMessage({action:'RESUME_CRAWL'})``dianshan/src/popup/hook/use-scan.ts:83`
- overlay`crawlOverlay.ts -> chrome.runtime.sendMessage({action:'RESUME_CRAWL'})``dianshan/src/content/crawlOverlay.ts:392`
### 5.2 background 处理 resume
入口:
- `background/index.ts` switch case `RESUME_CRAWL``dianshan/src/background/index.ts:66`
- 调用 `crawlTask.resumeCrawl()``dianshan/src/background/task/crawlTask.ts:219`
resumeCrawl 分两类:
#### A) 登录/验证码等中断(窗口仍存在)
条件:
- `state.status === 'paused'`
- `state.pause.reason !== 'window_closed'`
- `state.windowId && state.tabId` 还在
行为:
- 仅把状态改回 `running`(清掉 pause
- 原来的 `runCrawlSteps()` 仍在“死等恢复”,会自动继续跑下一轮
#### B) 窗口被关闭导致中断(需要重开窗口)
条件:
- `state.pause.reason === 'window_closed'``windowId/tabId` 不存在
行为:
- 根据 `currentStepIndex` 找到“第一个未 success 的步骤”作为 `startIndex`
- `openSingleTabWindow(url)` 重新打开爬取窗口(`dianshan/src/background/task/helper.ts:8`
- `setCrawlTaskState()` 写入新的 `windowId/tabId/status='running'/currentStepIndex=startIndex``dianshan/src/background/task/taskState.ts:20`
- 新建 `AbortController`,重新启动执行器:
- `runCrawlSteps(taskId, newTabId, platform.steps, signal, startIndex)``dianshan/src/background/task/crawlTask.ts:63`
---
## 6. “爬取过程中窗口被关掉”会发生什么(自动暂停)
触发点:
- `background/index.ts` 监听:
- `chrome.windows.onRemoved``dianshan/src/background/index.ts:92`
- `chrome.tabs.onRemoved`(兜底)(`dianshan/src/background/index.ts:97`
处理逻辑:
- 调用 `crawlTask.pauseCrawlOnWindowRemoved(windowId)``dianshan/src/background/task/crawlTask.ts:158`)或 `pauseCrawlOnTabRemoved(tabId)``dianshan/src/background/task/crawlTask.ts:188`
行为:
1) 中止执行器
- 找到当前任务对应的 `AbortController``abort()`
- 目的:避免后台继续对不存在的 tab 进行 `tabs.update/sendMessage` 导致刷屏错误
2) 状态改为 paused
- `status='paused'`
- `pause.reason='window_closed'`
- `pause.message` 提示用户点击继续可恢复
- 清空 `windowId/tabId`(避免 UI 侧再尝试 focus 老窗口)
用户点击继续后:
- 走第 5.2 的 B 分支:重开窗口 + 从未完成步骤继续
---
## 7. “全部步骤完成”会发生什么(发送结果 + 清理 + 关窗)
触发点:
- `runCrawlSteps()` 完成 for 循环后:
1) `updateCrawlTaskState(...status='completed')`
2) 调用 `finalizeCompletedTask(taskId)`
finalizeCompletedTask 做三件事(顺序很重要):
1) 发送结果给页面
- background -> content爬取 tab`sendTabMessage(tabId,'CRAWL_COMPLETED',{ result: ... })`
- 同时 storage 完成态会触发 `externalBridge` 对外广播 `DIANSHAN_CRAWL_DONE`
2) 清空本次任务记录
- `clearCrawlTaskState()`:移除 `chrome.storage.local['crawlTaskState']`
- popup 会收到 storage 改变并自动把 UI 恢复到“未开始”状态
3) 关闭爬取窗口
- `chrome.windows.remove(windowId)`
---
## 8. Popup/Overlay 如何拿到状态(同步机制总结)
### 8.1 Popup只看 storage强一致
- 初次加载:发 `GET_CRAWL_STATE`
- 后续更新:监听 `chrome.storage.onChanged`,只要 `crawlTaskState` 变化就刷新 UI
### 8.2 Overlay推送为主拉取为辅
- 推送background 写入 state 时会 `sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state)`
- overlay 监听 `chrome.runtime.onMessage`,收到 `action==='CRAWL_STATE_UPDATE'` 立刻渲染
- 拉取overlay 初始化时发 `GET_CRAWL_STATE_FOR_TAB`
- background 只在 `sender.tab.id === state.tabId` 时返回 state否则返回 null
---
## 9. 外部网页(官网/业务系统如何接收结果externalBridge
两种方式:
### 9.1 onMessageExternal短消息
- 网页发 `DIANSHAN_START_CRAWL / DIANSHAN_GET_CRAWL_STATE / ...`
- background `externalBridge.handleExternalMessage()` 返回当前状态/平台列表等
### 9.2 onConnectExternal长连接 Port
- 网页建立 Portname=`DIANSHAN_CRAWL`
- background 把当前 state 发一次 `DIANSHAN_CRAWL_STATE`
- 后续只要 storage 变化,就广播:
- `DIANSHAN_CRAWL_STATE`(进行中)
- `DIANSHAN_CRAWL_DONE`(完成且带 result
- `DIANSHAN_CRAWL_CLEARED / FAILED / CANCELED`(清理/失败/取消)
结果结构:
- `externalBridge.buildCrawlWebPayload(state)` 会给出:
- `state`:完整状态机
- `result`:仅当 `state.status==='completed'` 时非空,为按 step.uniqueKey 聚合的结果对象
---
## 10. 常见问题排查(建议)
1) overlay 不显示 / 不更新
- 先确认该 tab 是否为 `crawlTaskState.tabId`
- overlay 初始化会请求 `GET_CRAWL_STATE_FOR_TAB`,只有爬取 tab 才返回 state
- 确认 background 写入 state 时是否调用了 `setCrawlTaskState()`(它会推送 `CRAWL_STATE_UPDATE`
2) 点击取消 popup “闪一下”
- 通常是计时器回调解引用了空状态popup 侧必须在 state 清空时 stop timer
3) 窗口被关后任务还在后台跑
- 应当由 `windows.onRemoved/tabs.onRemoved` 触发 `pauseCrawlOnWindowRemoved/TabRemoved`
- 检查 background/index.ts 是否注册了监听
---
## 11. 约定:消息 action 一览(当前)
popup/content -> background`dianshan/src/shared/message.ts`
- `START_CRAWL`
- `GET_CRAWL_STATE`
- `GET_CRAWL_STATE_FOR_TAB`(仅爬取 tab 返回 state
- `CANCEL_CRAWL`
- `RESUME_CRAWL`
- `DISMISS_CRAWL`
- `CANCEL_AUTOCLOSE`(兼容旧 overlay 按钮,不建议新逻辑依赖)
background -> content`dianshan/src/shared/tab.ts`
- `CRAWL_STATE_UPDATE`overlay/pagerunner 可用)
- `CRAWL_COMPLETED`(完成时额外发送结果)