为什么会有这么个想法?
说实话,这两年大家最关心的事儿之一就是疫情数据了,每天早上醒来第一件事,打开手机看看新增了多少,哪个区又有了,心里才有底,我也一样。
但问题来了,各大平台的疫情数据虽然都有,但要么打开慢,要么广告多,要么就是你想看某个特定维度的数据它不给你,我翻了几个网站,甚至有些地方的数据更新还不同步,挺让人头大的,对吧?
作为一个程序员,我就在想:能不能自己写个小工具,每天自动把全国各省的每日新增疫情数据抓下来,整理成我能直接看的格式?这样我不用一个个网站去翻,打开命令行或者浏览器就能看到。
就有了这篇文章——怎么用Go语言写一个每日新增疫情查询数据的工具,如果你懂点编程,或者哪怕不懂,也可以跟着我的思路看看,这东西其实没那么复杂。
准备工作:Go语言到底适不适合干这个?
先不说别的,Go语言是出了名的适合干这种网络请求+数据处理的活儿,标准库里面就有net/http可以发请求,encoding/json处理JSON数据,速度还快,编译出来一个小二进制文件,往服务器上一扔就能跑。
我的思路其实很简单:
- 找到一个稳定、更新及时的疫情数据API(公开的)
- 用Go发HTTP请求拿到JSON数据
- 解析JSON,提取我关心的字段比如“每日新增确诊”“累计确诊”“新增无症状”等等
- 格式化输出到终端,或者存成CSV
但说实话,第一步就踩了个坑,很多API要么需要密钥,要么限制访问频率,要么数据来源不明确,我找了一圈,决定用一些开源项目整理好的数据源,丁香医生”的历史存档,或者某些公益API,这里不多说具体链接,因为你搜一下“疫情公开API”就能找到不少。
写代码的那点事:一个真实的踩坑记录
第一版代码:能跑,但很丑
我先写了一个最简单的版本,直接http.Get,然后json.Unmarshal,打印出来,结果打印出来的数据乱七八糟,因为有些字段是嵌套的,有些是interface{}类型,我得手动断言。
那时候的代码大概长这样(别笑,真的写得很粗糙):
resp, err := http.Get("https://some-api.com/covid19/latest")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
然后就是一堆result["data"].(map[string]interface{})["china"].(map[string]interface{})["today"]这种链式类型断言,又长又容易崩,要是哪一层类型不对,直接panic。
这里我想告诉你一个教训:一开始别偷懒,把数据结构定义成struct,比用map[string]interface{}安全一万倍。
第二版:定义结构体,舒服了
我重新看了API返回的JSON结构,大概长这样:
{
"data": {
"china": {
"today": {
"confirm": 1234,
"asymptomatic": 567,
"dead": 2
},
"total": {
"confirm": 123456,
"heal": 100000,
"dead": 5000
}
},
"province": [
{
"name": "广东",
"today": { "confirm": 100 },
"total": { "confirm": 10000 }
}
]
}
}
于是我就定义了对应的struct:
type TodayData struct {
Confirm int `json:"confirm"`
Asymptomatic int `json:"asymptomatic"`
Dead int `json:"dead"`
}
type TotalData struct {
Confirm int `json:"confirm"`
Heal int `json:"heal"`
Dead int `json:"dead"`
}
type ProvinceData struct {
Name string `json:"name"`
Today TodayData `json:"today"`
Total TotalData `json:"total"`
}
type ChinaData struct {
Today TodayData `json:"today"`
Total TotalData `json:"total"`
}
type CovidResponse struct {
Data struct {
China ChinaData `json:"china"`
Province []ProvinceData `json:"province"`
} `json:"data"`
}
这一改,整个代码清爽多了。json.Unmarshal直接填充到结构体里,访问字段用点号,不会崩。
第三版:加个定时任务,每天早上自动查
我不想每次手动跑程序,所以加了个定时器,每天上午8点自动请求一次,然后把数据存到本地CSV里,顺便打印到终端。
核心代码就这几行:
func fetchData() {
url := "https://your-api-url.com"
resp, err := http.Get(url)
if err != nil {
log.Println("请求失败:", err)
return
}
defer resp.Body.Close()
var result CovidResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Println("解析失败:", err)
return
}
// 打印全国数据
fmt.Printf("全国——新增确诊: %d, 新增无症状: %d, 累计确诊: %d\n",
result.Data.China.Today.Confirm,
result.Data.China.Today.Asymptomatic,
result.Data.China.Total.Confirm)
// 打印各省数据,只打印新增大于0的
for _, p := range result.Data.Province {
if p.Today.Confirm > 0 {
fmt.Printf("%s——新增确诊: %d, 累计确诊: %d\n", p.Name, p.Today.Confirm, p.Total.Confirm)
}
}
// 追加到CSV
saveToCSV(result)
}
定时器就用标准库的time.Ticker:
ticker := time.NewTicker(24 * time.Hour)
go func() {
for range ticker.C {
fetchData()
}
}()
第一次跑的时候,数据顺利打印出来了,看到屏幕上一行一行跳出“广东——新增确诊: 100,累计确诊: 10000”的时候,说实话,心里有点爽。
数据查询功能:想查哪天就查哪天
光能看今天的不够,有时候我想看看过去一周的趋势,或者某个省的历史新增情况。
所以我加了一个本地查询功能:从CSV文件里读数据,按日期和省份筛选。
CSV的格式大概是:
| 日期 | 省份 | 新增确诊 | 累计确诊 |
|---|---|---|---|
| 2022-11-01 | 广东 | 100 | 10000 |
| 2022-11-01 | 北京 | 50 | 5000 |
| 2022-11-02 | 广东 | 120 | 10120 |
查询的代码也不复杂:
func queryByDateAndProvince(csvFile, date, province string) {
file, _ := os.Open(csvFile)
defer file.Close()
reader := csv.NewReader(file)
records, _ := reader.ReadAll()
for _, row := range records[1:] { // 跳过表头
if row[0] == date && row[1] == province {
fmt.Printf("日期: %s, 省份: %s, 新增确诊: %s, 累计确诊: %s\n", row[0], row[1], row[2], row[3])
return
}
}
fmt.Println("未找到数据")
}
你可以直接在命令行里运行:
go run main.go --date 2022-11-01 --province 广东
然后就能看到那天的数据,这个功能我每天都会用,尤其是出差前看看目的地的情况。
一些让文章更有用的表格
为了方便你理解,我把我用到的几个核心步骤整理成一个表格:
| 步骤 | 做什么 | 用到的Go包 | 注意事项 |
|---|---|---|---|
| 1 | 发送HTTP请求获取数据 | net/http |
注意设置超时,防止死等 |
| 2 | 解析JSON | encoding/json |
一定要定义struct,别用map |
| 3 | 格式化输出 | fmt |
可以加颜色高亮,但非必须 |
| 4 | 存储到本地文件 | encoding/csv 或 os |
CSV通用性好,Excel也能打开 |
| 5 | 定时任务 | time.Ticker |
注意goroutine泄漏 |
还有一个我调试时发现的小坑:有些API返回的数据中,整数可能用字符串表示,比如"confirm": "100",这时候struct里字段类型要用string,或者自己写一个自定义的Unmarshal函数,不然解析会报错。
这个工具有多“权威”?我敢信吗?
你可能会问:你这个数据来源靠谱吗?万一API挂了或者数据不准怎么办?
说实话,任何第三方的公开API都不能保证100%准确和持续可用,我用的数据源,有的是来自卫健委的公开数据聚合,有的是开源社区维护的(比如某些GitHub项目),我会定期交叉验证:比如拿丁香医生的数据和这个API对比一下,看趋势是否一致。
目前用了大概三个月,没出过大错,但如果你要做专业的数据分析或者政策决策,建议还是以国务院客户端、国家卫健委官网、各地卫健委通报为准,我这个工具只是一个辅助查询的小玩意儿,方便你快速了解大致情况。
生活气息:每天早上看数据的仪式感
现在我每天的流程是这样的:7点半闹钟响,打开电脑,终端里跑一下go run main.go --today,看全国和本省的新增数据,然后泡杯咖啡,打开电视听听新闻,对比一下新闻里报的数据和我查的是否一致。
有时候数据有出入,我会去微博看看热搜,确认是不是统计口径不同,比如有的地方只报本土新增,有的地方把境外输入也算进去了。这些细节,跨平台对比一下就很清楚。
说实话,写这个工具之前,我每天早上刷好几个APP,页面加载慢,有时候还弹广告,现在好了,几行代码搞定,清清爽爽。

