【疫情监控】打造全国新冠疫情Web项目:三次优化,提升用户体验

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 本文分享了开发全国新冠疫情Web项目的经验和优化过程,包括调整项目结构、新增logger模块改善日志记录、实现数据实时刷新功能,以及通过spider模块代码自动更新疫情数据,提升了用户体验并简化了项目维护流程。

日拱一卒无有尽,功不唐捐终入海!

一、背景

在整理公司项目的时候 ,不晓得什么时候接了一个关于疫情的项目,需要可视化展示数据 ,大致了解过项目结构和实现逻辑,先是从公共接口爬取数据,然后清洗数据再写入数据库,最后使用flask后台框架启动访问web,读取数据库的数据,经过前端渲染,如下图所示:

在这里插入图片描述

1.1、调整原来的项目结构

在这里插入图片描述

新增了conf目录,作为该项目的数据配置项,譬如mysql信息就统一管理,因为原项目在spider和sqldata两处都创建了mysql连接,所以只保留一处即可

def mysql():
    """创建mysql连接,返回数据库操作对象"""
    db = pymysql.connect(host=MYSQL_HOST, user=MYSQL_DB_USER, password=MYSQL_DB_PASSWORD, database=MYSQL_DB_NAME, charset='utf8')
    cur = db.cursor()
    return db, cur


def close(db, cur):
    """关闭数据库连接"""
    cur.close()
    db.close()


def query(sql: str, *args):
    """执行sql并返回结果"""
    db, cur = mysql()
    cur.execute(sql, args)
    result = cur.fetchall()
    close(db, cur)
    return result

二、新增/优化功能

原项目中在spider文件中,每次操作都会在控制台输出print日志,这个样子有点不美观

在这里插入图片描述

2.1、新增logger模块

创建libs作为整个项目的工具类

class Logger(object):
    __instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.__instance: 
            cls.__instance = object.__new__(cls, *args) 
        return cls.__instance 

    def __init__(self):
        self.formater = logging.Formatter(
            '[%(asctime)s] [%(levelname)s] [%(module)s:%(lineno)d ], %(message)s')
        self.logger = logging.getLogger('log')
        self.logger.setLevel(logging.DEBUG)
        self.filelogger = handlers.RotatingFileHandler(LOG_PATH,
                                                       maxBytes=5242880,
                                                       backupCount=3,
                                                       encoding='utf-8')
        # self.console = logging.StreamHandler()
        # self.console.setLevel(logging.DEBUG)
        self.filelogger.setFormatter(self.formater)
        # self.console.setFormatter(self.formater)
        self.logger.addHandler(self.filelogger)
        # self.logger.addHandler(self.console)
  • 日志输出乱码?

在文件处理对象的时候,需要指定encoding=‘utf-8’,避免乱码

2.2、实时刷新数据

前面读了spider模块,每次执行都会先删除表然后再创建最后写入数据,从数据库配置来看完全没有任何压力,但是启动app时,它并没有刷新数据库,每次都会获取第一次执行spider的数据,所以需要做到实时刷新,也就是spider需要每次都执行。

@app.route('/')
def hello_world():
    logger.logger.info("用来刷新数据...")
    main() # 这个就是每次操作数据库,删除、创建、写入
    return render_template('main.html')

三、总结

就整个项目而言,原先是需要先执行spider然后再启动app,所以没那么智能,在经过调整后,只需要启动app就会连带讲数据库的数据刷新一遍,每次都会是最新数据,尽管是每天的某个固定时间才会更新,总好过每次手动指定spider模块;

  • 还有一点优化,就是requirements.txt;在当前环境安装pipreqs,然后在工程根路径下执行pipreqs ./就可以获得项目相关的库依赖,这样就方便项目迁移了。

附录spider模块代码<关注我,获取源码>:

