Python爬取疫情实战:Flask搭建web/Echarts可视化大屏/MySQL数据库/Linux项目部署与任务定时调度

展示地址

总的来说这次的项目还是遇到了不少坑,有的地方调了好久。

首先这个项目就是要会使用爬虫,能去各大巨头那拿到数据,先复习一下

urllib的使用

from urllib import request

header = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
}

url = "http://www.dianping.com"
req = request.Request(url, headers = header)
res = request.urlopen(req) #获取响应

print(res.info()) #响应头
print(res.getcode()) #状态码 2xx:正常   3xx:发生了重定向  4xx:访问资源有问题  5xx:服务器出错
print(res.geturl()) #返回响应地址
html = res.read()
# print(html)
html = html.decode("utf-8")
print(html)

requests的使用

import requests
header = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
}
url = "http://www.baidu.com"
res = requests.get(url, headers = header)

print(res.encoding)
print(res.headers) # 里面如果没有 Content-Type, encoding = utf-8,否则如果有charset 则以设置为准, 否则 为 IOS-8859-1
print(res.url)

res.encoding = "utf-8"
print(res.text)
print(res.status_code)

Beautifulsoup的简单使用

import re
import requests
from bs4 import BeautifulSoup

url = "http://wsjkw.sc.gov.cn/scwsjkw/gzbd/fyzt.shtml"
res = requests.get(url)
res.encoding = "utf-8"
html = res.text
soup = BeautifulSoup(html)
a = soup.find("a")
print(a)
print(a.attrs["href"])
url_new = "http://wsjkw.sc.gov.cn" + a.attrs["href"]
res = requests.get(url_new)
res.encoding = "utf-8"
soup = BeautifulSoup(res.text)
content = soup.find_all("p")
print(content)
text = content[1].text
patten = "新增新型冠状病毒肺炎确诊病例(\d+)例"
print(text)
res = re.search(patten, text)
print(res)
# .*后面的 ? 表示非贪心匹配
patten = "新增新型冠状病毒肺炎确诊病例(\d+).*?出院病例(\d+).*?疑似病例(\d+)?.*?死亡病例(\d+)?"
# print(text)
res = re.search(patten, text)
print(res)
# print(res.groups())

爬取腾讯的疫情数据

爬取这个数据最重要的就是解析数据包的结构,说实话这个结构包有一点复杂,花了我不少时间

import urllib.request as rq
import pymysql     #链接数据库
import requests
from bs4 import BeautifulSoup
import json
import time
import traceback   #追踪异常
url_today = "https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5"
url_history = "https://view.inews.qq.com/g2/getOnsInfo?name=disease_other"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
}



