codecamp

Colly 实战:抓取 Factbase 演讲全文并保存 JSON

从 API 列表 → 逐条详情 → 本地 JSON 文件,一条命令完成 Factbase 演讲全文抓取。零基础也能 5 分钟跑通!

一、整体思路(先背下来)

  1. 列表收集器:调 API 分页拿 slug + 日期
  2. 详情收集器:根据 slug/transcript/xxx 页面
  3. 上下文传递:把日期/文件名随请求一起带过去
  4. 结果输出:每条演讲保存为一个 <日期>_<slug>.json

二、精简中文代码(30 行)

package main


import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "strconv"


    "github.com/gocolly/colly/v2"
)


// 演讲全文结构
type Speech struct {
    Speaker string `json:"speaker"`
    Text    string `json:"text"`
}


func main() {
    const (
        searchAPI   = "https://factba.se/json/json-transcript.php?q=&f=&dt=&p="
        transcript  = "https://factba.se/transcript/"
    )


    // 1. 列表收集器:只访问 API
    listC := colly.NewCollector(colly.AllowedDomains("factba.se"))


    // 2. 详情收集器:爬详情页
    detailC := listC.Clone()


    // 3. 解析详情页 → 保存 JSON
    detailC.OnHTML("body", func(e *colly.HTMLElement) {
        var speech []Speech
        e.ForEach(".topic-media-row", func(_ int, el *colly.HTMLElement) {
            speech = append(speech, Speech{
                Speaker: el.ChildText(".speaker-label"),
                Text:    el.ChildText(".transcript-text-block"),
            })
        })


        date := e.Request.Ctx.Get("date")
        slug := e.Request.Ctx.Get("slug")
        file := colly.SanitizeFileName(date + "_" + slug + ".json")


        data, _ := json.MarshalIndent(speech, "", "  ")
        _ = ioutil.WriteFile(file, data, 0644)
        fmt.Printf("✅ 已保存 %s 共 %d 段\n", file, len(speech))
    })


    // 4. 解析 API → 触发详情
    stop := false
    listC.OnResponse(func(r *colly.Response) {
        type Resp struct {
            Data []struct {
                Slug string `json:"slug"`
                Date string `json:"date"`
            } `json:"data"`
        }
        var resp Resp
        _ = json.Unmarshal(r.Body, &resp)


        if len(resp.Data) == 0 {
            stop = true
            return
        }


        for _, d := range resp.Data {
            u := transcript + d.Slug
            ctx := colly.NewContext()
            ctx.Put("date", d.Date)
            ctx.Put("slug", d.Slug)
            detailC.Request("GET", u, nil, ctx, nil)
        }
    })


    // 5. 从第 1 页开始,最多 999 页
    for i := 1; i < 1000 && !stop; i++ {
        _ = listC.Visit(searchAPI + strconv.Itoa(i))
    }
}

三、3 步跑通

步骤 命令 说明
① 安装 go mod init factbase && go get github.com/gocolly/colly/v2 一键拉库
② 保存 复制上方代码 → main.go 零配置
③ 运行 go run main.go 终端实时显示保存进度

四、结果示例

本地生成文件:

2023-10-01_trump-ohio-rally.json
2023-09-30_biden-press-briefing.json
...

打开任意 JSON:

[
  {
    "speaker": "Donald Trump",
    "text": "We are going to make America great again..."
  },
  {
    "speaker": "Reporter",
    "text": "Mr. President, what about..."
  }
]

五、常见问题速查

症状 原因 解决
0 条数据 API 无返回 检查网络 / 换代理
文件中文乱码 Windows 默认编码 用 VS Code 打开即可
403 被拦截 缺 UA 加 colly.UserAgent("Mozilla/5.0...")

六、1 分钟扩展

需求 改动 1 行
只爬前 10 页 i < 11
存 CSV 把 json.MarshalIndent 换成 csv.Writer
存 MongoDB 在 OnHTML 里写 collection.InsertOne
Colly 实战:一键爬取 Coursera 全站课程
Colly 实战:5 分钟爬取 Google Groups 邮件列表
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }