620 lines
17 KiB
Markdown
620 lines
17 KiB
Markdown
# AI平台收集功能使用说明
|
||
|
||
## 概述
|
||
|
||
`internal/collect` 模块提供了访问多个AI平台并进行问答的功能,目前支持以下平台:
|
||
|
||
- **文心一言** (wenxin) - 百度AI助手
|
||
- **DeepSeek** (deepseek) - 深度求索AI
|
||
- **豆包** (doubao) - 字节跳动AI助手
|
||
- **通义千问** (qianwen) - 阿里云AI助手
|
||
|
||
## 架构设计
|
||
|
||
### 核心组件
|
||
|
||
1. **CollectorInterface** - 收集器接口
|
||
- `WaitLogin() (bool, string)` - 等待登录
|
||
- `AskQuestion(question string) (string, error)` - 提问并获取答案
|
||
|
||
2. **BaseCollector** - 基础收集器
|
||
- 浏览器驱动管理
|
||
- Cookie管理(保存/加载)
|
||
- 页面操作工具方法
|
||
|
||
3. **CollectManager** - 收集管理器
|
||
- 统一管理不同平台的收集器
|
||
- 提供便捷的API调用
|
||
|
||
4. **平台实现**
|
||
- `WenxinCollector` - 文心一言实现
|
||
- `DeepseekCollector` - DeepSeek实现
|
||
- `DoubaoCollector` - 豆包实现
|
||
- `QianwenCollector` - 通义千问实现
|
||
|
||
## 快速开始
|
||
|
||
### 1. 基本使用
|
||
|
||
``go
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"geo/internal/collect"
|
||
"geo/internal/config"
|
||
"log"
|
||
"os"
|
||
)
|
||
|
||
func main() {
|
||
// 加载配置
|
||
cfg := &config.Config{
|
||
Sys: config.SysConfig{
|
||
ChromePath: "chrome/chrome.exe", // Chrome浏览器路径
|
||
ChromeDataDir: "chrome_data", // Chrome数据目录
|
||
CookiesDir: "cookies", // Cookie存储目录
|
||
LogsDir: "logs", // 日志目录
|
||
},
|
||
}
|
||
|
||
ctx := context.Background()
|
||
logger := log.New(os.Stdout, "", log.LstdFlags)
|
||
|
||
// 创建管理器
|
||
manager := collect.NewCollectManager(ctx, cfg, logger)
|
||
|
||
// 设置参数
|
||
params := &collect.CollectParams{
|
||
Headless: false, // 是否无头模式(false显示浏览器窗口)
|
||
UserIndex: "user_001", // 用户索引
|
||
PlatIndex: "wenxin", // 平台索引
|
||
RequestID: "req_001", // 请求ID
|
||
Platform: "wenxin", // 平台类型
|
||
}
|
||
|
||
// 向文心一言提问
|
||
question := "请介绍一下Go语言的特点"
|
||
answer, err := manager.AskQuestion("wenxin", params, question)
|
||
if err != nil {
|
||
fmt.Printf("错误: %v\n", err)
|
||
return
|
||
}
|
||
|
||
fmt.Printf("问题: %s\n", question)
|
||
fmt.Printf("答案: %s\n", answer)
|
||
}
|
||
```
|
||
|
||
### 2. 多平台对比
|
||
|
||
``go
|
||
// 向多个AI平台提问同一个问题
|
||
platforms := []string{"wenxin", "deepseek", "doubao", "qianwen"}
|
||
question := "什么是人工智能?"
|
||
|
||
for _, platform := range platforms {
|
||
params := &collect.CollectParams{
|
||
Headless: true,
|
||
UserIndex: "user_001",
|
||
PlatIndex: platform,
|
||
RequestID: fmt.Sprintf("req_%s", platform),
|
||
Platform: platform,
|
||
}
|
||
|
||
answer, err := manager.AskQuestion(platform, params, question)
|
||
if err != nil {
|
||
fmt.Printf("[%s] 错误: %v\n", platform, err)
|
||
continue
|
||
}
|
||
|
||
fmt.Printf("[%s] 答案: %s\n\n", platform, answer)
|
||
}
|
||
```
|
||
|
||
### 3. 登录管理
|
||
|
||
``go
|
||
// 首次使用时需要登录
|
||
params := &collect.CollectParams{
|
||
Headless: false, // 显示浏览器窗口以便扫码登录
|
||
UserIndex: "user_001",
|
||
PlatIndex: "wenxin",
|
||
RequestID: "login_req",
|
||
Platform: "wenxin",
|
||
}
|
||
|
||
// 等待登录(会打开浏览器窗口,需要手动扫码或输入账号密码)
|
||
success, msg := manager.WaitLogin("wenxin", params)
|
||
if success {
|
||
fmt.Println("登录成功!Cookie已保存")
|
||
} else {
|
||
fmt.Printf("登录失败: %s\n", msg)
|
||
}
|
||
|
||
// 后续使用会自动加载Cookie,无需重复登录
|
||
params.Headless = true // 可以切换到无头模式
|
||
answer, _ := manager.AskQuestion("wenxin", params, "你好")
|
||
```
|
||
|
||
### 4. 列出支持的平台
|
||
|
||
``go
|
||
platforms := manager.ListPlatforms()
|
||
fmt.Printf("支持的平台: %v\n", platforms)
|
||
// 输出: 支持的平台: [wenxin deepseek doubao qianwen]
|
||
```
|
||
|
||
## 配置说明
|
||
|
||
### 必需的配置项
|
||
|
||
``go
|
||
type SysConfig struct {
|
||
ChromePath string // Chrome浏览器可执行文件路径
|
||
ChromeDataDir string // Chrome用户数据目录
|
||
CookiesDir string // Cookie存储目录
|
||
LogsDir string // 日志文件目录
|
||
}
|
||
```
|
||
|
||
### 示例配置
|
||
|
||
``go
|
||
cfg := &config.Config{
|
||
Sys: config.SysConfig{
|
||
ChromePath: "/usr/bin/google-chrome", // Linux
|
||
// ChromePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", // Windows
|
||
ChromeDataDir: "./chrome_data",
|
||
CookiesDir: "./cookies",
|
||
LogsDir: "./logs",
|
||
},
|
||
}
|
||
```
|
||
|
||
## 工作流程
|
||
|
||
1. **初始化浏览器** - 启动Chrome浏览器实例
|
||
2. **加载Cookie** - 从本地文件加载之前的登录状态
|
||
3. **检查登录** - 验证是否已登录
|
||
4. **导航到聊天页面** - 打开AI平台的对话界面
|
||
5. **输入问题** - 在输入框中输入问题
|
||
6. **点击发送** - 触发AI回答
|
||
7. **等待回答** - 等待AI生成完整答案
|
||
8. **提取答案** - 从页面中提取回答内容
|
||
9. **返回结果** - 将答案返回给调用者
|
||
|
||
## 注意事项
|
||
|
||
### 1. 首次使用需要登录
|
||
|
||
每个平台首次使用时需要手动登录:
|
||
- 设置 `Headless: false` 显示浏览器窗口
|
||
- 调用 `WaitLogin()` 方法
|
||
- 在浏览器中完成登录操作(扫码或输入账号密码)
|
||
- 登录成功后Cookie会自动保存
|
||
|
||
### 2. Cookie管理
|
||
|
||
- Cookie保存在 `cookies/{UserIndex}/{PlatIndex}.json`
|
||
- 下次使用会自动加载Cookie,无需重复登录
|
||
- 如果登录失效,重新调用 `WaitLogin()` 即可
|
||
|
||
### 3. 选择器适配
|
||
|
||
由于AI平台的页面结构可能会更新,如果遇到问题可能需要调整CSS选择器:
|
||
- 在对应的Collector文件中修改 `inputSelectors`、`sendSelectors`、`answerSelectors`
|
||
- 可以通过浏览器的开发者工具查看最新的元素选择器
|
||
|
||
### 4. 超时设置
|
||
|
||
- 登录超时: 180-300秒
|
||
- 回答超时: 120秒
|
||
- 可根据实际情况在代码中调整
|
||
|
||
### 5. 无头模式
|
||
|
||
- 开发调试时建议设置 `Headless: false`
|
||
- 生产环境可以设置 `Headless: true` 节省资源
|
||
|
||
## 扩展新平台
|
||
|
||
如果要添加新的AI平台,需要:
|
||
|
||
1. 创建新的Collector文件,如 `newplatform.go`
|
||
2. 实现 `CollectorInterface` 接口
|
||
3. 继承 `BaseCollector` 基础结构
|
||
4. 在 `interface.go` 的 `CollectorMap` 中注册
|
||
|
||
示例:
|
||
|
||
``go
|
||
package collect
|
||
|
||
import (
|
||
"context"
|
||
"geo/internal/config"
|
||
"log"
|
||
)
|
||
|
||
type NewPlatformCollector struct {
|
||
*BaseCollector
|
||
}
|
||
|
||
func NewNewPlatformCollector(ctx context.Context, params *CollectParams, cfg *config.Config, logger *log.Logger) CollectorInterface {
|
||
collector := &NewPlatformCollector{
|
||
BaseCollector: NewBaseCollector(ctx, params, cfg, logger),
|
||
}
|
||
collector.LoginURL = "https://example.com/login"
|
||
collector.ChatURL = "https://example.com/chat"
|
||
return collector
|
||
}
|
||
|
||
// 实现 CheckLoginStatus、WaitLogin、AskQuestion 等方法
|
||
// ...
|
||
```
|
||
|
||
然后在 `interface.go` 中注册:
|
||
|
||
``go
|
||
var CollectorMap = map[string]*CollectorValue{
|
||
// ... 其他平台
|
||
"newplatform": {
|
||
Name: "新平台",
|
||
InitMethod: NewNewPlatformCollector,
|
||
Platform: "newplatform",
|
||
},
|
||
}
|
||
```
|
||
|
||
## 故障排查
|
||
|
||
### 1. 浏览器启动失败
|
||
|
||
- 检查 `ChromePath` 是否正确
|
||
- 确认Chrome版本是否兼容
|
||
- 查看日志文件了解详细错误
|
||
|
||
### 2. 找不到输入框或发送按钮
|
||
|
||
- 页面结构可能已更新
|
||
- 打开浏览器(Headless: false)查看实际DOM结构
|
||
- 更新对应的选择器
|
||
|
||
### 3. 登录状态失效
|
||
|
||
- 删除对应的Cookie文件
|
||
- 重新调用 `WaitLogin()` 登录
|
||
- 检查账号是否正常
|
||
|
||
### 4. 获取不到答案
|
||
|
||
- 增加超时时间
|
||
- 检查网络连接
|
||
- 查看页面是否有验证码或其他拦截
|
||
|
||
## 技术栈
|
||
|
||
- **go-rod**: Chrome DevTools Protocol的Go语言封装
|
||
- **Chrome/Chromium**: 浏览器引擎
|
||
- **Context**: Go上下文管理
|
||
- **JSON**: Cookie序列化
|
||
|
||
## 许可证
|
||
|
||
与项目主许可证保持一致。
|
||
|
||
# Collect 模块 - 单浏览器多Page架构
|
||
|
||
## 架构说明
|
||
|
||
本模块采用**单浏览器多Page模式**,服务启动时创建一个全局浏览器实例,并为每个平台打开一个常驻 Page。
|
||
|
||
### 核心特性
|
||
|
||
1. **单一浏览器**:所有平台共用同一个浏览器实例
|
||
2. **启动时预创建**:服务启动时立即创建浏览器并打开所有平台的页面
|
||
3. **Page 常驻**:每个平台的 Page 在整个服务生命周期内保持活跃
|
||
4. **强制有头模式**:便于调试和人工干预(如扫码登录)
|
||
5. **统一管理**:Browser 和 Page 都由 Manager 统一管理和关闭
|
||
|
||
## 使用示例
|
||
|
||
### 基本用法
|
||
|
||
``go
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"geo/internal/collect"
|
||
"geo/internal/config"
|
||
"github.com/gofiber/fiber/v2/log"
|
||
)
|
||
|
||
func main() {
|
||
cfg, _ := config.LoadConfig()
|
||
ctx := context.Background()
|
||
|
||
// 创建管理器(会自动:启动浏览器 + 打开所有平台页面)
|
||
manager := collect.NewCollectManager(ctx, cfg, log.DefaultLogger())
|
||
|
||
// ⚠️ 重要:确保程序退出时关闭所有资源
|
||
defer manager.Close()
|
||
|
||
// 每次调用都使用对应的常驻 Page
|
||
params := &collect.CollectParams{
|
||
RequestID: "req_001",
|
||
Platform: "wenxin",
|
||
KeyWords: []string{"AI", "人工智能"},
|
||
}
|
||
|
||
// 提问(使用 wenxin 的常驻 Page)
|
||
result, err := manager.AskQuestion("wenxin", params, "什么是人工智能?")
|
||
if err != nil {
|
||
log.Errorf("提问失败: %v", err)
|
||
return
|
||
}
|
||
|
||
log.Infof("答案长度: %d", len(result.Answer))
|
||
log.Infof("分享链接: %s", result.ShareLink)
|
||
|
||
// 可以切换到其他平台(使用对应的常驻 Page)
|
||
result2, _ := manager.AskQuestion("deepseek", params, "第二个问题")
|
||
}
|
||
```
|
||
|
||
### 登录测试示例
|
||
|
||
``go
|
||
func loginTest(manager *collect.CollectManager) {
|
||
params := &collect.CollectParams{
|
||
RequestID: "login_test_001",
|
||
Platform: "deepseek",
|
||
}
|
||
|
||
// 等待登录(使用 deepseek 的常驻 Page)
|
||
// 浏览器窗口已经打开,可以直接看到页面并进行扫码登录
|
||
success, msg := manager.WaitLogin("deepseek", params)
|
||
if !success {
|
||
log.Errorf("登录失败: %s", msg)
|
||
return
|
||
}
|
||
|
||
log.Infof("登录成功: %s", msg)
|
||
// Cookie 已自动保存,下次可以直接使用
|
||
}
|
||
```
|
||
|
||
### 并发操作示例
|
||
|
||
``go
|
||
func concurrentExample(manager *collect.CollectManager) {
|
||
// 可以安全地并发调用不同平台
|
||
go func() {
|
||
params := &collect.CollectParams{
|
||
RequestID: "req_001",
|
||
Platform: "wenxin",
|
||
}
|
||
result, _ := manager.AskQuestion("wenxin", params, "问题1")
|
||
log.Infof("文心一言回答: %s", result.Answer)
|
||
}()
|
||
|
||
go func() {
|
||
params := &collect.CollectParams{
|
||
RequestID: "req_002",
|
||
Platform: "deepseek",
|
||
}
|
||
result, _ := manager.AskQuestion("deepseek", params, "问题2")
|
||
log.Infof("DeepSeek回答: %s", result.Answer)
|
||
}()
|
||
}
|
||
```
|
||
|
||
## 架构细节
|
||
|
||
### 浏览器管理
|
||
|
||
- **数量**:全局只有一个浏览器实例
|
||
- **创建时机**:`NewCollectManager()` 调用时立即创建
|
||
- **存储方式**:`manager.browser` 字段
|
||
- **线程安全**:browser 只读访问,无竞态条件
|
||
- **生命周期**:从服务启动到 `manager.Close()` 调用
|
||
- **关闭方式**:调用 `manager.Close()` 关闭浏览器和所有 Page
|
||
|
||
### Page 管理
|
||
|
||
- **数量**:每个平台一个 Page(共 4 个:wenxin, deepseek, doubao, qianwen)
|
||
- **创建时机**:`NewCollectManager()` 调用时为所有平台打开页面
|
||
- **存储方式**:`map[string]*rod.Page`,key 为平台名称
|
||
- **线程安全**:使用 `sync.RWMutex` 保护并发访问
|
||
- **生命周期**:从服务启动到 `manager.Close()` 调用
|
||
- **特点**:Page 常驻,不会在操作后关闭
|
||
|
||
### 数据流
|
||
|
||
```
|
||
服务启动
|
||
↓
|
||
NewCollectManager()
|
||
↓
|
||
创建全局浏览器(1个)
|
||
↓
|
||
为每个平台打开 Page(4个)
|
||
├─ wenxin Page → https://yiyan.baidu.com/
|
||
├─ deepseek Page → https://chat.deepseek.com/
|
||
├─ doubao Page → https://www.doubao.com/chat/
|
||
└─ qianwen Page → https://tongyi.aliyun.com/qianwen/
|
||
↓
|
||
所有 Page 保持活跃(可看到浏览器窗口)
|
||
|
||
每次请求:
|
||
↓
|
||
AskQuestion(platform, ...)
|
||
↓
|
||
获取对应平台的常驻 Page
|
||
↓
|
||
执行操作(输入、点击等)
|
||
↓
|
||
返回结果(Page 保持活跃,不关闭)
|
||
|
||
服务关闭:
|
||
↓
|
||
manager.Close()
|
||
↓
|
||
关闭所有 Page
|
||
↓
|
||
关闭浏览器
|
||
```
|
||
|
||
### 数据存储
|
||
|
||
```
|
||
ChromeDataDir/
|
||
└── global/
|
||
└── main/ # 全局浏览器用户数据(所有平台共用)
|
||
|
||
CookiesDir/
|
||
├── wenxin/
|
||
│ └── wenxin.json # Cookie 文件(按平台隔离)
|
||
├── deepseek/
|
||
│ └── deepseek.json
|
||
└── doubao/
|
||
└── doubao.json
|
||
```
|
||
|
||
## 关键优势
|
||
|
||
### 1. 资源占用最小化
|
||
- ✅ 只启动一个浏览器进程
|
||
- ✅ 内存占用最低
|
||
- ✅ 系统资源消耗最少
|
||
|
||
### 2. 启动速度快
|
||
- ✅ 浏览器在服务启动时已就绪
|
||
- ✅ 所有平台页面已打开
|
||
- ✅ 无需等待页面加载
|
||
|
||
### 3. 调试友好
|
||
- ✅ 有头模式,可实时观察所有平台
|
||
- ✅ 支持人工干预(扫码、验证码等)
|
||
- ✅ 便于问题排查
|
||
|
||
### 4. 会话保持
|
||
- ✅ Page 常驻,登录状态持续有效
|
||
- ✅ Cookie 自动保存和加载
|
||
- ✅ 无需重复登录
|
||
|
||
## 注意事项
|
||
|
||
### ⚠️ 必须调用 Close()
|
||
|
||
``go
|
||
manager := collect.NewCollectManager(ctx, cfg, logger)
|
||
defer manager.Close() // 确保程序退出时关闭所有资源
|
||
```
|
||
|
||
如果不调用 `Close()`,浏览器进程会残留。
|
||
|
||
### ⚠️ 有头模式限制
|
||
|
||
- 所有浏览器都以有头模式运行
|
||
- 无法切换到无头模式
|
||
- 适合开发和调试环境
|
||
|
||
### ⚠️ 并发访问注意
|
||
|
||
虽然使用了 `sync.RWMutex` 保护 pages map,但 rod 的 Page 本身不是线程安全的。建议:
|
||
- 同一平台的请求串行执行
|
||
- 不同平台可以并发执行
|
||
- 避免在同一 Page 上同时执行多个操作
|
||
|
||
### ⚠️ Cookie 隔离
|
||
|
||
虽然浏览器是共用的,但 Cookie 文件按平台隔离存储,确保各平台的会话独立。
|
||
|
||
## 迁移指南
|
||
|
||
### 变化点
|
||
|
||
1. ✅ `NewCollectManager()` 会立即启动浏览器并打开所有平台页面
|
||
2. ✅ Page 是常驻的,不会在操作后关闭
|
||
3. ✅ `Collector.Close()` 不再关闭 Page(空实现)
|
||
4. ✅ `Headless` 参数被忽略,强制为 `false`
|
||
|
||
### 无需修改
|
||
|
||
- ❌ Manager 的创建方式不变
|
||
- ❌ 业务代码调用方式不变
|
||
- ❌ Collector 的业务逻辑无需修改
|
||
|
||
## 性能对比
|
||
|
||
| 指标 | 旧架构 | 新架构 |
|
||
|------|--------|--------|
|
||
| 浏览器进程数 | 4个(每平台1个) | 1个(全局共用) |
|
||
| 首次启动 | ~10秒 | ~5秒 |
|
||
| 后续请求 | ~0.1秒 | ~0.1秒 |
|
||
| 内存占用 | 高 | 低 |
|
||
| 并发能力 | 中 | 中(需注意Page线程安全) |
|
||
| 资源泄漏风险 | 低 | 低 |
|
||
|
||
## 最佳实践
|
||
|
||
1. **服务启动时初始化**
|
||
```go
|
||
func initService() {
|
||
manager = collect.NewCollectManager(ctx, cfg, logger)
|
||
}
|
||
```
|
||
|
||
2. **服务关闭时清理**
|
||
```go
|
||
func shutdownService() {
|
||
manager.Close()
|
||
}
|
||
```
|
||
|
||
3. **异常处理**
|
||
```go
|
||
result, err := manager.AskQuestion(platform, params, question)
|
||
if err != nil {
|
||
log.Errorf("操作失败: %v", err)
|
||
// Page 仍然可用,可以继续尝试
|
||
}
|
||
```
|
||
|
||
4. **监控建议**
|
||
- 监控浏览器进程数量(应该只有1个)
|
||
- 监控内存使用情况
|
||
- 记录每次操作的耗时
|
||
- 监控各平台 Page 的健康状态
|
||
|
||
## 架构图
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ CollectManager │
|
||
│ │
|
||
│ ┌───────────────────────────────┐ │
|
||
│ │ Global Browser (1个) │ │
|
||
│ │ Headless: false │ │
|
||
│ │ Window: 1920x1080 │ │
|
||
│ └───────────────────────────────┘ │
|
||
│ │ │
|
||
│ ├──────────────────┐ │
|
||
│ │ │ │
|
||
│ ┌───────▼───────┐ ┌──────▼──┐│
|
||
│ │ Wenxin Page │ │Deepseek ││
|
||
│ │ (常驻) │ │Page ││
|
||
│ └───────────────┘ └─────────┘│
|
||
│ │ │ │
|
||
│ ┌───────▼───────┐ ┌──────▼──┐│
|
||
│ │ Doubao Page │ │Qianwen ││
|
||
│ │ (常驻) │ │Page ││
|
||
│ └───────────────┘ └─────────┘│
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|