def get_tencent_data():
    """
    return 返回历史数据和当日详细数据
    """
    
    # 获取历史数据    
    r = requests.get(url_history, headers)
    r.encoding = "utf-8"
    res = json.loads(r.text) # json字符串转成字典
    data_history = json.loads(res["data"]) #获取数据  
    
    ''' 
    数据包结构如下
    
        data_history
            cityStatis 
            chinaDayList 
                confirm
                suspect
                dead
                heal
                nowConfirm
                nowSevere
                importedCase
                deadRate
                healRate
                date
                y
                noInfect
                localConfirm
                noInfectH5
                localConfirmH5
                local_acc_confirm
            chinaDayAddList       
                confirm
                suspect
                dead
                heal
                importedCase
                infect
                localinfectionadd
                localConfirmadd
                deadRate
                healRate
                date
                y
            provinceCompare      
            nowConfirmStatis  
            statisGradeCityDetail
            dailyNewAddHistory
            dailyHistory
            wuhanDayList
            articleList
    '''                              
    
    history_data = {}
    for every_data in data_history["chinaDayList"]:
        date =  every_data["y"] + "." + every_data["date"]
        tup = time.strptime(date, "%Y.%m.%d")
        date = time.strftime("%Y-%m-%d", tup)  # 改变时间格式,不然插入数据库会报错,数据库是datetime类型

        confirm = every_data["confirm"]
        suspect = every_data["suspect"]
        dead = every_data["dead"]
        heal = every_data["heal"]
        # nowConfirm = every_data["nowConfirm"]
        history_data[date] = {"confirm":confirm, "suspect":suspect, "dead":dead, "heal":heal}

    for every_data_add in data_history["chinaDayAddList"]:
        date_add = every_data["y"] + "." + every_data_add["date"]
        tup_add = time.strptime(date_add, "%Y.%m.%d")
        date_add = time.strftime("%Y-%m-%d", tup_add)  # 改变时间格式,不然插入数据库会报错,数据库是datetime类型

        confirm_add = every_data_add["confirm"]
        suspect_add = every_data_add["suspect"]
        dead_add = every_data_add["dead"]
        heal_add = every_data_add["heal"]
        # g更新,update函数可以添加新的键值对
        print(date_add)
        if str(date_add) in history_data.keys():
            history_data[date_add].update({"confirm_add":confirm_add, "suspect_add":suspect_add, "dead_add":dead_add, "heal_add":heal_add})
    
    
    #获取当日数据
    
    r = requests.get(url_today, headers)
    r.encoding = "utf-8"
    res = json.loads(r.text) # json字符串转成字典
    data_today = json.loads(res["data"]) #获取数据
    
    ''' 
    数据包结构如下
    
        data_today
            lastUpdateTime 上次更新时间
            chinaTotal     中国总计
            chinaAdd       中国新增
            isShowAdd      是否展示
            showAddSwitch  展示信息开关
            areaTree :
                    name 中国数据
                    today
                    total
                    children :
                            -name 省级数据 
                            -today
                            -total
                            -children:
                                      -name 市级数据
                                      -today
                                      -total
    '''        
    
    details = []
    update_time = data_today["lastUpdateTime"]
    data_country = data_today["areaTree"]
    data_province = data_country[0]["children"]
    for pro_infos in data_province:
        province = pro_infos["name"]
        for city_infos in pro_infos["children"]:
            city = city_infos["name"]
            confirm = city_infos["total"]["confirm"]
            confirm_add = city_infos["today"]["confirm"]
            heal = city_infos["total"]["heal"]
            dead = city_infos["total"]["dead"]
            details.append([update_time, province, city, confirm, confirm_add, heal, dead])    
          
    return history_data, details

链接数据库,将数据库可持久化

爬取百度热搜方法一致,不再赘述

# 连接数据库
def get_conn():
    #return:链接, 游标
    
    #获取链接
    conn = pymysql.connect(host = "ip",
                       port = 3306,
                    user = "root",
                    password = "123456",
                    db = "cov"
    )
    # 创建游标:
    cursor = conn.cursor()
    return conn, cursor

def close_conn(conn, cursor):
    if cursor:
        cursor.close()
    if conn:
        conn.close()

def update_details():
    cursor = None
    conn = None
    try:
        detail_data = get_tencent_data()[1] # 0是字典数据 1是列表数据
        conn, cursor = get_conn()
        sql = "insert into details(update_time,province,city,confirm,confirm_add,heal,dead) values(%s,%s,%s,%s,%s,%s,%s)"
        sql_query = 'select %s=(select update_time from details order by id desc limit 1)' #对比当前最大时间戳,拿到最后一条数据
        cursor.execute(sql_query, detail_data[0][0])
        if not cursor.fetchone()[0]:
            print(f"{time.asctime()}开始更新最新数据")
            for item in detail_data:
                cursor.execute(sql, item)
            conn.commit()  # 提交事务 update delete insert操作
            print(f"{time.asctime()}更新最新数据完毕")
        else:
            print(f"{time.asctime()}已是最新数据!")
    except:
        traceback.print_exc()
    finally:
        close_conn(conn, cursor)


