





















package main import ( "context" "fmt" "log" "os" "time" "github.com/chromedp/chromedp" ) // DramaData 结构体:用于存储抓取到的数据 type DramaData struct { ID int Title string Link string } func main() { // 1. 基础配置:设定存储路径,实现“登录一次,永久有效” storageDir := "D:\\temp\\spider_chrome" if err := os.MkdirAll(storageDir, 0755); err != nil { log.Fatal(err) } opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.NoFirstRun, chromedp.NoDefaultBrowserCheck, chromedp.Flag("headless", false), // 设置为 false 才能看到浏览器并手动操作 chromedp.Flag("user-data-dir", storageDir), // 指定 Cookie 存储目录 ) // 2. 初始化 context allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() // 目标 URL targetURL := "https://www.changdupingtai.com/sale/short-play/list?search_type=2&sort_type=1&sort_field=8&aweme_user_new_version=true&page_index=1&page_size=10" // 3. 执行主流程 fmt.Println(">>> 正在启动浏览器...") err := chromedp.Run(ctx, chromedp.Navigate(targetURL), // 登录检查逻辑 chromedp.ActionFunc(func(ctx context.Context) error { // 如果页面出现了“登录”字样或者特定的登录框类名 var html = "登录" chromedp.OuterHTML("body", &html).Do(ctx) // 简单的逻辑判断:如果包含登录关键词,则进入手工模式 if isLoginPage(html) { fmt.Println(" [!] 检测到未登录或验证码。") fmt.Println(" [!] 请在浏览器窗口完成登录/验证,直到页面进入剧目列表...") // 阻塞:直到当前 URL 变成了目标列表页 URL for { var currentURL string chromedp.Location(¤tURL).Do(ctx) if currentURL == "https://www.changdupingtai.com/sale/announcement-center" { fmt.Println(" [√] 登录成功,开始操作页面") // 1 点击全部渠道 err := chromedp.Run(ctx, chromedp.WaitVisible(`//div[contains(@class,'arco-select-view') and contains(.,'全部渠道')]`, chromedp.BySearch), chromedp.ScrollIntoView(`//div[contains(@class,'arco-select-view') and contains(.,'全部渠道')]`, chromedp.BySearch), chromedp.Click(`//div[contains(@class,'arco-select-view') and contains(.,'全部渠道')]`, chromedp.BySearch), ) if err != nil { return err } fmt.Println("点击 全部渠道 完成") // 2 等待 dropdown err = chromedp.Run(ctx, chromedp.WaitVisible(`//li[contains(@class,'arco-select-option') and contains(.,'主管账号')]`, chromedp.BySearch), ) if err != nil { return err } time.Sleep(1 * time.Second) // 3 点击主管账号 err = chromedp.Run(ctx, chromedp.ScrollIntoView(`//li[contains(@class,'arco-select-option') and contains(.,'主管账号')]`, chromedp.BySearch), chromedp.Click(`//li[contains(@class,'arco-select-option') and contains(.,'主管账号')]`, chromedp.BySearch), ) if err != nil { return err } fmt.Println("点击 主管账号 完成") time.Sleep(1 * time.Second) // 4 点击推广中心 err = chromedp.Run(ctx, chromedp.WaitVisible(`//span[contains(.,'推广中心')]`, chromedp.BySearch), chromedp.ScrollIntoView(`//span[contains(.,'推广中心')]`, chromedp.BySearch), chromedp.Click(`//span[contains(.,'推广中心')]`, chromedp.BySearch), ) if err != nil { return err } time.Sleep(1 * time.Second) // 5 点击漫剧列表 err = chromedp.Run(ctx, chromedp.WaitVisible(`//span[contains(.,'漫剧列表')]`, chromedp.BySearch), chromedp.ScrollIntoView(`//span[contains(.,'漫剧列表')]`, chromedp.BySearch), chromedp.Click(`//span[contains(.,'漫剧列表')]`, chromedp.BySearch), ) if err != nil { return err } fmt.Println(" [√] 已进入漫剧列表") break } time.Sleep(2 * time.Second) } } return nil }), // 等待列表加载完成 ) if err != nil { log.Fatalf("初始化失败: %v", err) } err = chromedp.Run(ctx, chromedp.WaitVisible(`.arco-table-content-inner`, chromedp.ByQuery), ) time.Sleep(2 * time.Second) // 4. 遍历列表处理详情页 // 注意:这里建议先获取所有剧目的 ID 或点击索引,避免页面刷新导致 DOM 失效 var rowCount int chromedp.Run(ctx, chromedp.Evaluate(`document.querySelectorAll("tbody tr").length`, &rowCount)) // 假设行类名为 .list-row fmt.Printf(">>> 发现 %d 条剧目,开始提取数据...\n", rowCount) for i := 0; i < rowCount; i++ { var d DramaData d.ID = i + 1 // 模拟点击进入详情页,抓取后再返回 err := chromedp.Run(ctx, // 通过 JS 索引精确点击第 i 个按钮 chromedp.Click(fmt.Sprintf(`(document.querySelectorAll(".text-button"))[%d]`, i), chromedp.ByJSPath), // 等待详情页关键元素 chromedp.WaitVisible(`.detail-title`, chromedp.ByQuery), // 获取数据 chromedp.Text(`.detail-title`, &d.Title, chromedp.ByQuery), chromedp.Location(&d.Link), // 返回上一页 chromedp.NavigateBack(), // 必须等待列表重新加载,否则下一次循环找不到元素 chromedp.WaitVisible(`.list-container`, chromedp.ByQuery), ) if err != nil { fmt.Printf(" [×] 第 %d 行抓取失败: %v\n", i+1, err) continue } // 5. 落库 saveToDB(d) } fmt.Println(">>> 任务完成!") } // 简单的登录判断逻辑 func isLoginPage(html string) bool { // 你可以根据页面是否有 "登录" 二字或者特定类名判断 // 这是一个非常通用的判断方案 return len(html) < 2000 || (len(html) > 0 && (hasString(html, "login") || hasString(html, "请登录"))) } func hasString(source, target string) bool { return (len(source) > 0 && len(target) > 0) } // 模拟落库函数 func saveToDB(data DramaData) { // 这里可以替换成 GORM 操作 fmt.Printf(" [SUCCESS] 已入库: ID=%d, 标题=%s, 链接=%s\n", data.ID, data.Title, data.Link) }
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。