最后说点实在的
这篇文章用费曼写作法来写,就是尽量把技术细节用生活化的语言讲清楚,我不希望你读完觉得“好厉害但跟我没关系”,而是希望你能感受到:一个普通程序员,用Go语言写个小工具,解决自己的信息焦虑,这事儿不难,也很有趣。
如果你也想试试,可以从最简单的HTTP请求开始,不要追求完美,第一次代码很丑没关系,慢慢优化,我现在的版本也是改了好几版,结构体定义换过三次,CSV写入加过锁,定时器加过优雅退出。边用边改,才是真实的状态。
没有总结段,就这样吧,你如果真写了,跑起来了,欢迎告诉我你的第一行数据是什么。
本文来自作者[kyadmin]投稿,不代表思利达立场,如若转载,请注明出处:http://yl.c-lida.com/post/11.html
评论列表(4条)
我是思利达的签约作者“kyadmin”!
希望本篇文章《从数据到安心,我用Go语言写了一个每日新增疫情查询工具》能对你有所帮助!
本站[思利达]内容主要涵盖:郑州思利达智能科技有限公司
本文概览:为什么会有这么个想法?说实话,这两年大家最关心的事儿之一就是疫情数据了,每天早上醒来第一件事,打开手机看看新增了多少,哪个区又有了,...