1
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import { getPlatformById } from '@/config/platforms';
|
||||
import type { CrawlPauseInfo, CrawlProgressStep, CrawlTaskState, PlatformConfig, PlatformStepConfig } from '@/types';
|
||||
import type { DomScrapeResult } from '../domScraper';
|
||||
import type { CrawlStateResponse } from '../types';
|
||||
import { getCrawlTaskState, setCrawlTaskState, updateCrawlTaskState } from './taskState';
|
||||
|
||||
interface PageRunnerResponse {
|
||||
ok: boolean;
|
||||
data?: DomScrapeResult | null;
|
||||
data?: any | null;
|
||||
interrupt?: CrawlPauseInfo;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
661
we.md
Normal file
661
we.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# 店闪扩展执行链路说明
|
||||
|
||||
这份文档按当前项目代码整理,目的是方便顺着代码阅读整个爬取流程。
|
||||
|
||||
## 1. 扩展入口关系
|
||||
|
||||
扩展入口由 `manifest.config.ts` 配置:
|
||||
|
||||
- `action.default_popup` 指向 `src/popup/index.html`,点击浏览器插件图标后打开 popup。
|
||||
- `background.service_worker` 指向 `src/background/index.ts`,负责接收消息、创建爬取窗口、执行爬取任务。
|
||||
- `content_scripts` 指向 `src/content/main.ts`,会注入到所有 `http/https` 页面,用于右下角进度浮窗和页面 DOM 抓取。
|
||||
- `permissions` 中的 `storage`、`tabs`、`windows` 支撑状态保存、页面跳转和窗口管理。
|
||||
|
||||
## 2. 点击 popup 后发生什么
|
||||
|
||||
### 2.1 popup 初始化
|
||||
|
||||
文件:`src/popup/App.vue`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `onMounted`
|
||||
- `getToken`
|
||||
- `refreshCrawlState`
|
||||
- `updateElapsedSeconds`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. 用户点击扩展图标,Chrome 打开 `src/popup/index.html`。
|
||||
2. Vue 加载 `src/popup/App.vue`。
|
||||
3. `onMounted` 执行:
|
||||
- 调用 `getToken()` 读取登录状态。
|
||||
- 调用 `refreshCrawlState()` 获取当前爬取任务。
|
||||
- 启动 `setInterval`,每秒刷新爬取状态和计时。
|
||||
4. `getToken()` 来自 `src/shared/auth.ts`:
|
||||
- 优先从 `chrome.storage.local` 读取 `token`。
|
||||
- 非扩展环境下从 `localStorage` 读取。
|
||||
|
||||
### 2.2 点击“立即爬取”
|
||||
|
||||
文件:`src/popup/App.vue`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `handleScan`
|
||||
- `sendBackgroundMessage`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. 用户点击“立即爬取”按钮。
|
||||
2. `handleScan()` 检查当前是否选择了平台。
|
||||
3. 通过 `sendBackgroundMessage()` 发送消息给 background:
|
||||
|
||||
```ts
|
||||
{
|
||||
action: 'START_CRAWL',
|
||||
payload: { platformId: selectedPlatform.value.id },
|
||||
}
|
||||
```
|
||||
|
||||
4. `sendBackgroundMessage()` 实际调用:
|
||||
|
||||
```ts
|
||||
chrome.runtime.sendMessage(message)
|
||||
```
|
||||
|
||||
5. popup 收到 background 返回的 `CrawlTaskState` 后,更新本地的 `crawlState`,页面开始显示进度。
|
||||
|
||||
## 3. background 如何接住 popup 消息
|
||||
|
||||
### 3.1 background 消息入口
|
||||
|
||||
文件:`src/background/index.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `chrome.runtime.onMessage.addListener`
|
||||
- `handleBackgroundMessage`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. background service worker 启动后,注册 `chrome.runtime.onMessage`。
|
||||
2. popup 发来的 `START_CRAWL` 会进入 `handleBackgroundMessage()`。
|
||||
3. `handleBackgroundMessage()` 调用:
|
||||
|
||||
```ts
|
||||
handleBackgroundCommand(message)
|
||||
```
|
||||
|
||||
4. 如果执行成功,调用 `sendResponse(result)` 把结果回给 popup。
|
||||
5. 如果执行失败,统一返回:
|
||||
|
||||
```ts
|
||||
{ ok: false, error: messageText }
|
||||
```
|
||||
|
||||
### 3.2 background 指令分发
|
||||
|
||||
文件:`src/background/service.ts`
|
||||
|
||||
作用:
|
||||
|
||||
```ts
|
||||
export {
|
||||
handleBackgroundCommand,
|
||||
handleInstalled,
|
||||
handleStartup,
|
||||
handleWindowRemoved,
|
||||
} from './service/lifecycle';
|
||||
```
|
||||
|
||||
这个文件现在只是 re-export,真正逻辑在 `src/background/service/lifecycle.ts`。
|
||||
|
||||
文件:`src/background/service/lifecycle.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `handleBackgroundCommand`
|
||||
|
||||
分发关系:
|
||||
|
||||
- `START_CRAWL` -> `startCrawl`
|
||||
- `GET_CRAWL_STATE` -> `getCrawlTaskState`
|
||||
- `CANCEL_CRAWL` -> `cancelCrawl`
|
||||
- `RESUME_CRAWL` -> `resumeCrawl`
|
||||
|
||||
这些消息类型定义在 `src/background/types.ts`。
|
||||
|
||||
## 4. 创建爬取任务和新窗口
|
||||
|
||||
文件:`src/background/service/crawlTask.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `startCrawl`
|
||||
- `createCrawlWindow`
|
||||
- `runCrawlSteps`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. `startCrawl(platformId)` 先调用 `getPlatformById(platformId)`。
|
||||
2. `getPlatformById` 来自 `src/config/platforms.ts`,用于找到当前平台配置。
|
||||
3. 读取平台配置中的第一个 step:
|
||||
|
||||
```ts
|
||||
const firstStep = platform.steps[0];
|
||||
```
|
||||
|
||||
4. 创建初始任务状态 `CrawlTaskState`:
|
||||
- `id`
|
||||
- `platformId`
|
||||
- `platformName`
|
||||
- `startedAt`
|
||||
- `status: 'running'`
|
||||
- `currentStepIndex: 0`
|
||||
- `steps`
|
||||
5. 调用 `setCrawlTaskState(nextState)` 写入 `chrome.storage.local`。
|
||||
6. 调用 `createCrawlWindow(firstStep.url)` 打开新的普通浏览器窗口。
|
||||
7. 窗口创建成功后,把 `windowId` 写回任务状态。
|
||||
8. 调用:
|
||||
|
||||
```ts
|
||||
void runCrawlSteps(platform, stateWithWindow);
|
||||
```
|
||||
|
||||
这里使用 `void`,表示后台任务异步继续跑;`startCrawl` 会先把初始状态返回给 popup。
|
||||
|
||||
## 5. 爬取状态保存在哪里
|
||||
|
||||
文件:`src/background/service/taskState.ts`
|
||||
|
||||
核心方法:
|
||||
|
||||
- `getCrawlTaskState`
|
||||
- `setCrawlTaskState`
|
||||
- `updateCrawlTaskState`
|
||||
|
||||
保存位置:
|
||||
|
||||
```ts
|
||||
chrome.storage.local
|
||||
```
|
||||
|
||||
保存 key:
|
||||
|
||||
```ts
|
||||
crawlTaskState
|
||||
```
|
||||
|
||||
当前项目没有把爬取结果单独保存到数据库、文件或独立 result key。每一步的结果直接保存在:
|
||||
|
||||
```ts
|
||||
CrawlTaskState.steps[index].result
|
||||
```
|
||||
|
||||
也就是说,popup 和 content 看到的进度、暂停信息、最终结果,都来自 `chrome.storage.local` 中的 `crawlTaskState`。
|
||||
|
||||
## 6. background 如何逐步爬取页面
|
||||
|
||||
文件:`src/background/service/crawlTask.ts`
|
||||
|
||||
核心方法:
|
||||
|
||||
- `runCrawlSteps`
|
||||
- `getWindowActiveTabId`
|
||||
- `waitForTabLoaded`
|
||||
- `scrapeStepInContent`
|
||||
- `sendPageRunnerMessage`
|
||||
- `pauseForInterrupt`
|
||||
- `waitUntilResumed`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. `runCrawlSteps(platform, initialState)` 按 `platform.steps` 顺序循环。
|
||||
2. 每进入一个 step,会调用 `updateCrawlTaskState`:
|
||||
- 更新 `currentStepIndex`。
|
||||
- 把当前 step 标记为 `running`。
|
||||
- 清空当前 step 的旧 message。
|
||||
3. 调用 `getWindowActiveTabId(windowId)` 找到爬取窗口里的当前 tab。
|
||||
4. 调用:
|
||||
|
||||
```ts
|
||||
chrome.tabs.update(tabId, { url: step.url, active: true })
|
||||
```
|
||||
|
||||
跳转到当前 step 配置的页面地址。
|
||||
|
||||
5. 调用 `waitForTabLoaded(tabId)` 等待 Chrome 的 tab 状态变成 `complete`。
|
||||
6. 调用 `scrapeStepInContent(tabId, step)`,让目标页面里的 content script 开始检查页面和抓取 DOM。
|
||||
|
||||
## 7. background 怎么通知目标网页抓取
|
||||
|
||||
文件:`src/background/service/crawlTask.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `scrapeStepInContent`
|
||||
- `sendPageRunnerMessage`
|
||||
|
||||
发送消息:
|
||||
|
||||
```ts
|
||||
{
|
||||
action: 'SCRAPE_STEP',
|
||||
payload: {
|
||||
fields: step.fields,
|
||||
checkSelector: step.checkSelector,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
实际调用:
|
||||
|
||||
```ts
|
||||
chrome.tabs.sendMessage(tabId, message)
|
||||
```
|
||||
|
||||
这里不是 popup 发给 background,而是 background 发给目标网页 tab 里的 content script。
|
||||
|
||||
`scrapeStepInContent` 还有一个容错逻辑:如果 content script 还没注入完成,出现类似 `Could not establish connection. Receiving end does not exist.` 的错误,会在 20 秒内每 500ms 重试一次。
|
||||
|
||||
## 8. content script 如何接住抓取消息
|
||||
|
||||
### 8.1 content script 挂载
|
||||
|
||||
文件:`src/content/main.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `mountApp`
|
||||
- `setupPageRunner`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. 目标页面加载时,Chrome 根据 `manifest.config.ts` 注入 `src/content/main.ts`。
|
||||
2. `main.ts` 等待 DOM 可用后执行 `mountApp()`。
|
||||
3. `mountApp()` 创建 `#dianshan-crx-root` 容器。
|
||||
4. 挂载 `src/content/App.vue`,用于显示右下角爬取计时按钮和进度面板。
|
||||
5. 调用 `setupPageRunner()` 注册页面消息监听器。
|
||||
|
||||
### 8.2 页面执行器接收 SCRAPE_STEP
|
||||
|
||||
文件:`src/content/pageRunner.ts`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `setupPageRunner`
|
||||
- `handlePageRunnerMessage`
|
||||
- `detectPageInterrupt`
|
||||
- `waitForStableSelector`
|
||||
- `processFields`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. `setupPageRunner()` 注册:
|
||||
|
||||
```ts
|
||||
chrome.runtime.onMessage.addListener(...)
|
||||
```
|
||||
|
||||
2. background 发送 `SCRAPE_STEP` 后,进入 `handlePageRunnerMessage()`。
|
||||
3. 先调用 `detectPageInterrupt()` 判断是否遇到:
|
||||
- 登录页:`reauth`
|
||||
- 验证码或风控:`shield`
|
||||
- 页面不存在:`not_found`
|
||||
4. 如果检测到中断,返回:
|
||||
|
||||
```ts
|
||||
{ ok: false, interrupt }
|
||||
```
|
||||
|
||||
5. 如果没有中断,调用 `waitForStableSelector(checkSelector, 18000)`。
|
||||
6. `checkSelector` 来自 `src/config/platforms.ts` 的 step 配置,用于判断页面关键 DOM 是否出现并可见。
|
||||
7. 如果 18 秒内关键 DOM 仍未稳定出现,返回 `page_not_ready` 中断。
|
||||
8. 如果页面可用,调用:
|
||||
|
||||
```ts
|
||||
processFields(message.payload.fields, document.body)
|
||||
```
|
||||
|
||||
开始真正的 DOM 字段采集。
|
||||
|
||||
## 9. DOM 数据怎么提取
|
||||
|
||||
文件:`src/background/domScraper.ts`
|
||||
|
||||
虽然文件在 `background` 目录下,但它被 `src/content/pageRunner.ts` import 后,实际是在目标网页的 content script 环境里执行 DOM 查询。
|
||||
|
||||
核心方法:
|
||||
|
||||
- `processFields`
|
||||
- `processList`
|
||||
- `processTable`
|
||||
- `autoClick`
|
||||
- `extractValue`
|
||||
- `waitForElement`
|
||||
|
||||
执行逻辑:
|
||||
|
||||
1. `processFields(columns, rootDom)` 遍历当前 step 的 `fields`。
|
||||
2. 每个字段先执行 `autoClick(item, rootDom)`:
|
||||
- 如果字段配置了 `condition.list`,就按顺序点击对应选择器。
|
||||
- 每次点击后等待 `condition.time`。
|
||||
3. 再根据 `item.className` 查询元素。
|
||||
4. 普通字段:
|
||||
- 没有 `keys` 时,调用 `extractValue(element, item)`。
|
||||
- 有 `keys` 时,递归调用 `processFields(item.keys, element)`。
|
||||
5. 列表字段:
|
||||
- `type === 1` 时进入 `processList`。
|
||||
- 按 `config.className` 找到列表项。
|
||||
- 对每个列表项递归执行 `processFields(config.keys, element)`。
|
||||
- 如果配置了 `pagination`,会点击下一页继续采集。
|
||||
6. 表格字段:
|
||||
- `type === 2` 时进入 `processTable`。
|
||||
- 按 `tableParts` 找到不同 table 片段。
|
||||
- 以第一个 part 的行数为准,按行拼接不同 part 的字段。
|
||||
- 如果配置了 `pagination`,会点击下一页继续采集。
|
||||
7. `extractValue` 的取值规则:
|
||||
- 配置了 `attr` 就取指定属性。
|
||||
- `IMG` 默认取 `src`。
|
||||
- `A` 默认取 `href`,相对路径会拼上当前 origin。
|
||||
- 其他元素默认取 `textContent`。
|
||||
|
||||
## 10. 采集结果怎么回传和保存
|
||||
|
||||
数据流向:
|
||||
|
||||
1. `processFields` 返回当前 step 的 DOM 采集结果。
|
||||
2. `src/content/pageRunner.ts` 返回:
|
||||
|
||||
```ts
|
||||
{ ok: true, data }
|
||||
```
|
||||
|
||||
3. `src/background/service/crawlTask.ts` 的 `scrapeStepInContent` 接到 response。
|
||||
4. `runCrawlSteps` 调用 `updateCrawlTaskState`:
|
||||
|
||||
```ts
|
||||
steps[index].result = response.data
|
||||
steps[index].status = 'success'
|
||||
```
|
||||
|
||||
5. 最新任务状态写回 `chrome.storage.local` 的 `crawlTaskState`。
|
||||
6. popup 和 content 浮窗每秒发送 `GET_CRAWL_STATE`,读取到最新结果并展示。
|
||||
|
||||
所以当前项目的数据传递是:
|
||||
|
||||
```text
|
||||
目标网页 DOM
|
||||
-> content/pageRunner.ts
|
||||
-> background/crawlTask.ts
|
||||
-> chrome.storage.local:crawlTaskState
|
||||
-> popup/App.vue 和 content/App.vue 轮询展示
|
||||
```
|
||||
|
||||
## 11. popup 进度如何刷新
|
||||
|
||||
文件:`src/popup/App.vue`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `refreshCrawlState`
|
||||
- `sendBackgroundMessage`
|
||||
- `updateElapsedSeconds`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. popup 打开后每秒调用一次 `refreshCrawlState()`。
|
||||
2. `refreshCrawlState()` 发送:
|
||||
|
||||
```ts
|
||||
{ action: 'GET_CRAWL_STATE' }
|
||||
```
|
||||
|
||||
3. background 的 `handleBackgroundCommand` 收到后调用 `getCrawlTaskState()`。
|
||||
4. popup 拿到 `CrawlTaskState` 后:
|
||||
- 展示平台名。
|
||||
- 展示运行时间。
|
||||
- 展示每个 step 的状态。
|
||||
- 如果 step 有 `result`,用 `JSON.stringify(step.result, null, 2)` 打印出来。
|
||||
|
||||
## 12. 目标网页右下角按钮如何刷新
|
||||
|
||||
文件:`src/content/App.vue`
|
||||
|
||||
触发方法:
|
||||
|
||||
- `onMounted`
|
||||
- `refreshCrawlState`
|
||||
- `updateElapsedSeconds`
|
||||
- `handleResumeCrawl`
|
||||
|
||||
执行过程:
|
||||
|
||||
1. content script 挂载后,`src/content/App.vue` 每秒调用 `refreshCrawlState()`。
|
||||
2. 它同样发送:
|
||||
|
||||
```ts
|
||||
{ action: 'GET_CRAWL_STATE' }
|
||||
```
|
||||
|
||||
3. 如果任务状态是 `running` 或 `paused`,显示右下角计时按钮。
|
||||
4. 点击按钮会展开进度面板。
|
||||
5. 如果任务是 `paused`,面板里显示暂停原因,并提供“我已处理,继续”按钮。
|
||||
6. 点击继续时,调用 `handleResumeCrawl()`,发送:
|
||||
|
||||
```ts
|
||||
{ action: 'RESUME_CRAWL' }
|
||||
```
|
||||
|
||||
## 13. 暂停和继续流程
|
||||
|
||||
触发原因主要来自 `src/content/pageRunner.ts`:
|
||||
|
||||
- `isLoginPage()` 检测到登录页。
|
||||
- `isShieldPage()` 检测到验证码或风控。
|
||||
- `isNotFoundPage()` 检测到页面不存在。
|
||||
- `waitForStableSelector()` 超时,认为页面关键内容没准备好。
|
||||
|
||||
流程:
|
||||
|
||||
1. content 返回:
|
||||
|
||||
```ts
|
||||
{ ok: false, interrupt }
|
||||
```
|
||||
|
||||
2. background 的 `runCrawlSteps` 检测到 `response.interrupt`。
|
||||
3. 调用 `pauseForInterrupt(taskId, stepIndex, interrupt)`。
|
||||
4. `pauseForInterrupt` 把状态写成:
|
||||
- `status: 'paused'`
|
||||
- `pause: interrupt`
|
||||
- 当前 step 保持 `running`
|
||||
- 当前 step 的 `message` 设置为中断提示
|
||||
5. popup 和 content 每秒轮询到 paused 状态后展示提示。
|
||||
6. 用户处理完登录或验证码后,点击继续。
|
||||
7. popup 或 content 发送 `RESUME_CRAWL`。
|
||||
8. background 调用 `resumeCrawl()`:
|
||||
- `status` 改回 `running`
|
||||
- 清空 `pause`
|
||||
- 当前 step 的 `message` 清空
|
||||
9. `runCrawlSteps` 中的 `waitUntilResumed()` 发现状态恢复为 `running`,重新执行当前 step。
|
||||
|
||||
## 14. 取消和关闭窗口流程
|
||||
|
||||
### 14.1 用户点击取消
|
||||
|
||||
文件:
|
||||
|
||||
- `src/popup/App.vue`
|
||||
- `src/background/service/lifecycle.ts`
|
||||
- `src/background/service/crawlTask.ts`
|
||||
|
||||
方法:
|
||||
|
||||
- `handleCancelCrawl`
|
||||
- `handleBackgroundCommand`
|
||||
- `cancelCrawl`
|
||||
|
||||
流程:
|
||||
|
||||
1. popup 发送:
|
||||
|
||||
```ts
|
||||
{ action: 'CANCEL_CRAWL' }
|
||||
```
|
||||
|
||||
2. background 分发到 `cancelCrawl()`。
|
||||
3. `cancelCrawl()` 把当前任务状态改成 `canceled`。
|
||||
4. 当前 step 被标记为 `failed`,message 为 `用户已取消`。
|
||||
5. 如果存在 `windowId`,调用 `chrome.windows.remove(windowId)` 关闭爬取窗口。
|
||||
|
||||
### 14.2 用户直接关闭爬取窗口
|
||||
|
||||
文件:
|
||||
|
||||
- `src/background/index.ts`
|
||||
- `src/background/service/lifecycle.ts`
|
||||
- `src/background/service/crawlTask.ts`
|
||||
|
||||
方法:
|
||||
|
||||
- `chrome.windows.onRemoved.addListener`
|
||||
- `handleWindowRemoved`
|
||||
- `cancelCrawlWhenWindowRemoved`
|
||||
|
||||
流程:
|
||||
|
||||
1. Chrome 触发 `windows.onRemoved`。
|
||||
2. `handleWindowRemoved(windowId)` 被调用。
|
||||
3. `cancelCrawlWhenWindowRemoved(windowId)` 检查关闭的是否是当前爬取窗口。
|
||||
4. 如果匹配且任务还在 running,就把任务改成 `canceled`。
|
||||
5. 当前 step 标记为 `failed`,message 为 `爬取窗口已关闭`。
|
||||
|
||||
## 15. 平台配置如何驱动爬取
|
||||
|
||||
文件:`src/config/platforms.ts`
|
||||
|
||||
核心导出:
|
||||
|
||||
- `PLATFORM_CONFIGS`
|
||||
- `getPlatformById`
|
||||
|
||||
平台配置结构来自 `src/types/platform.ts`:
|
||||
|
||||
- `PlatformConfig`
|
||||
- `PlatformStepConfig`
|
||||
- `PlatformFieldConfig`
|
||||
- `PlatformPaginationConfig`
|
||||
- `PlatformTablePartConfig`
|
||||
- `PlatformClickCondition`
|
||||
|
||||
关键字段:
|
||||
|
||||
- `steps`:平台要按顺序爬取的页面。
|
||||
- `step.url`:当前步骤要打开的页面地址。
|
||||
- `step.checkSelector`:判断页面是否可抓取的关键元素。
|
||||
- `step.fields`:当前页面要采集的字段。
|
||||
- `field.className`:字段选择器。
|
||||
- `field.keys`:子字段,支持递归。
|
||||
- `field.type`:字段类型,默认普通字段,`1` 是列表,`2` 是表格。
|
||||
- `field.condition`:采集字段前要自动点击的元素。
|
||||
- `field.pagination`:列表或表格翻页配置。
|
||||
- `field.tableParts`:表格分段配置,用来拼接多个 table 片段。
|
||||
|
||||
## 16. 类型结构在哪里看
|
||||
|
||||
主要类型文件:
|
||||
|
||||
- `src/background/types.ts`:background 接收的消息类型和统一响应类型。
|
||||
- `src/types/crawl.ts`:爬取任务状态、步骤状态、暂停原因。
|
||||
- `src/types/platform.ts`:平台配置、字段配置、分页配置、表格配置。
|
||||
- `src/types/index.ts`:统一导出类型。
|
||||
|
||||
最关键的运行时状态是 `CrawlTaskState`:
|
||||
|
||||
```ts
|
||||
interface CrawlTaskState {
|
||||
id: string;
|
||||
platformId: string;
|
||||
platformName: string;
|
||||
windowId?: number;
|
||||
startedAt: number;
|
||||
status: 'running' | 'paused' | 'completed' | 'failed' | 'canceled';
|
||||
pause?: CrawlPauseInfo;
|
||||
currentStepIndex: number;
|
||||
steps: CrawlProgressStep[];
|
||||
}
|
||||
```
|
||||
|
||||
每个 step 的结果保存在:
|
||||
|
||||
```ts
|
||||
interface CrawlProgressStep {
|
||||
name: string;
|
||||
uniqueKey: string;
|
||||
status: 'pending' | 'running' | 'success' | 'failed';
|
||||
message?: string;
|
||||
result?: unknown;
|
||||
}
|
||||
```
|
||||
|
||||
## 17. 整体数据流图
|
||||
|
||||
```text
|
||||
用户点击插件图标
|
||||
-> src/popup/App.vue:onMounted
|
||||
-> 读取 token 和 crawlTaskState
|
||||
|
||||
用户点击立即爬取
|
||||
-> src/popup/App.vue:handleScan
|
||||
-> chrome.runtime.sendMessage({ action: 'START_CRAWL' })
|
||||
|
||||
background 接收消息
|
||||
-> src/background/index.ts:handleBackgroundMessage
|
||||
-> src/background/service/lifecycle.ts:handleBackgroundCommand
|
||||
-> src/background/service/crawlTask.ts:startCrawl
|
||||
|
||||
创建任务和窗口
|
||||
-> src/background/service/taskState.ts:setCrawlTaskState
|
||||
-> src/background/service/crawlTask.ts:createCrawlWindow
|
||||
-> src/background/service/crawlTask.ts:runCrawlSteps
|
||||
|
||||
逐个页面跳转
|
||||
-> chrome.tabs.update(tabId, { url: step.url })
|
||||
-> src/background/service/crawlTask.ts:waitForTabLoaded
|
||||
-> src/background/service/crawlTask.ts:scrapeStepInContent
|
||||
|
||||
目标页面执行抓取
|
||||
-> chrome.tabs.sendMessage({ action: 'SCRAPE_STEP' })
|
||||
-> src/content/pageRunner.ts:handlePageRunnerMessage
|
||||
-> src/content/pageRunner.ts:waitForStableSelector
|
||||
-> src/background/domScraper.ts:processFields
|
||||
|
||||
结果回到 background
|
||||
-> src/background/service/crawlTask.ts:runCrawlSteps
|
||||
-> src/background/service/taskState.ts:updateCrawlTaskState
|
||||
-> 写入 chrome.storage.local:crawlTaskState
|
||||
|
||||
多端展示
|
||||
-> src/popup/App.vue 每秒 GET_CRAWL_STATE
|
||||
-> src/content/App.vue 每秒 GET_CRAWL_STATE
|
||||
-> popup 展示完整进度和结果
|
||||
-> 目标网页右下角展示计时按钮和进度面板
|
||||
```
|
||||
|
||||
## 18. 推荐阅读代码顺序
|
||||
|
||||
如果要按最顺的方式读代码,可以这样看:
|
||||
|
||||
1. `manifest.config.ts`:先看扩展入口。
|
||||
2. `src/popup/App.vue`:看用户点按钮时发什么消息。
|
||||
3. `src/background/index.ts`:看 background 怎么接消息。
|
||||
4. `src/background/service/lifecycle.ts`:看消息怎么分发。
|
||||
5. `src/background/service/crawlTask.ts`:看任务创建、窗口打开、页面跳转、暂停恢复。
|
||||
6. `src/background/service/taskState.ts`:看状态怎么保存。
|
||||
7. `src/content/main.ts`:看 content script 怎么挂载。
|
||||
8. `src/content/pageRunner.ts`:看目标页面怎么接收 background 抓取指令。
|
||||
9. `src/background/domScraper.ts`:看 DOM 字段怎么递归提取。
|
||||
10. `src/config/platforms.ts`:结合抓取逻辑看配置如何控制页面和字段。
|
||||
11. `src/types/crawl.ts`、`src/types/platform.ts`:最后看数据结构。
|
||||
Reference in New Issue
Block a user