geoGo/internal/collect/deepseek.go

292 lines
6.9 KiB
Go
Raw 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.

package collect
import (
"context"
"fmt"
"geo/internal/config"
"log"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
// DeepseekCollector DeepSeek收集器
type DeepseekCollector struct {
*BaseCollector
}
// NewDeepseekCollector 创建DeepSeek收集器
func NewDeepseekCollector(ctx context.Context, params *CollectParams, cfg *config.Config, logger *log.Logger) CollectorInterface {
collector := &DeepseekCollector{
BaseCollector: NewBaseCollector(ctx, params, cfg, logger),
}
// 设置DeepSeek的URL
collector.LoginURL = "https://chat.deepseek.com/"
collector.ChatURL = "https://chat.deepseek.com/"
return collector
}
// CheckLoginStatus 检查登录状态
func (c *DeepseekCollector) CheckLoginStatus() bool {
currentURL := c.GetCurrentURL()
// 如果在首页或登录页面,可能未登录
if strings.Contains(currentURL, "chat.deepseek.com") {
// 检查是否有用户头像或登录标识
userAvatar, err := c.SafeElement(".user-avatar, [class*='avatar'], [class*='profile']")
if err == nil && userAvatar != nil {
return true
}
// 检查是否有聊天输入框(登录后才有)
inputBox, err := c.SafeElement("textarea, [contenteditable='true']")
if err == nil && inputBox != nil {
return true
}
}
return false
}
// WaitLogin 等待登录
func (c *DeepseekCollector) WaitLogin() (bool, string) {
c.LogInfo("开始等待DeepSeek登录...")
if err := c.SetupDriver(); err != nil {
return false, fmt.Sprintf("浏览器启动失败: %v", err)
}
defer c.Close()
// 访问聊天页面
c.Page.MustNavigate(c.ChatURL)
c.Sleep(3)
// 检查是否已登录
if c.CheckLoginStatus() {
c.SaveCookies()
c.LogInfo("已有登录状态")
return true, "already_logged_in"
}
c.LogInfo("未检测到登录状态,请登录账号...")
// 等待用户手动登录最多300秒
for i := 0; i < 300; i++ {
if c.CheckLoginStatus() {
c.SaveCookies()
c.LogInfo("登录成功")
return true, "login_success"
}
time.Sleep(1 * time.Second)
}
return false, "登录超时,请检查网络或账号状态"
}
// AskQuestion 提问并获取答案
func (c *DeepseekCollector) AskQuestion(question string) (string, error) {
c.LogInfo(fmt.Sprintf("开始向DeepSeek提问: %s", question))
// 初始化浏览器
if err := c.SetupDriver(); err != nil {
return "", fmt.Errorf("浏览器启动失败: %v", err)
}
defer c.Close()
// 初始化页面
if err := c.InitPage(); err != nil {
return "", fmt.Errorf("页面初始化失败请先调用WaitLogin登录: %v", err)
}
c.Sleep(3)
// 输入问题
if err := c.inputQuestion(question); err != nil {
return "", fmt.Errorf("输入问题失败: %v", err)
}
// 点击发送
if err := c.clickSendButton(); err != nil {
return "", fmt.Errorf("点击发送按钮失败: %v", err)
}
// 等待并获取答案
answer, err := c.waitForAnswer()
if err != nil {
return "", fmt.Errorf("获取答案失败: %v", err)
}
c.LogInfo(fmt.Sprintf("成功获取DeepSeek答案长度: %d 字符", len(answer)))
return answer, nil
}
// inputQuestion 输入问题
func (c *DeepseekCollector) inputQuestion(question string) error {
c.LogInfo("输入问题到DeepSeek...")
// DeepSeek的输入框选择器
inputSelectors := []string{
"textarea[placeholder*='输入']",
"textarea[placeholder*='问']",
"textarea",
"[contenteditable='true']",
".chat-input textarea",
"#message-input",
}
var inputBox *rod.Element
var err error
for _, selector := range inputSelectors {
inputBox, err = c.WaitForElementVisible(selector, 10)
if err == nil && inputBox != nil {
c.LogInfo(fmt.Sprintf("找到输入框: %s", selector))
break
}
}
if inputBox == nil {
return fmt.Errorf("未找到输入框")
}
// 点击获取焦点
if err := inputBox.Click(proto.InputMouseButtonLeft, 1); err != nil {
return fmt.Errorf("点击输入框失败: %v", err)
}
c.SleepMs(500)
// 清空输入框
if err := c.ClearInput(inputBox); err != nil {
c.LogInfo(fmt.Sprintf("清空输入框失败: %v", err))
}
c.SleepMs(300)
// 输入问题
if err := c.SetInputValue(inputBox, question); err != nil {
inputBox.Input(question)
}
c.LogInfo(fmt.Sprintf("问题已输入"))
c.SleepMs(1000)
return nil
}
// clickSendButton 点击发送按钮
func (c *DeepseekCollector) clickSendButton() error {
c.LogInfo("点击发送按钮...")
// 发送按钮选择器
sendSelectors := []string{
"button[class*='send']",
"button[class*='submit']",
".send-button",
".submit-button",
"button svg[path*='send']",
"[aria-label*='发送']",
"[aria-label*='Send']",
}
var sendBtn *rod.Element
var err error
for _, selector := range sendSelectors {
sendBtn, err = c.WaitForElementClickable(selector, 5)
if err == nil && sendBtn != nil {
c.LogInfo(fmt.Sprintf("找到发送按钮: %s", selector))
break
}
}
if sendBtn == nil {
// 尝试查找发送图标
sendBtn, err = c.Page.Element("button svg")
if err != nil {
return fmt.Errorf("未找到发送按钮")
}
}
c.SleepMs(500)
// 点击发送按钮
if err := c.JSClick(sendBtn); err != nil {
return fmt.Errorf("点击发送按钮失败: %v", err)
}
c.LogInfo("已点击发送按钮")
c.SleepMs(2000)
return nil
}
// waitForAnswer 等待并获取答案
func (c *DeepseekCollector) waitForAnswer() (string, error) {
c.LogInfo("等待DeepSeek回答...")
timeout := 120 // 最大等待时间(秒)
startTime := time.Now()
lastAnswerLength := 0
for time.Since(startTime).Seconds() < float64(timeout) {
// 查找答案区域
answerSelectors := []string{
".message-content",
".response-content",
"[class*='assistant'] [class*='content']",
"[class*='ai'] [class*='message']",
".chat-message.ai",
}
for _, selector := range answerSelectors {
answerElements, err := c.Page.Elements(selector)
if err == nil && len(answerElements) > 0 {
// 获取最后一个答案元素
lastAnswer := answerElements[len(answerElements)-1]
visible, _ := lastAnswer.Visible()
if visible {
text, err := lastAnswer.Text()
if err == nil && len(strings.TrimSpace(text)) > 0 {
// 检查是否正在生成
isGenerating := strings.Contains(text, "正在") ||
strings.Contains(text, "思考") ||
strings.Contains(text, "generating")
if !isGenerating {
// 检查答案是否还在增长
currentLength := len(text)
if currentLength == lastAnswerLength && currentLength > 10 {
// 答案不再增长,认为已完成
c.LogInfo("获取到完整答案")
return strings.TrimSpace(text), nil
}
lastAnswerLength = currentLength
}
}
}
}
}
c.SleepMs(1500)
}
return "", fmt.Errorf("等待答案超时")
}
// SafeElement 安全地获取元素
func (c *DeepseekCollector) SafeElement(selector string) (*rod.Element, error) {
exists, _, err := c.Page.Has(selector)
if err != nil {
return nil, err
}
if !exists {
return nil, nil
}
return c.Page.Element(selector)
}