# 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 ││ │ └───────────────┘ └─────────┘│ └─────────────────────────────────────┘ ```