调查长江水情

2020-07-25

江河养人, 亦能淹人。 水患频发, 水情不可不察。 本文演示如何制作长江水情历史数据报表。

背景

重庆新闻说有洪灾, 湖北新闻说有洪灾, 安徽新闻也说有洪灾, 看到这些新闻, 你猜测也许整个长江都有洪灾, 但对长江汛情, 还是难以形成全局、 连续的认识。

观察发现, 洪灾新闻常常引用长江水文网的数据。 于是你找到长江水文网 , 找到了实时水情的数据。
可惜, 它不提供历史数据, 你看不见过去和未来的趋势。

作品展示

image

作品网址 https://cxumol.github.io/LongRiver/index.html (数据实时更新)

架构

            +-----------+
            |Data Source|
            +--^--+-----+
               |  |
+-------+    +-+--v--+    +-------+
|Trigger+---->Fetcher+---->Storage|
+-------+    +-------+    +-^--+--+
                            |  |
                         +--+--v--+
                         |Showcase|
                         +----+---+
                              |
                         +----+------+
                         | User view |
                         +-----------+

理论上流程是, 定期启动 (Trigger) 一个脚本 (Fetcher) 从数据源 (Data Source) 获得实时数据, 储存到某处 (Storage)。 当人 (User) 访问指定位置 (Showcase) , 就会取得数据并呈现。

所有这些, 都能放在一台服务器上面运行, 但我想用无服务器 “serverless” 的方案。 Serverless 不仅时尚, 而且实惠。

为了 Serverless, 尝试过不少云服务, 比如 crawlab , Task 君 , Glitch , UptimeRobot , DBHub 等。 有些难用, 有些不靠谱。

某天突然开窍想通了, 其实上 GitHub 全家桶就能统统搞定。 实际流程如下。

                  +-----------+
                  |Data Source|
                  +--^--+-----+
                     |  |
                 +-----------------------------+
                 |   |  |       Git Repository |
+----------------------------+                 |
| +-----------+  |   |  |    |                 |
| |on:        |  | +-+--v--+ | +-----------+   |
| |  schedule:+---->main.py+--->.json,.csv |   |
| |  - cron   |  | +-------+ | +----+------+   |
| +--+--------+  |           |      |          |
|                |           | +------------+  |
| GitHub Actions |           | | .html,.js  |  |
+----------------------------+ +----+-------+  |
                 |                  |          |
                 +-----------------------------+
                                    |
                                +---v----------+
                                | GitHub Pages |
                                +--------------+


脚本用 python 好写; 存数据不用数据库, 用 json 和 csv 存纯文本; 获取数据和生成图表, 交给浏览器客户端去做。 于是, 无论脚本、 数据, 还是展示网页, 统统塞进 Git 仓库就完事了。 然后 GitHub Actions 套 crontab 语法定期调脚本更新数据, GitHub Pages 托管网页。

GitHub 全家桶能覆盖到这个 serverless 的方方面面, 真省心。

爬虫

脚本负责获取、 预处理、 储存、 更新数据。

打开 长江水文网 , F12 找实时水情, 发现是个 <iframe>, 继而打开 对应网址 , 看源码, 正则匹配就能直接获取 JSON 字串, 从而得到原始数据。 真省心。 详细过程可以参考一篇图文并茂的大佬博客

JSON 中每个字段的含义, 最好要理解。 我纯靠自身聪明才智, 猜出来一些。

rvnm = river name = 河流名
stnm = station name = 水站名
tm = time = 时间
q = 流量
z = 水位
oq = out q = 出流量
stcd = station code = 水站站码
wptn = water potential tend? = 水势 (绿4落, 红5涨, 灰6平稳) 

各站更新频率不一, 需要避免记录重复项。 以时间戳判断, 未更新的数据丢弃, 已更新的数据才添加为新纪录。

最后一步, 保存。 原始数据存到 json (LongRiver.json) ; 按水站分表, 存到各 csv (河流_水站.csv) 。

这些数据最后保存在这里

数据可视化

库太多, 选起来费劲。 有人说 D3.js 经典靠谱, 有人说 <canvas> 吊打 <svg>, 有人说 bokeh 结合 py 好, 有人说 ECharts 国货当自强。

挑选库有什么窍门? 希望有大佬指点下。 总之, 看到基于 D3.js 简化的 C3.js 似乎比较简单易学, 于是看文档一路试着用下来, 发现还可以, 就先选它了。

如何使用 C3.js? HTML 引入 c3.min.js、 c3.min.css、 d3.min.js 的 CDN 网址, 然后给 c3.generate() 传入一个对象, 对象里填写生成这个图表所需的一切配置。