def get_json(url):
    headers = {
   
   
        'user - agent': 'Mozilla / 5.0(WindowsNT10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 80.0.3987.116Safari / 537.36'
    }
    response = requests.get(url=url, headers=headers).text
    data = json.loads(response)
    return data['data']


# 获取当日数据
def get_now(data):
    now = []
    data_time = str(data['diseaseh5Shelf']['lastUpdateTime'])  # 数据更新时间
    data_all = data['diseaseh5Shelf']['areaTree'][0]
    data_province_s = data['diseaseh5Shelf']['areaTree'][0]['children']

    # 获取全国今日新增、累计确诊、治愈人数、死亡人数
    confirms = data_all['total']['confirm']
    confirms_add = data_all['today']['confirm']
    heals = data_all['total']['heal']
    deads = data_all['total']['dead']

    # 获取每个省份的每个城市今日新增、累计确诊、治愈人数、死亡人数
    for data_province in data_province_s:
        province = data_province['name']  # 省份
        for data_city in data_province['children']:
            city = data_city['name']  # 城市
            confirm = data_city['total']['confirm']  # 确诊
            confirm_add = data_city['today']['confirm']  # 新增
            heal = data_city['total']['heal']  # 治愈
            dead = data_city['total']['dead']  # 死亡
            now.append((data_time, province, city, confirm_add, confirm, heal, dead))
    return confirms, confirms_add, heals, deads, now


# 获取历史数据
def get_past(data):
    past = {
   
   }
    for data_day in data:
        data_time = data_day['date']  # 获取最原始的时间
        time_deal = time.strptime(data_time, '%m.%d')  # 根据指定的格式把一个时间字符串解析为时间元组
        date = time.strftime('%m-%d', time_deal)  # 重新组成新的时间字符串
        past[date] = {
   
   
            'confirm': data_day['confirm'],  # 确诊
            'suspect': data_day['suspect'],  # 疑似
            'heal': data_day['heal'],  # 治愈
            'dead': data_day['dead']  # 死亡
        }

    return past


# 写入当日数据
def insert_now(now):
    try:
        logger.logger.info("每次这个很有意思,每次刷新都需要先删除数据表,然后再创建表,最后再将清洗的数据写入表中.")
        cur.execute("DROP TABLE IF EXISTS 当日数据")
        # 写创建表的sql语句
        set_sql_now = "create table 当日数据(时间 varchar(100),省份 varchar(50),城市 varchar(50),新增确诊 int(11)," \
                      "确诊人数 int(11),治愈人数 int(11),死亡人数 int(11))ENGINE=InnoDB DEFAULT CHARSET=utf8"
        # 执行sql语句
        cur.execute(set_sql_now)
        # 保存
        db.commit()
        # 写入数据库
        save_sql_now = "insert into 当日数据 values(%s,%s,%s,%s,%s,%s,%s)"
        cur.executemany(save_sql_now, now)  # now位置必须是个列表,列表里面的元素是数组
        db.commit()
        logger.logger.info('当日数据写入成功')
    except Exception as e:
        logger.logger.error('当日数据写入失败原因:%s' % e)


# 写入历史数据
def insert_past(past):
    try:
        cur.execute("DROP TABLE IF EXISTS 历史数据")
        # 写创建表的sql语句
        set_sql_past = "create table 历史数据(时间 varchar(100),确诊人数 int(11),疑似病例 int(11),治愈人数 int(11)," \
                       "死亡人数 int(11))ENGINE=InnoDB DEFAULT CHARSET=utf8"
        # 执行sql语句
        cur.execute(set_sql_past)
        # 保存
        db.commit()
        # 写入历史数据
        for date, data in past.items():
            sql_past = f"insert into 历史数据 values('{date}',{data['confirm']},{data['suspect']},{data['heal']}," \
                       f"{data['dead']})"
            cur.execute(sql_past)
        db.commit()
        logger.logger.info('历史数据写入成功')
    except Exception as e:
        logger.logger.error('历史数据写入失败原因:%s' % e)


