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

18 KiB
Raw Blame History

dianshan 插件爬取任务开发文档(逻辑/通信/状态)

目的:把“点击爬取后发生了什么、会触发哪些方法、给谁通信、改变哪些状态”说清楚,方便后续二开与排查。

范围:dianshan/ 目录内的扩展popup + background + content/overlay + 外部网页桥接)。

注意:当前项目代码里没有 startDianshanCrawl() / getDianshanCrawlState() 这类方法名; 正确入口分别是 startCrawl()dianshan/src/background/task/crawlTask.ts:14)与 getCrawlTaskState()dianshan/src/background/task/taskState.ts:10),以及对应的消息 actionSTART_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 的 messagechrome.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.localcrawlTaskState
    • 写入时同时 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.onRemoveddianshan/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

  • 爬取窗口悬浮 UImountCrawlOverlay()dianshan/src/content/crawlOverlay.ts:36

  • 悬浮 UI 初始化拉取快照:GET_CRAWL_STATE_FOR_TABdianshan/src/content/crawlOverlay.ts:67

  • 悬浮 UI 实时状态推送:CRAWL_STATE_UPDATEdianshan/src/content/crawlOverlay.ts:54


2. 核心数据结构CrawlTaskState状态机

存储位置:chrome.storage.local['crawlTaskState']

关键字段(简化说明):

  • id:任务唯一 IDplatformId-startedAt
  • platformId/platformName:平台信息
  • windowId/tabId:当前爬取窗口与承载爬取的 taboverlay 只在这个 tab 渲染)
  • startedAt:任务开始时间戳(用于 popup/overlay 计时)
  • statusrunning | paused | completed | failed | canceled
  • pause:当 status='paused' 时存在,包含
    • reasonreauth | shield | not_found | page_not_ready | window_closed
    • message:给用户看的暂停提示
  • currentStepIndex:当前执行到的步骤索引
  • steps[]:每个步骤的进度条记录
    • uniqueKey/name
    • statuspending | 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. 触发方法
  • popupuse-scan.ts -> handleScan()dianshan/src/popup/hook/use-scan.ts:46
  1. 通信
  • popup -> backgroundsendBackgroundMessage({ action:'START_CRAWL', payload:{ platformId } })
  1. background 入口
  • background/index.tschrome.runtime.onMessage 收到 START_CRAWLdianshan/src/background/index.ts:22 / dianshan/src/background/index.ts:34
  • 调用:crawlTask.startCrawl(platformId)dianshan/src/background/task/crawlTask.ts:14
  1. 状态变化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) 写入 storagedianshan/src/background/task/taskState.ts:20
    • 这一步会触发:
      • popup 的 storage 监听更新 UI
      • background 同时 sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state) 推送给 overlaydianshan/src/background/task/taskState.ts:20
      • externalBridge 通过 storage.onChanged 对外广播(如果网页有连 port
  1. 启动执行器
  • 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. 触发方法
  • backgroundupdateCrawlTaskState(taskId, updater)
  1. 状态变化
  • currentStepIndex = i
  • steps[i].status = 'running'(其它步骤保持原样)
  1. 通信影响
  • 因为 updateCrawlTaskState() 内部会 setCrawlTaskState()
    • storage 写入 -> popup UI 更新
    • 同时 sendTabMessage(tabId,'CRAWL_STATE_UPDATE',state) -> overlay 更新
    • storage.onChanged -> external bridge 广播(如果外部网页连着)

4.2 跳转页面并等待加载完成

  1. 触发方法
  • backgroundchrome.tabs.update(tabId, { url: step.url, active: true })
  • backgroundwaitForTabLoaded(tabId, signal)
  1. 通信对象
  • background -> Chrome tabs API无 content 参与)
  1. 状态变化
  • 不改变任务状态,只是准备让 content script 进入目标页面上下文

4.3 让 content script 执行抓取(核心通信)

  1. 触发方法
  • backgroundscrapeStepInContent(tabId, step, signal)
  1. 通信
  • background -> content爬取窗口 tabchrome.tabs.sendMessage(tabId, { action:'SCRAPE_STEP', payload:{ fields, checkSelector } })
  1. content 入口
  • content/pageRunner.tschrome.runtime.onMessage 收到 SCRAPE_STEP
    • detectPageInterrupt():先判断是否需要人工处理(登录/验证码/404/未就绪)
    • waitForStableSelector(checkSelector, timeout):等待关键 DOM 稳定出现
    • processFields(fields, document.body):按配置抓取 DOM 数据
  1. 返回值约定PageRunnerResponse
  • ok: true, data: DomScrapeResult:本步骤抓取成功
  • interrupt: CrawlPauseInfo:需要人工处理(会进入 paused
  • ok: false, error:未就绪/异常background 会重试

4.4 抓取成功:写入结果并标记 step success

  1. 触发方法
  • backgroundupdateCrawlTaskState(taskId, updater)
  1. 状态变化
  • steps[i].status = 'success'
  • steps[i].result = res.data
  1. 通信影响
  • 同 4.1storage + overlay 推送 + external bridge 广播

4.5 抓取中断:进入 paused需要用户处理

中断来源:

  • content 检测到 shield/reauth/not_found/page_not_ready
  • 或者:爬取窗口被用户关掉(见第 6 章)
  1. 触发方法(以 content 中断为例)
  • backgroundupdateCrawlTaskState(taskId, s => ({ ...s, status:'paused', pause: interrupt }))dianshan/src/background/task/crawlTask.ts:93
  1. 状态变化
  • status='paused'
  • pause={reason,message}
  1. 执行器行为
  • background 在 runCrawlSteps 内部“死等恢复”:
    • 循环检查 getCrawlTaskState()?.status === 'paused'
    • 直到用户点击“继续”把状态改回 running 才继续
  1. UI 行为
  • popup / overlay 都会显示暂停提示 + “Continue”

5. “点击继续”会发生什么RESUME_CRAWL

触发来源:

  • popup 的 Continue
  • overlay 的 Continue

5.1 popup/overlay 发消息

  • popupuse-scan.ts -> handleResumeCrawl() -> sendBackgroundMessage({action:'RESUME_CRAWL'})dianshan/src/popup/hook/use-scan.ts:83
  • overlaycrawlOverlay.ts -> chrome.runtime.sendMessage({action:'RESUME_CRAWL'})dianshan/src/content/crawlOverlay.ts:392

5.2 background 处理 resume

入口:

  • background/index.ts switch case RESUME_CRAWLdianshan/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=startIndexdianshan/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.onRemoveddianshan/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. 中止执行器
  • 找到当前任务对应的 AbortControllerabort()
  • 目的:避免后台继续对不存在的 tab 进行 tabs.update/sendMessage 导致刷屏错误
  1. 状态改为 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爬取 tabsendTabMessage(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
  1. 点击取消 popup “闪一下”
  • 通常是计时器回调解引用了空状态popup 侧必须在 state 清空时 stop timer
  1. 窗口被关后任务还在后台跑
  • 应当由 windows.onRemoved/tabs.onRemoved 触发 pauseCrawlOnWindowRemoved/TabRemoved
  • 检查 background/index.ts 是否注册了监听

11. 约定:消息 action 一览(当前)

popup/content -> backgrounddianshan/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 -> contentdianshan/src/shared/tab.ts

  • CRAWL_STATE_UPDATEoverlay/pagerunner 可用)
  • CRAWL_COMPLETED(完成时额外发送结果)