Go语言CSV网站IP归属地查询:JSON输出合并问题求助
问题拆解与修复方案
首先得提个关键问题:你期望的输出格式其实是无效JSON——JSON对象里不允许重复的键(country重复出现),解析器只会保留最后一个键值对,所以咱们得先把目标调整成合理的有效格式,比如用网站作为键、国家作为值的对象,或者包含网站和国家信息的数组。
接下来看你代码里的几个核心问题:
- 重复输出数组:你在每次循环里都打印整个
result数组,导致每次循环都会输出一个越来越长的数组,这就是你看到[{"country":"Singapore"}] [{"country":"United States"},...]这种重复输出的原因。 - map引用覆盖:你一直复用同一个
mmap,每次Unmarshal都会覆盖它的内容,导致result数组里的所有元素其实都是指向同一个map的引用,后面的结果会把前面的覆盖掉,这就是为什么会出现多个相同国家的原因。 - API字段错误:你请求里指定了
fields=org,但你要的是country字段,这会导致返回的JSON里根本没有country,你的m自然拿不到正确的值!
修正后的代码
咱们一步步把这些问题都解决掉:
package main import ( "encoding/csv" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" ) func closeFile(f *os.File) { err := f.Close() if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } func main() { // 用map存储网站和对应国家,键是网站,值是国家,既清晰又避免重复键问题 siteCountryMap := make(map[string]string) csvFile, err := os.Open("test.csv") if err != nil { log.Fatal(err) } defer closeFile(csvFile) reader := csv.NewReader(csvFile) for { line, err := reader.Read() if err == io.EOF { break } else if err != nil { log.Fatal(err) } site := line[0] // 修正API请求的fields参数,获取我们需要的country字段,同时加上status和message用来处理错误 resp, err := http.Get(fmt.Sprintf("http://ip-api.com/json/%s?fields=status,country,message", site)) if err != nil { log.Printf("请求网站 %s 失败: %v", site, err) continue } // 一定要记得关闭响应体,避免资源泄漏 defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取 %s 的响应内容失败: %v", site, err) continue } // 用结构体解析API响应,比用map更安全、可读性更高 type ApiResponse struct { Status string `json:"status"` Country string `json:"country"` Message string `json:"message"` } var apiResp ApiResponse err = json.Unmarshal(data, &apiResp) if err != nil { log.Printf("解析 %s 的响应JSON失败: %v", site, err) continue } // 检查API返回的状态,处理查询失败的情况 if apiResp.Status != "success" { log.Printf("查询 %s 的国家信息失败: %s", site, apiResp.Message) continue } // 将网站和对应的国家存入map siteCountryMap[site] = apiResp.Country } // 所有请求完成后,统一把结果转成JSON并输出(用MarshalIndent让JSON更美观) resultJson, err := json.MarshalIndent(siteCountryMap, "", " ") if err != nil { log.Fatal("生成结果JSON失败: ", err) } fmt.Println(string(resultJson)) }
输出效果
运行这段代码后,你会得到一个有效的JSON对象,格式大概是这样的:
{ "www.google.com": "Singapore", "www.bing.com": "United States", "www.pokemon.com": "Singapore", "www.yahoo.com": "Ireland" }
如果你更偏好数组格式(每个元素包含网站和国家信息),可以稍微调整代码:
// 先定义一个结构体来存储单条记录 type SiteCountry struct { Site string `json:"site"` Country string `json:"country"` } // 初始化一个切片来存储所有记录 var siteCountryList []SiteCountry // 在循环里把每条记录追加到切片中 siteCountryList = append(siteCountryList, SiteCountry{Site: site, Country: apiResp.Country}) // 最后输出切片的JSON resultJson, err := json.MarshalIndent(siteCountryList, "", " ")
这样输出的JSON会是:
[ { "site": "www.google.com", "country": "Singapore" }, { "site": "www.bing.com", "country": "United States" }, { "site": "www.pokemon.com", "country": "Singapore" }, { "site": "www.yahoo.com", "country": "Ireland" } ]
额外小贴士
- ip-api对非商业用途有请求频率限制(每分钟最多45次),如果你的CSV里有大量网站,记得添加延迟或者使用它的批量查询接口,避免被限流。
- 代码里一定要处理各种错误场景(比如请求失败、解析失败),不然遇到异常情况程序直接崩溃就麻烦啦。
- 用结构体解析JSON比用map更靠谱,能明确知道返回的字段结构,避免拼写错误导致的问题。
内容的提问来源于stack exchange,提问作者Bilal Jama




