You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Go语言CSV网站IP归属地查询:JSON输出合并问题求助

问题拆解与修复方案

首先得提个关键问题:你期望的输出格式其实是无效JSON——JSON对象里不允许重复的键(country重复出现),解析器只会保留最后一个键值对,所以咱们得先把目标调整成合理的有效格式,比如用网站作为键、国家作为值的对象,或者包含网站和国家信息的数组。

接下来看你代码里的几个核心问题:

  1. 重复输出数组:你在每次循环里都打印整个result数组,导致每次循环都会输出一个越来越长的数组,这就是你看到[{"country":"Singapore"}] [{"country":"United States"},...]这种重复输出的原因。
  2. map引用覆盖:你一直复用同一个m map,每次Unmarshal都会覆盖它的内容,导致result数组里的所有元素其实都是指向同一个map的引用,后面的结果会把前面的覆盖掉,这就是为什么会出现多个相同国家的原因。
  3. 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

火山引擎 最新活动