惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
G
Google Developers Blog
B
Blog
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 叶小钗
The Cloudflare Blog
The Hacker News
The Hacker News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
雷峰网
雷峰网
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
T
Threat Research - Cisco Blogs
A
About on SuperTechFans
量子位
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
H
Help Net Security
Help Net Security
Help Net Security
P
Palo Alto Networks Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
W
WeLiveSecurity
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
Know Your Adversary
Know Your Adversary
Apple Machine Learning Research
Apple Machine Learning Research
Scott Helme
Scott Helme
N
News | PayPal Newsroom
AWS News Blog
AWS News Blog
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
腾讯CDC
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
TaoSecurity Blog
TaoSecurity Blog
GbyAI
GbyAI
Y
Y Combinator Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
D
Docker

博客园 - 雨V幕

遍历redis按照前缀给未设置过期时间的数据添加过期时间 使用rabbitmq 进行任务调度 使用trace进行排查网络瓶颈 使用vscode 调试 Python 使用power shell 拆分 csv文件 将大文件拆分成小文件。 使用postman 添加预处理验签。 go 使用pprof 进行问题排查 Mysql无主键删除重复数据的快速方法 解决mysql 事务死锁的方法 go在处理批量下载时候出现fatal error: runtime: out of memory AnalyticDB 创建db go 序列化反序列化之后时区信息丢失 clickhouse 进行建表期间的一些优化 kraots2.0 在windows 环境搭建开发环境 Sql Server使用函数获取拼音码 关于async 和await关键字 使用kubespray 一键部署 containerd 的安装和熟悉 VMware 配置双网卡实现上网和固定ip
使用chromedp 来做人工模拟操作爬取数据方法
雨V幕 · 2026-03-17 · via 博客园 - 雨V幕
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(&currentURL).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)
}