You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go的go-colly爬虫本地正常,部署至Heroku无数据输出问题排查

问题

用Go语言的go-colly库开发的网页爬虫,本地运行正常,但部署到Heroku的Docker容器后无法返回任何数据,求排查方向。

爬虫代码

type Song struct {
    Title    string
    Subtitle string
    Link     string
}

var wg sync.WaitGroup
var mu sync.Mutex

func getTune(query string, allSongs *[]Song, wg *sync.WaitGroup) {

    c := colly.NewCollector(
        colly.AllowedDomains("https://get-tune.cc", "get-tune.cc"),
    )

    c.WithTransport(&http.Transport{
        Proxy: http.ProxyFromEnvironment,
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
            DualStack: true,
        }).DialContext,
        IdleConnTimeout:       120 * time.Second,
        TLSHandshakeTimeout:   20 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    })

    runtime.LockOSThread()

    mu.Lock()
    defer mu.Unlock()
    defer wg.Done()

    c.OnHTML(".playlist li", func(element *colly.HTMLElement) {
        link := element.Attr("data-mp3")
        songs := element.DOM

        song := songs.Find(".playlist-name").Find("b").Text()
        em := songs.Find(".playlist-name").Find("em").Text()

        if len(em) > 5 && !strings.Contains(strings.ToLower(em), "remix") && !strings.Contains(strings.ToLower(em), "mix") && !strings.Contains(strings.ToLower(em), "edit") && !strings.Contains(strings.ToLower(song), "mix") && !strings.Contains(strings.ToLower(song), "edit") && !strings.Contains(strings.ToLower(song), "remix") {
            *allSongs = append(*allSongs, Song{
                Title:    song,
                Subtitle: em,
                Link:     link,
            })
        }
    })

    err := c.Visit(fmt.Sprintf("https://get-tune.cc/search/f/%s/", strings.Join(strings.Split(query, " "), "+")))
    if err != nil {
        fmt.Printf("Error: %v", err)
    }
}

func Crawler(query string) []Song {
    songs := []Song{}
    wg.Add(1)
    go getTune(query, &songs, &wg)
    wg.Wait()
    return songs
}

Dockerfile

FROM golang:1.19.2-alpine3.15 AS builder

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY . .

RUN go build -o main .

EXPOSE 4000

CMD ["./main"]

docker-compose.yml

version: '3.9'

services: 
  redis:
    image: 'bitnami/redis:latest'
    command: redis-server --requirepass password
    ports:
      - 6379:6379
    volumes:
      - $PWD/redis-data:/var/lib/redis
      - $PWD/redis.conf:/usr/local/etc/redis/redis.conf
    
    environment:
      - REDIS_REPLICATION_MODEL=master
      - ALLOW_EMPTY_PASSWORD=yes
    
  app:
    build: .
    command: go run main.go
    volumes:
      - .:/app
    ports:
      - 4000:4000
    depends_on:
      - redis

heroku.yml

build:
  docker:
    web: Dockerfile

run:
  web: ./main
可能的问题原因及排查方向
  • AllowedDomains配置错误:colly的AllowedDomains参数应传入不带协议的纯域名,你写了"https://get-tune.cc",会导致域名匹配失败,爬虫被阻止发起请求。修正为仅"get-tune.cc"即可。
  • 锁的使用逻辑错误:getTune函数一开始就调用mu.Lock(),并通过defer在函数结束时解锁,但OnHTML的回调是在colly内部的goroutine中执行的,此时锁已释放,向allSongs追加数据时会出现竞态条件。另外,整个函数持有锁会让goroutine失去并发意义,应把锁的范围缩小到修改allSongs的代码块内。
  • 网络访问限制:Heroku容器环境可能存在出站网络限制,或者目标网站对Heroku的IP段做了拦截。可以在Heroku容器内执行curl https://get-tune.cc测试连通性,或在代码中增加更详细的网络错误日志。
  • 错误日志未输出到标准错误:当前用fmt.Printf输出错误,Heroku日志可能无法捕获,改用log.Printf或直接输出到os.Stderr,方便在heroku logs中查看具体错误信息。
  • runtime.LockOSThread()无意义且可能引发问题:该函数会将goroutine绑定到OS线程,容器环境中线程资源有限,这种绑定可能导致线程耗尽,直接删除即可。
  • 端口监听问题:如果服务硬编码了监听4000端口,Heroku会动态分配端口,需要读取PORT环境变量设置监听端口,否则服务无法被外部访问(虽不是爬虫无数据的直接原因,但会影响整体服务可用性)。

内容的提问来源于stack exchange,提问作者Javokhirbek Khaydaraliev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 02:35:23