def update_history():
    cursor = None
    conn = None
    try:
        dic = get_tencent_data()[0]
        print(f"{time.asctime()}开始更新历史数据")
        conn, cursor = get_conn()
        sql = "insert into history values(%s,%s,%s,%s,%s,%s,%s,%s,%s)"
        sql_query = "select confirm from history where ds=%s"
        for key, value in dic.items():
            # item 格式 {'2020-01-13': {'confirm': 41, 'suspect': 0, 'heal': 0, 'dead': 1}
            if not cursor.execute(sql_query, key):
                cursor.execute(sql, [key, value.get("confirm"), value.get("confirm_add"), value.get("suspect"),
                                     value.get("suspect_add"), value.get("heal"), value.get("heal_add"),
                                     value.get("dead"), value.get("dead_add")])
        conn.commit()
        print(f"{time.asctime()}历史数据更新完毕")
    except:
        traceback.print_exc()
    finally:
        close_conn(conn, cursor)

if __name__ == '__main__':
    update_details()
    update_history()        

前端页面和后端路由的实现

路由使用flask框架可以简单实现

from flask import Flask
app = Flask(__name__)

@app.route('/') #路由
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(0.0.0.0)

然后前端使用ajax刷新页面,比如时间的版块就可以用定时调度(每秒)这个实现

function gettime() {
	$.ajax({
		url: "/time",
		timeout: 10000, //超时时间设置为10秒;
		success: function(data) {
			$("#time").html(data)
		},
		error: function(xhr, type, errorThrown) {

		}
	});
}

Echarts可视化大屏

这个可以通过Echarts官网找到对应的示例进行修改,然后从数据库读取数据实现
示例如下:需要Echarts.min.js和China.js

var ec_center = echarts.init(document.getElementById('c2'), "dark");

var mydata = [{'name': '上海', 'value': 318}, {'name': '云南', 'value': 162}];

var ec_center_option = {
    title: {
        text: '',
        subtext: '',
        x: 'left'
    },
    tooltip: {
        trigger: 'item'
    },
    //左侧小导航图标
    visualMap: {
        show: true,
        x: 'left',
        y: 'bottom',
        textStyle: {
            fontSize: 12,
        },
        splitList: [{ start: 1,end: 9 },
            {start: 10, end: 99 },
			{ start: 100, end: 999 },
            {  start: 1000, end: 9999 },
            { start: 10000 }],
        color: ['#8A3310', '#C64918', '#E55B25', '#F2AD92', '#F9DCD1']
    },
    //配置属性
    series: [{
        name: '累计确诊人数',
        type: 'map',
        mapType: 'china',
        roam: false, //拖动和缩放
        itemStyle: {
            normal: {
                borderWidth: .5, //区域边框宽度
                borderColor: '#009fe8', //区域边框颜色
                areaColor: "#ffefd5", //区域颜色
            },
            emphasis: { //鼠标滑过地图高亮的相关设置
                borderWidth: .5,
                borderColor: '#4b0082',
                areaColor: "#fff",
            }
        },
        label: {
            normal: {
                show: false, //省份名称
                color: 'black',
                fontSize: 12,
            },
            emphasis: {
                show: true,
                fontSize: 12,
            }
        },
        data: [] //mydata //数据
    }]
};
ec_center.setOption(ec_center_option);

将项目部署到服务器上

