使用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" } } } }
错误原因分析
- 类型不匹配:
Contests["AtCoder"]["FutureContests"]的类型是map[string]map[string]string,但用户尝试直接赋值map[string]string类型的字面量覆盖整个层级,导致类型错误。正确做法是给FutureContests下的具体索引(如"1"、"2")赋值对应的map[string]string。 - 回调逻辑冗余:循环中多次注册
OnHTML会导致同一选择器绑定多个回调,且无法关联同一竞赛的名称和时间(两个回调独立触发)。 - 错误忽略:时区加载、时间解析的错误被直接忽略,可能引发运行时异常。
修复后的代码
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) } }
修复说明
- 类型匹配:通过
Contests["AtCoder"]["FutureContests"][indexStr]给具体索引赋值map[string]string,完全符合嵌套Map的层级类型要求。 - 回调优化:注册表格行的统一回调,一次性获取同一竞赛的名称和时间,确保数据关联正确,避免循环绑定的冗余问题。
- 错误处理:添加时区加载、时间解析、页面访问的错误处理,提升代码健壮性。
- JSON生成:新增Map转格式化JSON并写入文件的逻辑,直接实现最终需求。
内容的提问来源于stack exchange,提问作者Vinay Kumar Rasala
相关产品推荐
相关产品推荐