欲做大量图表, 先得做一个最朴素、 能看、 只有一座水站的数据图表。 然后扩充、 调整样式。 尽量调好一个图表以后, 再将同样配置扩展到多个图表。

调图表样式, 踩坑部分注解如下:

config = {
  data: {
    hide: ['oq', 'stcd', 'wptn',  'rvnm', 'stnm'], //不能从 csv 中直接挑选数据列, 但可以隐藏不需要的部分
    legend: {
      hide: ['oq', 'stcd', 'wptn', 'rvnm', 'stnm'] //lable 需要额外隐藏一次
  },
  axis: {
    x: {
      //type: 'timeseries', //这样解析不动 unix epoch 格式的时间, 不知为啥
      tick: {
        format: function(x) {
          return new Date(x).toLocaleDateString('zh-CN');
        } //好在允许通过回调函数设置时间格式, 于是就能抛开原本的 `axis.x.tick.type` 不管。
      }
    }
  }
};

调好一个图表, 接下来就能批量生成多个图表, 以及对应标题。 这里通过 JS 操作网页 DOM 的方式完成。 for 循环取得每个文件名(河流_水站.csv), 变量对应到各图表的 数据源 data.url 选项和 绑定HTML元素 bindto 选项。

图表之间的顺序也不能乱排, 于是到地图软件搜索了解各个水站位置, 然后让这组图表大致按照上游到下游的顺序排列。

共 13 个水站, 有两个是水库。 水库可以人为控制蓄水和放水, 所以流量分为入流量和出流量, 而其他水站只有一个流量。 先前按照常规水站调整好的图表, 对于水库型水站需要额外调整。 采取的方式是, 通过文件名是否含有 “水库” 字样判断水站类型, 函数 genChart 多传一个带默认值的参数 type, 函数内部再由 type 判断更改相关配置, 把"流量"改为"入流量"并添加"出流量"。

GitHub Actions

不像 Travis CI 的 cron jobs 只给你按天、 按周、 按月这几个选项定期运行, GitHub Actions 允许你使用真正的 crontab 语法。 好方便你尽情滥用

GitHub Actions 另一个好处是, 可以引用别人做好的动作包, 动作包在左侧 market place 搜索添加。 这次用到的动作包有:

  • actions/checkout@v2 开通自身仓库的权限;
  • actions/setup-python@v2 配置 python 环境;
  • EndBug/add-and-commit@v4.2.1 提交对仓库的修改。

此外, 欲调试 workflow, 最好加上 on: workflow_dispatch:, 这样就能在仓库网页的 Actions 面板手动触发流程。

总结

请看 https://cxumol.github.io/LongRiver/index.html
清晰明了, 有助于形成对长江汛情全局、 连续的认识。 希望能以此帮助灾区朋友正确应对, 减少损失。

起初爬虫写出来后, 没找到合适的云服务运行, 就停止收集数据。 经同学的启发才醒悟到, 早该先在服务器上跑, 数据到手了再说。

改进方向:

  1. 找个数据库, 存进去吧, 做个体面人; 现在按月份分表, 再也不需要数据库啦;
  2. csv 里的无用字段, 白白浪费了客户端的流量和内存, 应产出简化版 csv, 剔除无用的字段;
  3. 长江水文网上的水位标记了三种颜色, 表示水势涨落。 C3.js 好像不支持同一折线上的点染不同颜色, 可能无法体现水势, 不知咋办, 要不换个库。
  4. 现经 GitHub API 获取可用月份, 易触顶, 应替代。

你对此有何意见或建议? 乐意在评论区聆听你的见解。

作业

请尝试回答以下问题:

  1. c3.js, d3.js, bokeh.js, chart.js 这些图表绘制库分别有什么缺点? 你有什么图表库推荐?
  2. 使用 GitHub 全家桶, 和分散使用多方云服务相比, 有什么缺点?
  3. 不用数据库, 用 json 保存数据, 有什么缺点?

欢迎在下方评论区留言你的解答或想法。

上期作业参考答案

  1. 点击按钮挂载, 发现这个挂载是只读模式的, 不香。
  2. 可能不是常见编码, 转码就行。 Colab 上就能转码, 就是慢。
  3. 自动生成字幕无需额外操作, 多等一会儿就行。 但示例视频不能自动生成字幕, 因为 Google 官网帮助页 说 “自动字幕目前支持英语、荷兰语、法语、德语、意大利语、日语、韩语、葡萄牙语、俄语和西班牙语。”, 而示例视频的语音为汉语普通话(洋腔洋调), 不在此列。
Developscrapdatadata-visgh-actionsd3jswebapp



Support Me

您可以 打赏 支持本文作者

Non-interactive rclone configuration for SharePoint with custom API

Support Me 鼓励作者