首先,推荐使用docker进行部署,这样比较安全,不会影响到服务器上的其它项目。
首先就是mysql,docker有专门的mysql镜像,我一开始想在一个docker内部装,死活装不上
后来查了一些资料发现,一般mysql是单独开一个docker部署的,于是我按照网上的教程部署好了mysql,将本地数据移植过去,开始进行试验。
这里强调一点,一定要记得去云服务器上打开对应的端口,不然外部无法访问。然后,我很顺利地链接上了数据库。
接下来,由于我不熟悉ftps,我只能去寻求软件来从Windows传输文件到linux服务器。开始我也找了许多软件,要不就是收费,要不就是不好用
收费的破解也有点麻烦,总之我没能按照网上教程破解成功。最后我突然想到我们金仓数据库上用的MobaXterm就有这个功能,我拿过来一实验,果然可以。
于是我赶紧拿我的Django镜像创建了一个docker,端口映射10000:22,准备把本地的代码文件传上去跑起来。传过去了之后,我立马 chmod +x app.py,准备运行,结果,报错了。
稍微一看,我没有加标注信息,于是加上,后来又发现没下包,于是把包也安装上。
但是,woc,又报错了
Python——/usr/bin/env: ‘python(3)\r’: No such file or directory
异常原因是:
DOS系统下和Linux系统下对于换行键的表示不同。
在windows下,用连续的'\r'和'\n'两个字符进行换行。'\r'为回车符,'\n'为换行符,比如原来的'aaabbb'更改为'aaa \n bbb'后输出的结果为:aaa 换行 bbb。
解决方式:
看了很多网上的教程,说用VIM编辑器进行修改,由于我不是很常用VIM编辑器,尝试了几次并没有成功。后来发现可以用dos2unix这个包进行转换。
首先通过apt-get命令安装dos2unix的包,然后通过dos2unix这个命令即可完成转换。

sudo apt-get install dos2unix
dos2unix app.py

好,这下不报错了,开始运行!
确实,程序跑起来了,然后我app.run()直接跑的,下面默认地址是127.0.0.1,端口是5000
我通过我自己电脑浏览器去访问我的服务器ip:5000
结果啥也没访问到
我以为我搞错了,然后取访问ip:10000
嗯,服务器返回了无效的页面,我百思不得其解
于是我搜了一下,改成了app.run(host='0.0.0.0', port=10000)
继续访问,woc!还是访问不了。
又是网上查资料查了几个小时,还是没找出原因
于是我退回到我的服务器上(原本在服务器的docker容器内)
用最简单的flask代码搭了个hello world页面
app.run(host='0.0.0.0', port=10000),然后进行测试
woc!可以访问。
后来仔细检查发现,我创建docker镜像的时候,只开了一个端口而且映射到了22的ssh端口,根本没有开方其它端口
最后,我重新建立了一个docker,把东西重新导了过去(操作有点low),终于,可以访问了
然后记得将代码改成部署到生产环境

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return"Hello!"

if __name__ =="__main__":
    from waitress import serve
    serve(app, host="0.0.0.0", port=8080)

将爬虫部署到服务器上并且定时执行

我将我本地的爬虫传到服务器后,安装了对应的包
然后,遇到的大问题就是selenium的问题,我原来是用selenium写的爬取百度热搜
但是当这个放到服务器上去之后,就跑不了了,为什么?因为我没有安装Chrome
网上搜了一下,要用yum安装,我用的是Ubuntu,不支持yum,试了几下没弄成,我只好作罢
还好,我发现用最普通的方式也能爬到信息,于是我重写了这一部分,没能解决这个坑
最后,就是使用crontab定时执行爬虫就行。

补充

这里对于echarts再补充一点
按照上述情况将程序部署好之后,确实是能正常访问了,但是一旦页面缩放,问题就来了
图形要么就重叠,要么就出现了空缺位置,显然是无法接受的,因为练窗口化都会出现这个问题
然后我也找了一些同学来访问网页,发现有时候还会出现页面一直抖动的情况
解决方式:
抖动的话,在页面的css文件中加入

html, body {height:100%;overflow:auto;margin: 0;}
html{overflow-y:scroll;}

echarts图表自适应div大小

你可以在每个图表的setOption后
添加
window.addEventListener('resize', function () {myChart.resize();})
posted @ 2021-11-27 18:56  beatlesss  阅读(28)  评论(0编辑  收藏  举报