package services import ( "context" "encoding/json" "errors" "fmt" "go-account-register/libs" "go-account-register/models" "go-account-register/utils" "log" "math/rand" "os" "strconv" "strings" "time" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/cdp" "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/proto" ) type TwitterService struct{} // 登录 func (*TwitterService) Login(id int) *libs.ErrorInfo { UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(id)) obj := Browser.GetBrowser(id, &libs.Fingerprint{ Proxy: user.Proxy, }) browser := obj.Browser page := obj.Page err3 := rod.Try(func() { // 创建 Cookie 对象 // cookies := user.Cookies // log.Println("获取cookie", cookies) // b, _ := json.MarshalIndent(cookies, "", " ") // log.Println(string(b)) // var token string // for _, v := range user.Cookies { // if v.Name == "auth_token" { // token = v.Value // } // } // log.Println("当前账号Token", token) cookies := []*proto.NetworkCookieParam{ { Name: "auth_token", Value: user.Token, Domain: ".x.com", Path: "/", Expires: proto.TimeSinceEpoch(time.Now().Add(24 * time.Hour).Unix()), // 过期时间 HTTPOnly: true, // 禁止 JS 访问 Secure: true, // 仅 HTTPS 传输 }, } log.Println("Cookie", cookies[0].Value) // 全局设置 browser.SetCookies(cookies) page.MustNavigate("https://x.com/home").MustWaitLoad() page.MustWaitNavigation() }) if e, ok := err3.(*cdp.Error); ok { if e.Code == -32000 && e.Message == "Inspected target navigated or closed" { Browser.CancelBrowser(id) return libs.ErrorCode["LoginFailed"] } } time.Sleep(3 * time.Second) url := page.MustEval("() => window.location.href").String() if url == "https://x.com/account/access" { Browser.CancelBrowser(id) return libs.ErrorCode["AccountLocked"] } log.Println(url) err1 := rod.Try(func() { page.Timeout(5 * time.Second).MustElement(`input[autocomplete="username"]`) }) if err1 == nil { Browser.CancelBrowser(id) return libs.ErrorCode["LoginFailed"] } // 是否检测到侧边栏 err2 := rod.Try(func() { page.Timeout(5 * time.Second).MustElement(`a[data-testid="AppTabBar_Profile_Link"]`) }) if err2 != nil { Browser.CancelBrowser(id) return libs.ErrorCode["LoginFailed"] } // 是否检测到帖子列表 err := rod.Try(func() { page.Timeout(5 * time.Second).MustElement(`article[data-testid="tweet"]`) }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("请求空闲等待超时!") // 关闭浏览器 Browser.CancelBrowser(id) return libs.ErrorCode["NetworkTimeout"] } if err != nil { fmt.Println(err) } getAccountInfo(page, int64(id)) getRegisterInfo(page, int64(id), user.Password) return libs.ErrorCode["LoginSuccessful"] } // 获取账号信息 func getAccountInfo(page *rod.Page, id int64) { profileButton := page.Timeout(10 * time.Second).MustElement(`a[data-testid="AppTabBar_Profile_Link"]`) profileButton.MustClick() // 等待新页面加载完成 page.MustWaitLoad() res := getApiResponse(page, "/UserByScreenName") var dataBody map[string]interface{} json.Unmarshal([]byte(res), &dataBody) outerMap := dataBody["data"].(map[string]interface{}) userMap := outerMap["user"].(map[string]interface{}) resultMap := userMap["result"].(map[string]interface{}) // 获取头像 avatarMap := resultMap["avatar"].(map[string]interface{}) avatarImageURL := avatarMap["image_url"].(string) legacyMap := resultMap["legacy"].(map[string]interface{}) // 获取背景图 backdropImgUrl := legacyMap["profile_banner_url"] var backdropUrl string if backdropImgUrl == nil { backdropUrl = "" } else { backdropUrl = legacyMap["profile_banner_url"].(string) } // 获取昵称 coreMap := resultMap["core"].(map[string]interface{}) nickname := coreMap["name"].(string) screenName := coreMap["screen_name"].(string) // 获取自我介绍 bio := legacyMap["description"].(string) // 获取自己设置的地理位置 locationObj := resultMap["location"].(map[string]interface{}) location := locationObj["location"].(string) // 获取粉丝数量 followersCount := legacyMap["followers_count"].(float64) // 获取关注数量 friendsCount := legacyMap["friends_count"].(float64) // 获取发布的帖子数量 mediaCount := legacyMap["media_count"].(float64) log.Println("头像地址", avatarImageURL) log.Println("背景图地址", backdropUrl) log.Println("昵称", nickname) log.Println("用户名", screenName) log.Println("自我介绍", bio) log.Println("地理位置", location) log.Println("粉丝数量", int64(followersCount)) log.Println("关注数量", int64(friendsCount)) log.Println("发布的帖子数量", int64(mediaCount)) userService := InitTwitterAccountService() userService.Update(id, &models.TwitterAccount{ Uid: screenName, }) } func getRegisterInfo(page *rod.Page, id int64, password string) { // 进入账号信息页面 page.MustNavigate("https://x.com/settings/your_twitter_data/account").MustWaitLoad() page.MustWaitNavigation() page.MustWaitLoad() // DOM 加载完成 page.MustWaitIdle() // 基本静止(减少早期误判) page.MustActivate() // 前置 // 判断当前是否需要输入密码 inpurPassword, err := page.Timeout(5 * time.Second).Element(`input[name="current_password"]`) if err == nil { if is, err1 := inpurPassword.Visible(); err1 == nil && is { inpurPassword.MustInput(password) } ConfirmButton := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-ddtstp.r-1xpp3t0.r-2yi16.r-1qi8awa.r-3pj75a.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`) ConfirmButton.MustClick() } time.Sleep(5 * time.Second) // 获取创建时间 dateElement := page.Timeout(10 * time.Second).MustElement(`#react-root > div > div > div.css-175oi2r.r-1f2l425.r-13qz1uu.r-417010.r-18u37iz > main > div > div > div > section:nth-child(2) > div.css-175oi2r.r-qocrb3.r-14lw9ot.r-1h0z5md.r-1jx8gzb.r-f8sm7e.r-13qz1uu.r-1ye8kvj > div > div:nth-child(6) > div:nth-child(2)`) createdAt, _ := dateElement.Text() // 获取注册IP ipElement := page.Timeout(10 * time.Second).MustElement(`#react-root > div > div > div.css-175oi2r.r-1f2l425.r-13qz1uu.r-417010.r-18u37iz > main > div > div > div > section:nth-child(2) > div.css-175oi2r.r-qocrb3.r-14lw9ot.r-1h0z5md.r-1jx8gzb.r-f8sm7e.r-13qz1uu.r-1ye8kvj > div > div:nth-child(6) > div:nth-child(3)`) ipAddr, _ := ipElement.Text() // 分离IP地址和地区 var ip, region string // ipAddr 格式: "68.132.165.85 (United States)" if strings.Contains(ipAddr, "(") { parts := strings.Split(ipAddr, " (") ip = strings.TrimSpace(parts[0]) if len(parts) > 1 { region = strings.TrimRight(parts[1], ")") } } else { ip = ipAddr } log.Println("注册IP:", ip) log.Println("注册地区:", region) log.Println("创建时间:", createdAt) userService := InitTwitterAccountService() userService.Update(id, &models.TwitterAccount{ RegisterIp: ip, RegisterDate: createdAt, RegisterAddress: region, }) } // 退出登录 func (*TwitterService) Logout(id int) { Browser.CancelBrowser(id) } // 注册账号 func (bit *TwitterService) Register() *libs.ErrorInfo { // 随机获取代理 proxyService := InitProxyService() proxy := proxyService.GetInfo() // 获取一个邮箱 emailService := InitEmailService() emailInfo := emailService.GetEmailOne() log.Println("当前邮箱信息", emailInfo) obj := Browser.GetNewBrowser(&libs.Fingerprint{}) page := obj.Page page.MustNavigate("https://x.com/").MustWaitLoad() // 点击创建账号 createAccountA := page.Timeout(5 * time.Second).MustElement(`a[data-testid="signupButton"]`) createAccountA.MustClick() // // 点击创建账号 // createAccount := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-ywje51.r-184id4b.r-13qz1uu.r-2yi16.r-1qi8awa.r-3pj75a.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`) // createAccount.MustClick() // 判断当前是否是邮箱注册 // _, err := page.Element(`input[name="email"]`) // if err != nil { switchButton := page.Timeout(5 * time.Second).MustElement(`button.css-146c3p1.r-bcqeeo.r-qvutc0.r-37j5jr.r-1ff274t.r-a023e6.r-rjixqe.r-16dba41`) switchButton.MustClick() // } // 获取邮箱输入框 inputEmail := page.Timeout(5 * time.Second).MustElement(`input[name="email"]`) inputEmail.MustFocus() inputEmail.MustInput(emailInfo.Email) // 获取名称输入框 inputName := page.Timeout(5 * time.Second).MustElement(`input[name="name"]`) inputName.MustFocus() nameStr := emailInfo.Name if emailInfo.Name == "" { nameStr = utils.RandString(12) } inputName.MustInput(nameStr) // 随机生成生日 dateParams := utils.RandDate(18, 80) dateSelects, _ := page.Timeout(5 * time.Second).Elements(`select.r-30o5oe.r-1niwhzg.r-17gur6a.r-1yadl64.r-18jsvk2.r-1loqt21.r-1inkyih.r-rjixqe.r-crgep1.r-1wzrnnt.r-1ny4l3l.r-t60dpp.r-xd6kpl.r-is05cd.r-ttdzmv`) log.Println(dateSelects) fmt.Println(strconv.Itoa(dateParams.Month)) fmt.Println(strconv.Itoa(dateParams.Day)) fmt.Println(strconv.Itoa(dateParams.Year)) time.Sleep(2 * time.Second) // 月份 dateSelects[0].WaitVisible() opts, _ := dateSelects[0].Elements("option") for i, opt := range opts { txt, _ := opt.Text() val, _ := opt.Attribute("value") log.Printf("option[%d] text=%s value=%s\n", i, txt, *val) if i != 0 && i == dateParams.Month { dateSelects[0].MustSelect(txt) } } // 年份 dateSelects[2].WaitVisible() dateSelects[2].MustSelect(strconv.Itoa(dateParams.Year)) time.Sleep(2 * time.Second) // 日期 dateSelects[1].WaitVisible() dateSelects[1].MustSelect(strconv.Itoa(dateParams.Day)) // dateSelects[0].MustClick() // monthOption := dateSelects[0].MustElement(fmt.Sprintf(`option[value="%d"]`, dateParams.Month)) // monthOption.MustClick() time.Sleep(2 * time.Second) // 点击下一步 ocfSignupNextLink := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfSignupNextLink"]`) ocfSignupNextLink.MustClick() // 判断是否需要验证码 time.Sleep(4 * time.Second) _, err1 := page.Timeout(5 * time.Second).Element(`input[name="verfication_code"]`) if err1 != nil { // res := getApiResponse(page, "") } //TODO 对接获取邮箱信息接口 time.Sleep(10 * time.Second) codeValue := LoopGetEmailCode("pop.dragonsmail.com:993", emailInfo.Email, emailInfo.Password) if codeValue == "0" { emailService.Update(emailInfo.Email, models.Email{ Status: 0, }) return libs.ErrorCode["MailboxUnavailable"] } // 获取邮箱验证码 log.Println("获取当前验证码", codeValue) codeInput1, _ := page.Timeout(5 * time.Second).Element(`input[name="verfication_code"]`) codeInput1.MustInput(codeValue) time.Sleep(2 * time.Second) // 点击下一步 codeNextButton := page.Timeout(5 * time.Second).MustElement(`button.css-175oi2r.r-sdzlij.r-1phboty.r-rs99b7.r-lrvibr.r-19yznuf.r-64el8z.r-1fkl15p.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`) codeNextButton.MustClick() // // 点击验证按钮 // verifyButton := page.Timeout(5 * time.Second).MustElement(`button[data-theme="home.verifyButton"]`) // verifyButton.MustClick() // 输入密码 passwordInput, err2 := page.Timeout(5 * time.Second).Element(`input[name="password"]`) if err2 != nil { // res := getApiResponse(page, "") } passwordInput.MustInput(emailInfo.Password) time.Sleep(5 * time.Second) // 点击下一步 loginButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="LoginForm_Login_Button"]`) loginButton.MustClick() time.Sleep(5 * time.Second) page.MustNavigate("https://x.com").MustWaitLoad() time.Sleep(10 * time.Second) // 获取cookie cookieArr := GetPageCookie(page, "https://x.com") // 创建用户 twitterAccountService := InitTwitterAccountService() twitterAccountService.Create(&models.TwitterAccount{ Name: nameStr, Email: emailInfo.Email, Password: emailInfo.Password, Birthday: strconv.Itoa(dateParams.Year) + "-" + strconv.Itoa(dateParams.Month) + "-" + strconv.Itoa(dateParams.Day), Cookies: cookieArr, Status: "注册成功", LoginStatus: "登录成功", Proxy: func() string { if proxy != nil && proxy.Type != "" { return proxy.Type + "://" + proxy.Proxy } return "" }(), }) emailService.Update(emailInfo.Email, models.Email{ UseStatus: 1, }) // 设置fa2 page.MustNavigate("https://x.com/settings/account/login_verification").MustWaitLoad() // 点击fa2按钮 inputCheckbox := page.Timeout(5 * time.Second).MustElement(`input[aria-describedby="CHECKBOX_2_LABEL"]`) inputCheckbox.MustClick() // 等待2秒 time.Sleep(2 * time.Second) // 设置密码 inpurPassword := page.Timeout(5 * time.Second).MustElement(`input[name="password"]`) inpurPassword.MustInput(emailInfo.Password) // 点击确认 faConfirm := page.Timeout(5 * time.Second).MustElement(`button[data-testid="LoginForm_Login_Button"]`) faConfirm.MustClick() // 等待2秒 time.Sleep(2 * time.Second) ActionListNextButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ActionListNextButton"]`) ActionListNextButton.MustClick() // 点击无法扫描二维码 queryCode := page.Timeout(5 * time.Second).MustElement("div.css-175oi2r.r-1awozwy.r-w7s2jr>div.css-175oi2r.r-95jzfe>div.css-146c3p1.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41>button.css-1jxf684.r-bcqeeo.r-qvutc0.r-poiln3.r-fdjqy7") queryCode.MustClick() fa2Code := page.Timeout(5 * time.Second).MustElement(`div.css-146c3p1.r-bcqeeo.r-qvutc0.r-37j5jr.r-1blvdjr.r-vrz42v.r-b88u0q.r-x572qd.r-ywje51.r-1oqcu8e.r-q4m81j.r-13qz1uu`) fa2CodeText := fa2Code.MustText() log.Println("当前账号的二步验证码,", fa2CodeText) ocfShowCodeNextLink := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfShowCodeNextLink"]`) ocfShowCodeNextLink.MustClick() codeText, _ := utils.GetFA2Code(fa2CodeText) ocfEnterTextTextInput := page.Timeout(5 * time.Second).MustElement(`input[data-testid="ocfEnterTextTextInput"]`) ocfEnterTextTextInput.MustInput(codeText) ocfEnterTextNextButton := page.Timeout(5 * time.Second).MustElement(`button[data-testid="ocfEnterTextNextButton"]`) ocfEnterTextNextButton.MustClick() // 删除浏览器 return libs.ErrorCode["RegisterSuccessful"] } // 发推 func (*TwitterService) SendPost(accountId int, Content string, Img []string) *libs.ErrorInfo { // 获取账号状态 UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(accountId)) if user.LoginStatus != "登录成功" { return libs.ErrorCode["AccountHasBeenTakenOffline"] } // 获取实例 obj := Browser.GetBrowser(accountId, &libs.Fingerprint{ Proxy: user.Proxy, }) page := obj.Page // 跳转到首页 page.MustNavigate("https://x.com/home").MustWaitLoad() err := rod.Try(func() { // 获取内容输入框 textInput := page.Timeout(10 * time.Second).MustElement(`div.notranslate.public-DraftEditor-content`) textInput.MustInput(Content) // 检测字符串是否包含标签 if strings.Contains(Content, "#") { _, err := page.Timeout(5 * time.Second).Element(`div.css-175oi2r.r-14lw9ot.r-z2wwpe.r-1upvrn0.r-h3f8nf.r-11yh6sk.r-1rnoaur`) time.Sleep(2 * time.Second) if err == nil { tagBuuton := page.Timeout(5 * time.Second).MustElement(`div.css-175oi2r.r-14lw9ot.r-z2wwpe.r-1upvrn0.r-h3f8nf.r-11yh6sk.r-1rnoaur div[data-testid="typeaheadResult"] button.css-175oi2r.r-x572qd.r-6dt33c.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l`) tagBuuton.MustClick() time.Sleep(2 * time.Second) } } // 循环保存图片到本地 if len(Img) > 0 { var file libs.File for _, v := range Img { tempFile, _ := file.DownloadImage(v) // 结束时删除临时文件 defer os.Remove(tempFile) page.MustElement(`input[data-testid="fileInput"]`).MustSetFiles(tempFile) time.Sleep(1 * time.Second) } time.Sleep(5 * time.Second) } }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("请求空闲等待超时!") return libs.ErrorCode["SendPostFailed"] } code := postButton(page) if code == 200 { return libs.ErrorCode["SendPostSuccessful"] } return libs.ErrorCode["SendPostFailed"] } // 发推按钮点击之后监听请求 func postButton(page *rod.Page) int { // 定义一个通道接收消息 router := page.HijackRequests() defer router.MustStop() ch := make(chan int, 1) router.MustAdd("*/CreateTweet*", func(ctx *rod.Hijack) { ctx.ContinueRequest(&proto.FetchContinueRequest{}) ch <- ctx.Response.Payload().ResponseCode }) go router.Run() // 点击发布按钮 postButton := page.Timeout(10 * time.Second).MustElement(`button[data-testid="tweetButtonInline"]`) postButton.MustClick() select { case res := <-ch: return res case <-time.After(30 * time.Second): log.Println("等待上传响应超时") return 500 } } // 搜索 func (*TwitterService) Search(accountId int, keyword string, twitterId string) *libs.ErrorInfo { // 获取账号状态 UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(accountId)) if user.LoginStatus != "登录成功" { return libs.ErrorCode["AccountHasBeenTakenOffline"] } // 获取实例 obj := Browser.GetBrowser(accountId, &libs.Fingerprint{ Proxy: user.Proxy, }) page := obj.Page // 跳转到首页 page.MustNavigate("https://x.com/home").MustWaitLoad() err2 := rod.Try(func() { forYou := page.Timeout(10 * time.Second).MustElement(`div[data-testid="ScrollSnap-List"] div.css-175oi2r.r-14tvyh0.r-cpa5s6.r-16y2uox:nth-child(1)`) forYou.MustClick() }) if err2 != nil { } page.MustWaitNavigation() // 是否检测到帖子列表 err := rod.Try(func() { page.Timeout(5 * time.Second).MustElement(`article[data-testid="tweet"]`) }) if errors.Is(err, context.DeadlineExceeded) { fmt.Println("请求空闲等待超时!") // 关闭浏览器 Browser.CancelBrowser(accountId) return libs.ErrorCode["NetworkTimeout"] } page.MustNavigate("https://x.com/explore").MustWaitLoad() searchInput := page.Timeout(10 * time.Second).MustElement(`input[data-testid="SearchBox_Search_Input"]`) // 聚焦 searchInput.Focus() searchInput.MustInput(keyword) searchCode := waitSearch(page) if searchCode != 200 { return libs.ErrorCode["SearchFailed"] } peopleButton := page.Timeout(10 * time.Second).MustElement(`div[data-testid="ScrollSnap-List"] div.css-175oi2r.r-14tvyh0.r-cpa5s6.r-16y2uox:nth-child(3)`) peopleButton.MustClick() retry, err1 := page.Timeout(5 * time.Second).Element(`div.css-175oi2r.r-1awozwy.r-16y2uox.r-1777fci.r-dd0y9b.r-3o4zer.r-f8sm7e.r-13qz1uu.r-1ye8kvj`) if retry != nil || err1 == nil { return libs.ErrorCode["SearchFailed"] } var isSearch bool = true var searchCount int = 0 var activeUser *rod.Element = nil var nameArr []string = []string{} var nameBool map[string]bool = make(map[string]bool) for isSearch && searchCount < 10 { searchCount++ // 获取视口内的所有元素 ids, err2 := getVisibleElements(page, `div.css-175oi2r.r-f8sm7e.r-13qz1uu.r-1ye8kvj div.css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41.r-18u37iz.r-1wvb978`) if err2 != nil || len(ids) <= 0 { return libs.ErrorCode["SearchFailed"] } for _, v := range ids { text := v.MustText() if !nameBool[text] { nameBool[text] = true nameArr = append(nameArr, text) } if text == "@"+twitterId { activeUser = v break } } if activeUser != nil { isSearch = false log.Println("循环:") log.Println(searchCount) log.Println(len(nameArr)) log.Println("找到用户") } else { log.Println("开始滚动") scrollElementLocationTop(ids[len(ids)-1]) time.Sleep(5 * time.Second) } } if searchCount >= 10 { return libs.ErrorCode["SearchFailed"] } time.Sleep(1 * time.Second) activeUser.MustClick() // 等待5-8秒 s := 5 + rand.Intn(3) time.Sleep(time.Duration(s) * time.Second) posts := page.Timeout(10 * time.Second).MustElements(`div.css-175oi2r.r-j5o65s.r-qklmqi.r-1adg3ll.r-1ny4l3l`) var postArr []*rod.Element for _, post := range posts { aid := post.Timeout(10 * time.Second).MustElement(`div.css-146c3p1.r-dnmrzs.r-1udh08x.r-1udbk01.r-3s2u2q.r-bcqeeo.r-1ttztb7.r-qvutc0.r-37j5jr.r-a023e6.r-rjixqe.r-16dba41.r-18u37iz.r-1wvb978`) text := aid.MustText() if text == "@"+twitterId { postArr = append(postArr, post) } } if len(postArr) <= 0 { return libs.ErrorCode["SearchSuccessful"] } // 随机选择一个 activePost := postArr[rand.Intn(len(postArr))] activePost.MustScrollIntoView() // 将元素滑动到页面中间 tweetText := activePost.Timeout(10 * time.Second).MustElement(`div[data-testid="tweetText"]`) // 禁用链接点击 _, err3 := page.Evaluate(&rod.EvalOptions{ JS: `(selector) => { const el = document.querySelector(selector); const links = el.querySelectorAll('a'); links.forEach(a => { a.style.pointerEvents = 'none'; }); }`, JSArgs: []interface{}{`div[data-testid="tweetText"]`}, }) if err3 != nil { } tweetText.MustClick() // 恢复链接的点击行为(如果需要) _, _ = page.Evaluate(&rod.EvalOptions{ JS: `(selector) => { const el = document.querySelector(selector); const links = el.querySelectorAll('a'); links.forEach(a => { a.style.pointerEvents = ''; }); }`, JSArgs: []interface{}{`div[data-testid="tweetText"]`}, }) // 等待15-20秒 s2 := 15 + rand.Intn(10) time.Sleep(time.Duration(s2) * time.Second) // 返回上一页 // 模拟点击后退按钮 page.MustNavigateBack() // 等待新页面加载完成 page.MustWaitLoad() // 等待5-8秒 time.Sleep(time.Duration(s) * time.Second) return libs.ErrorCode["SearchSuccessful"] } // 获取视口内可见的指定元素 func getVisibleElements(page *rod.Page, selector string) ([]*rod.Element, error) { // 1. 获取所有候选元素 elements, err := page.Elements(selector) if err != nil { return nil, err } // 2. 过滤出视口内可见的元素 var visibleElements []*rod.Element for _, el := range elements { isVisible, err := el.Eval(`function() { const rect = this.getBoundingClientRect(); const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth; // 判断元素是否至少部分在视口内 return ( rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0 ); }`) if err == nil && isVisible.Value.Bool() { visibleElements = append(visibleElements, el) } } return visibleElements, nil } // 将指定元素滑动到顶部 func scrollElementLocationTop(el *rod.Element) { // 执行JS:滚动到视口顶部(平滑滚动) el.MustEval(`function() { this.scrollIntoView({ behavior: "smooth", block: "start" // 顶部对齐 }); }`) // 等待滚动完成 // time.Sleep(2 * time.Second) } // 搜索按钮点击之后监听请求 func waitSearch(page *rod.Page) int { // 定义一个通道接收消息 router := page.HijackRequests() defer router.MustStop() ch := make(chan int, 1) router.MustAdd("*/SearchTimeline*", func(ctx *rod.Hijack) { ctx.ContinueRequest(&proto.FetchContinueRequest{}) ch <- ctx.Response.Payload().ResponseCode }) go router.Run() // 点击搜索按钮 searchInput := page.Timeout(10 * time.Second).MustElement(`input[data-testid="SearchBox_Search_Input"]`) searchInput.MustKeyActions().Press(input.Enter).MustDo() select { case res := <-ch: return res case <-time.After(30 * time.Second): log.Println("等待上传响应超时") return 500 } } // 获取指定接口的响应参数 func getApiResponse(page *rod.Page, url string) string { // 定义一个通道接收消息 ch := make(chan string) go page.EachEvent(func(e *proto.PageLoadEventFired) { log.Println("1234567890") }, func(e *proto.NetworkResponseReceived) { if strings.Contains(e.Response.URL, url) { reply, err := (proto.NetworkGetResponseBody{RequestID: e.RequestID}).Call(page) if err != nil { fmt.Println(err) } ch <- reply.Body // fmt.Println(reply.Body) } })() select { case res := <-ch: return res case <-time.After(30 * time.Second): log.Println("等待上传响应超时") return "" } } func (*TwitterService) LoopViewPost(id int) *libs.ErrorInfo { UserService := InitTwitterAccountService() user := UserService.GetInfo(int64(id)) obj := Browser.GetBrowser(id, &libs.Fingerprint{ Proxy: user.Proxy, }) page := obj.Page // 获取推文列表 postList := InitTwitterPostService().GetAll() if len(postList) <= 0 { return libs.ErrorCode["NoPostsToView"] } for _, v := range postList { page.MustNavigate(v.Link).MustWaitLoad() page.MustWaitNavigation() page.MustWaitLoad() // DOM 加载完成 page.MustWaitIdle() // 基本静止(减少早期误判) page.MustActivate() // 前置 // 等待5-10秒 s := 5 + rand.Intn(5) time.Sleep(time.Duration(s) * time.Second) log.Println("浏览推文链接:", v.Link) if v.ContentLink != "" { page.MustNavigate(v.ContentLink).MustWaitLoad() page.MustWaitNavigation() page.MustWaitLoad() // DOM 加载完成 page.MustWaitIdle() // 基本静止(减少早期误判) page.MustActivate() // 前置 // 等待5-10秒 s2 := 5 + rand.Intn(5) time.Sleep(time.Duration(s2) * time.Second) log.Println("浏览推文内容链接:", v.ContentLink) } } return libs.ErrorCode["ViewSuccessful"] } // 导出 Twitter 账号数据到 Excel func (s *TwitterService) ExportAccounts() (code int, msg string, files []string) { result, err := ExportTwitterAccounts() if err != nil { return 500, "导出失败", nil } return 200, "导出成功", result }