# 写入历史新增数据
def insert_past_add(past):
    try:
        cur.execute("DROP TABLE IF EXISTS 历史新增数据")
        # 写创建表的sql语句
        set_sql_past = "create table 历史新增数据(时间 varchar(100),新增确诊 int(11),新增疑似 int(11),新增治愈 int(11)," \
                       "新增死亡 int(11))ENGINE=InnoDB DEFAULT CHARSET=utf8"
        # 执行sql语句
        cur.execute(set_sql_past)
        # 保存
        db.commit()
        # 写入历史数据
        for date, data in past.items():
            sql_past = f"insert into 历史新增数据 values('{date}',{data['confirm']},{data['suspect']},{data['heal']}," \
                       f"{data['dead']})"
            cur.execute(sql_past)
        db.commit()
        logger.logger.info('历史新增数据写入成功')
    except Exception as e:
        logger.logger.error('历史新增数据写入失败原因:%s' % e)


# 写入累计确诊等四个关键数据
def insert_main(confirm, confirm_add, heal, dead):
    try:
        cur.execute("DROP TABLE IF EXISTS 关键数据")
        # 写创建表的sql语句
        set_sql_main = "create table 关键数据(确诊人数 int(11),新增确诊 int(11),治愈人数 int(11),死亡人数 int(11))" \
                       "ENGINE=InnoDB DEFAULT CHARSET=utf8"
        cur.execute(set_sql_main)
        db.commit()
        # 写入确诊人数、新增确诊、治愈人数、死亡人数的数据
        save_sql_main = f"insert into 关键数据 values({confirm},{confirm_add},{heal},{dead})"
        cur.execute(save_sql_main)
        db.commit()
        logger.logger.info('关键数据写入成功')
    except Exception as e:
        logger.logger.error('关键数据写入失败原因:%s' % e)
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
3月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
391 1
|
7月前
|
监控 Linux 调度
Veeam ONE 13 之初见 - Web 控制台和 Veeam 监控的未来
Veeam ONE 13 之初见 - Web 控制台和 Veeam 监控的未来
182 1
Veeam ONE 13 之初见 - Web 控制台和 Veeam 监控的未来
|
4月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
523 64
|
5月前
|
安全 JavaScript Java
java Web 项目完整案例实操指南包含从搭建到部署的详细步骤及热门长尾关键词解析的实操指南
本项目为一个完整的JavaWeb应用案例,采用Spring Boot 3、Vue 3、MySQL、Redis等最新技术栈,涵盖前后端分离架构设计、RESTful API开发、JWT安全认证、Docker容器化部署等内容,适合掌握企业级Web项目全流程开发与部署。
372 0
|
7月前
|
人工智能 安全 程序员
用 Colab 和 ngrok 免费部署你的 Web UI 项目,随时随地访问!
用 Colab 和 ngrok 免费部署你的 Web UI 项目,随时随地访问!
|
10月前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
671 7
|
12月前
|
运维 前端开发 C#
一套以用户体验出发的.NET8 Web开源框架
一套以用户体验出发的.NET8 Web开源框架
313 7
一套以用户体验出发的.NET8 Web开源框架
|
10月前
|
安全 Linux 开发工具
零基础构建开源项目OpenIM桌面应用和pc web- Electron篇
OpenIM 为开发者提供开源即时通讯 SDK,作为 Twilio、Sendbird 等云服务的替代方案。借助 OpenIM,开发者可以构建安全可靠的即时通讯应用,如 WeChat、Zoom、Slack 等。 本仓库基于开源版 OpenIM SDK 开发,提供了一款基于 Electron 的即时通讯应用。您可以使用此应用程序作为 OpenIM SDK 的参考实现。本项目同时引用了 @openim/electron-client-sdk 和 @openim/wasm-client-sdk,分别为 Electron 版本和 Web 版本的 SDK,可以同时构建 PC Web 程序和桌面应用(Wi
752 2
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
250 4
|
6月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!