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

使用Colly包将爬取竞赛信息存入Go Map并生成JSON的问题

Colly爬虫嵌套Map赋值类型不匹配问题解决

问题背景

使用Colly包开发网页爬虫,目标从AtCoder网站抓取竞赛名称(ContestName)和竞赛时间(ContestTime),存入嵌套Map后生成指定格式的JSON文件,但赋值时触发类型不匹配错误:

cannot use (map[string]string literal) (value of type map[string]string) as map[string]map[string]string value in assignment

用户实现代码:

package main

import (
	"fmt"
	"time"

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

func main() {
	Contests := make(map[string]map[string]map[string]map[string]string)

	Contests["AtCoder"] = make(map[string]map[string]map[string]string)
	Contests["AtCoder"]["FutureContests"] = make(map[string]map[string]string)

	AtcoderFunc(Contests)
}

func AtcoderFunc(Contests map[string]map[string]map[string]map[string]string) {
	collector := colly.NewCollector(
		colly.AllowedDomains("atcoder.jp", "www.atcoder.jp"),
	)

	format := "2006-01-02 15:04:05-0700"
	loc, _ := time.LoadLocation("Asia/Calcutta")

	for i := 1; i < 10; i++ {
		ContestSelTime := fmt.Sprintf("#contest-table-upcoming  div  div  table  tbody  tr:nth-child(%d)  td:nth-child(1)  a", i+1)
		ContestSelName := fmt.Sprintf("#contest-table-upcoming  div  div  table  tbody  tr:nth-child(%d)  td:nth-child(2)", i)

		collector.OnHTML(ContestSelName, func(element *colly.HTMLElement) {
			ContestName := element.ChildText("a")
			fmt.Printf("%T \n", ContestName)
			fmt.Println(ContestName)
			// 错误赋值尝试
			// Contests["AtCoder"]["FutureContests"] = map[string]string{
			// 	"Name": string(ContestName),
			// }
		})

		collector.OnHTML(ContestSelTime, func(element *colly.HTMLElement) {
			ContestStartTime := element.ChildText("time")
			parsed_time, _ := time.Parse(format, ContestStartTime)
			IST_time := parsed_time.In(loc)
			fmt.Println("Time in IST", IST_time)
		})
	}

	collector.OnRequest(func(request *colly.Request) {
		fmt.Println("Visiting", request.URL.String())
	})

	collector.Visit("https://atcoder.jp/contests")
}

期望生成的JSON格式:

{
  "AtCoder": {
    "FutureContests": {
      "1": {
        "Name": "Contest name",
        "Start": "time here"
      },
      "2": {
        "Name": "Contest name",
        "Start": "time here"
      }
    }
  }
}

错误原因分析

  1. 类型不匹配:Contests["AtCoder"]["FutureContests"]的类型是map[string]map[string]string,但用户尝试直接赋值map[string]string类型的字面量覆盖整个层级,导致类型错误。正确做法是给FutureContests下的具体索引(如"1"、"2")赋值对应的map[string]string。
  2. 回调逻辑冗余:循环中多次注册OnHTML会导致同一选择器绑定多个回调,且无法关联同一竞赛的名称和时间(两个回调独立触发)。
  3. 错误忽略:时区加载、时间解析的错误被直接忽略,可能引发运行时异常。

修复后的代码

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"time"

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

func main() {
	// 初始化匹配期望JSON的嵌套Map结构
	Contests := map[string]map[string]map[string]map[string]string{
		"AtCoder": {
			"FutureContests": make(map[string]map[string]string),
		},
	}

	AtcoderFunc(Contests)

	// 生成格式化JSON并写入文件
	jsonData, err := json.MarshalIndent(Contests, "", "  ")
	if err != nil {
		fmt.Println("生成JSON失败:", err)
		return
	}

	if err := os.WriteFile("atcoder_contests.json", jsonData, 0644); err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}
	fmt.Println("JSON文件生成成功")
}

func AtcoderFunc(Contests map[string]map[string]map[string]map[string]string) {
	collector := colly.NewCollector(
		colly.AllowedDomains("atcoder.jp", "www.atcoder.jp"),
	)

	format := "2006-01-02 15:04:05-0700"
	loc, err := time.LoadLocation("Asia/Calcutta")
	if err != nil {
		fmt.Println("加载时区失败:", err)
		return
	}

	// 注册表格行回调,一次性获取同一行的名称和时间
	collector.OnHTML("#contest-table-upcoming table tbody tr", func(element *colly.HTMLElement) {
		// 获取行索引(优先用data-row属性,无则用位置索引)
		index := element.Index + 1
		if rowAttr := element.Attr("data-row"); rowAttr != "" {
			if idx, err := strconv.Atoi(rowAttr); err == nil {
				index = idx
			}
		}
		indexStr := strconv.Itoa(index)

		// 提取竞赛名称
		contestName := element.ChildText("td:nth-child(2) a")
		// 提取并转换竞赛时间为IST时区
		contestTimeStr := element.ChildText("td:nth-child(1) time")
		parsedTime, err := time.Parse(format, contestTimeStr)
		if err != nil {
			fmt.Printf("解析时间失败:%s,错误:%v\n", contestTimeStr, err)
			return
		}
		istTime := parsedTime.In(loc).Format("2006-01-02 15:04:05")

		// 正确赋值到嵌套Map,匹配类型要求
		Contests["AtCoder"]["FutureContests"][indexStr] = map[string]string{
			"Name":  contestName,
			"Start": istTime,
		}
	})

	collector.OnRequest(func(request *colly.Request) {
		fmt.Println("Visiting", request.URL.String())
	})

	if err := collector.Visit("https://atcoder.jp/contests"); err != nil {
		fmt.Println("访问页面失败:", err)
	}
}

修复说明

  1. 类型匹配:通过Contests["AtCoder"]["FutureContests"][indexStr]给具体索引赋值map[string]string,完全符合嵌套Map的层级类型要求。
  2. 回调优化:注册表格行的统一回调,一次性获取同一竞赛的名称和时间,确保数据关联正确,避免循环绑定的冗余问题。
  3. 错误处理:添加时区加载、时间解析、页面访问的错误处理,提升代码健壮性。
  4. JSON生成:新增Map转格式化JSON并写入文件的逻辑,直接实现最终需求。

内容的提问来源于stack exchange,提问作者Vinay Kumar Rasala

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 16:37:44