项目

项目,第1张

文章目录 基于网络爬虫的疫情数据可视化分析0、前言1、背景2、技术栈2.1、后端2.2、前端 3、设计思路3.1、本地开发阶段3.2、上线部署阶段 4、实现效果4.1、后端效果4.2、前端效果 5、实现步骤5.1、导入依赖5.2、HttpClient爬取数据5.3、Jsoup解析数据5.4、创建Java实体类5.5、JSON与实体类映射5.6、数据持久化和可视化

基于网络爬虫的疫情数据可视化分析
0、前言

这个是去年9月份,大三上学期《大数据技术应该开发》的课程设计,这个作品我很早就想做了,但是因为课业加上项目,所以一直耽搁,所以就当成大数据课程作业做了。虽然做了一个月,只是因为个人技术菜,其实做了一小部分的功能,我也拿着这个作品去参加了很多的比赛,目前拿到了校内比赛“大数据应用大赛”的一等奖,还有另外一个比赛的三等奖。

其他有想法的小伙伴可以加上一些其他的功能,完善之后可以去参加计算机设计大赛,单拿这个去参赛还是比较单薄的,加上自己的想法完善一下也是一个不错的选择。

加上自己的想法,换上一个好看的模板(之前上传一些模板),继续优化优化,让内容更加丰富,就可以去参加很多比赛了。

资料包括:

后端源码(含数据库脚本)前端源码报告(50页左右,大概10000字)

源码下载:基于网络爬虫的新冠肺炎疫情数据可视化分析。

资源下载:可视化大屏模板。


1、背景

唉……大学才四年,疫情占三年。


2、技术栈 2.1、后端

Spring Boot、MyBatis、Druid、HttpClient、Jsoup、Fastjson、MySQL、Maven。

2.2、前端

ECharts、Vue.js、Axios、JavaScript、CSS、HTML。


3、设计思路 3.1、本地开发阶段

基于爬虫技术在 https://ncov.dxy.cn/ncovh5/view/pneumonia 爬取的疫情数据,之后使用 Jsoup 解析器对数据进行解析处理得到原始的 JSON 数据,再将 JSON 数据转化为Java 实体类并进行持久化处理,基于ECharts对数据进行可视化展示。

3.2、上线部署阶段

为达到对疫情数据实时监控的效果,采用实时爬取数据的方式,开启 Spring Boot 的定时任务并将项目部署至阿里云服务器,于每日早上八点和晚上十点各爬取一次数据,并对数据进行处理和持久化 *** 作,全天不间断地向前端输出接口,以保证数据的时效性、精确性。


4、实现效果 4.1、后端效果





4.2、前端效果



5、实现步骤

数据持久化使用MyBatis很简单,数据展示使用ECharts也没什么难度。

稍有难度就是数据的爬取和处理了,所以在此处简单地将一下。


5.1、导入依赖

后端项目目录。

首先,创建一个Spring Boot工程项目,导入HttpClient和Jsoup的依赖。


5.2、HttpClient爬取数据

TimeUtils.java工具类源码:

/**
 * Author Mr.Zhang
 * Date 2020/5/27 11:23
 * Desc 时间工具类
 */
public abstract class TimeUtils {
    public static String format(Long timestamp, String pattern) {
        return FastDateFormat.getInstance(pattern).format(timestamp);
    }
}

HttpUtils.java工具类源码:

/**
 * Author Mr.Zhang
 * Date 2021/9/10 16:05
 * Desc 封装HttpClient工具,方便爬取网页内容
 */
@Component
public class HttpUtils {
    //声明httpClient管理器对象(HttpClient连接池)
    private static PoolingHttpClientConnectionManager cm = null;
    private static RequestConfig config = null;
    private static List<String> userAgentList = null;

    //静态代码块会在类被加载的时候执行
    static {
        cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(200);
        cm.setDefaultMaxPerRoute(20);
        config = RequestConfig.custom()
                .setSocketTimeout(10000)
                .setConnectTimeout(10000)
                .setConnectionRequestTimeout(10000)
                .build();
        userAgentList = new ArrayList<String>();
        userAgentList.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36");
        userAgentList.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:73.0) Gecko/20100101 Firefox/73.0");
        userAgentList.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15");
        userAgentList.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299");
        userAgentList.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");
        userAgentList.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0");

    }

    /**
     * 获取页面的HTML的源码字符串
     *
     * @param url 页面的URL地址
     * @return HTML的源码字符串
     */
    public String getHtml(String url) {
        //1.从连接池中获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
        //2.创建HttpGet对象
        HttpGet httpGet = new HttpGet(url);
        //3.设置请求配置对象和请求头
        httpGet.setConfig(config);
        httpGet.setHeader("User-Agent", userAgentList.get(new Random().nextInt(userAgentList.size())));
        //4.发起请求
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            //5.获取响应内容
            if (response.getStatusLine().getStatusCode() == 200) {
                String html = "";
                if (response.getEntity() != null) {
                    html = EntityUtils.toString(response.getEntity(), "UTF-8");
                }
                return html;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //httpClient.close();//注意:这里的HttpClient是从cm(连接池)中获取的,不需要关闭
        }
        return null;
    }
}

分析:封装HttpClient工具类,对HttpClient连接池进行配置,对外提供一个静态方法getHtml(String url),方便爬取网页的内容。

传入https://ncov.dxy.cn/ncovh5/view/pneumonia,作为getHtml的参数。


5.3、Jsoup解析数据

Covid19DataCrawler.java的源码:

/**
 * 实现疫情数据爬取
 */
@Component
public class Covid19DataCrawler {
    private static final Logger logger = LoggerFactory.getLogger(Covid19DataCrawler.class);
    @Autowired
    private HttpUtils httpUtils;
    @Autowired
    private ProvinceCovidService provinceCovidService;
    @Autowired
    private CityCovidService cityCovidService;
    @Autowired
    private StatisticsDataService statisticsDataService;

    //后续需要将该方法改为定时任务,如每天8点定时爬取疫情数据
    //@Scheduled(initialDelay = 1000, fixedDelay = 1000 * 60 * 60 * 24)
//    @Scheduled(cron = "0 8 8,0 * * ?")
    @Scheduled(cron = "0 22 20,0 * * ?")   //每天早的八点半,晚十点半,各更新执行一次
    public void crawling() {
        //获取当前时间
        String dateTime = TimeUtils.format(System.currentTimeMillis(), "yyyy-MM-dd");
        logger.info("当前系统的时间为:{}", dateTime);
        //1.爬取指定页面
        String html = httpUtils.getHtml("https://ncov.dxy.cn/ncovh5/view/pneumonia");

        //2.解析页面中指定内容
        //获取 id = getAreaStat 的全国疫情数据
        Document document = Jsoup.parse(html);
        String text = document.select("script[id=getAreaStat]").toString();

        //3.使用正则表达式获取json格式的疫情数据
        //定义正则规则
        String regex = "\[(.*)\]";
        //编译成正则对象
        Pattern pattern = Pattern.compile(regex);
        //去text中进行匹配
        Matcher matcher = pattern.matcher(text);
        String jsonStr = "";
        if (matcher.find()) {
            jsonStr = matcher.group(0);
            //由各省份组成的JSON字符串,包含省份和下属城市
            logger.info("已经爬取到截止至{}数据", dateTime);
        } else {
            logger.info("no match");
        }

        //对JSON数据进行进一步解析
        //4.将第一层json(省份数据)解析为JavaBean
        List<CovidData> provincesCovidDataList = JSON.parseArray(jsonStr, CovidData.class);  //由各个省份CovidData组成的List集合

        logger.info("已经爬取到 {} 个省份的数据", provincesCovidDataList.size());       //全国34个省(包括港澳台)
        Integer provinceCount = 0;      //省份的计数变量
        for (CovidData provinceCovidData : provincesCovidDataList) {     //provinceCovidData为省份的数据
            provinceCovidData.setDatetime(dateTime);        //设置省份实体的时间
            provinceCount++;
            //5.获取每个省份的每一天的新冠数据的统计数据
            //System.out.println("count-- +" + count + "每天统计数据的URL:" + provinceCovidData.getStatisticsData());      //一共有34条URL,对应34个省份
            String provinceStatisticsDataJsonStr = httpUtils.getHtml(provinceCovidData.getStatisticsData());
            //5.1、provinceStatisticsDataJsonStr有两个字段,我们获取其中的“data”字段,“data”字段的值是某省每一天的统计数据(JSON数据格式)
            String statisticsDataStr = JSON.parseObject(provinceStatisticsDataJsonStr).getString("data");
            //5.2、把解析出来的每一天的统计数据设置回省份provinceCovidData的statisticsData中,因为之前存放的只是一个URL路径
//            provinceCovidData.setStatisticsData(statisticsDataStr);       //这个字段太长了,所以新建一个表单独分开

            /*
                CovidData的省份部分数据已经爬取完毕,实例化新冠数据省份实体类ProvinceCovid
             */
            ProvinceCovid provinceCovid = new ProvinceCovid();
            provinceCovid.setProvinceName(provinceCovidData.getProvinceName());
            provinceCovid.setProvinceShortName(provinceCovidData.getProvinceShortName());
            provinceCovid.setCurrentConfirmedCount(provinceCovidData.getCurrentConfirmedCount());
            provinceCovid.setConfirmedCount(provinceCovidData.getConfirmedCount());
            provinceCovid.setSuspectedCount(provinceCovidData.getSuspectedCount());
            provinceCovid.setCuredCount(provinceCovidData.getCuredCount());
            provinceCovid.setDeadCount(provinceCovidData.getDeadCount());
            provinceCovid.setLocationId(provinceCovidData.getLocationId());
            provinceCovid.setHighDangerCount(provinceCovidData.getHighDangerCount());
            provinceCovid.setMidDangerCount(provinceCovidData.getMidDangerCount());
            provinceCovid.setDatetime(provinceCovidData.getDatetime());
            provinceCovidService.addProvinceCovid(provinceCovid);           //将省份数据存入数据库中
            logger.info("已持久化第 {} 个省份的数据:{} ", provinceCount, provinceCovid.getProvinceName());
            logger.info("{} 当前确诊人数:{}", provinceCovid.getProvinceName(), provinceCovid.getCurrentConfirmedCount());
            logger.info("{} 累计确诊人数:{}", provinceCovid.getProvinceName(), provinceCovid.getConfirmedCount());
            logger.info("{} 疑似病例人数:{}", provinceCovid.getProvinceName(), provinceCovid.getSuspectedCount());
            logger.info("{} 治愈人数:{}", provinceCovid.getProvinceName(), provinceCovid.getCuredCount());
            logger.info("{} 死亡人数:{}", provinceCovid.getProvinceName(), provinceCovid.getDeadCount());
            logger.info("统计时间:{}", provinceCovid.getDatetime());

            //5.3、把provinceStatisticsDataJsonStr解析成StatisticsData实体对象集合
            List<StatisticsData> statisticsDataList = JSON.parseArray(statisticsDataStr, StatisticsData.class);
            for (StatisticsData statisticsData : statisticsDataList) {
                //psId=null, provinceId=null, provinceName='null',dateId='20210703',其中,psId不用管,
                String provinceName = provinceCovidData.getProvinceName();
                statisticsData.setProvinceName(provinceName);//设置provinceName
                /*
                    将StatisticsData实体对象存入到数据库中
                 */
                statisticsDataService.addStatisticsData(statisticsData);
            }
            logger.info("统计数量:{}", statisticsDataList.size());
            logger.info("已持久化 {} 的统计数据", provinceCovidData.getProvinceName());

            //6、获取每个省份的下属城市的数据
            String citiesCovidDataJsonStr = provinceCovidData.getCities();      //每个省份包含城市组成的JSON字符串
            //System.out.println(provinceCovidData.getProvinceName() + "的下属城市:");   //可以正常打印出34个省份的各个城市
            //6.1、解析省份下属的城市
            List<CityCovid> citiesCovidDataList = JSON.parseArray(citiesCovidDataJsonStr, CityCovid.class);     //由当前省份下属城市CityCovid组成的List集合
            for (CityCovid cityCovid : citiesCovidDataList) {
                cityCovid.setDatetime(dateTime);        //给城市实体设置时间
                cityCovid.setProvinceId(provinceCovidService.getProvinceIdByProvinceName(provinceCovidData.getProvinceName()));   //设置城市所属的省份id
                /*
                    将当前省份的下属城市数据存入数据库中
                 */
                cityCovidService.addCityCovid(cityCovid);
                logger.info("已持久化 {} 的 {} 的数据", provinceCovidData.getProvinceName(), cityCovid.getCityName());
            }
        }
    }
}

分析:使用Jsoup的id选择器,获取"script[id=getAreaStat]"疫情数据,再使用正则表达式"\[(.*)\]"对疫情数据进行匹配,获取到由每个省份的新冠肺炎疫情数据组成的JSON数组。

 //1.爬取指定页面
String html = HttpUtils.getHtml("https://ncov.dxy.cn/ncovh5/view/pneumonia");
//2.解析页面中指定内容
//获取 id = getAreaStat 的全国疫情数据
Document document = Jsoup.parse(html);
String text = document.select("script[id=getAreaStat]").toString();
//3.使用正则表达式获取json格式的疫情数据
String regex = "\[(.*)\]";  //定义正则规则
Pattern pattern = Pattern.compile(regex);   //编译成正则对象
Matcher matcher = pattern.matcher(text);    //去text中进行匹配

使用JSON校验工具对JSON数据进行格式化。

其中“statisticsData”字段的值是一个URL地址,需要再解析一次,如图所示。解析后得到JSON字符串,我们再拿到该字符串的data字段即可。

使用JSON校验工具对JSON数据进行格式化。


5.4、创建Java实体类

根据爬取得到的新冠肺炎疫情数据的JSON字符串的字段,创建省份、城市和统计数据三个实体类,将JSON数据的字段与实体类的属性一一对应,再使用Fastjson工具进行转换。

/**
 * 省份实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProvinceCovid implements Serializable {
    private Integer provinceId;              //省份id
    private String provinceName;             //省份名称
    private String provinceShortName;        //省份短名
    private Integer currentConfirmedCount;   //当前确诊人数
    private Integer confirmedCount;          //累记确诊人数
    private Integer suspectedCount;          //疑似病例人数
    private Integer curedCount;              //治愈人数
    private Integer deadCount;               //死亡人数
    private Integer locationId;              //当前省份的位置id
    private Integer highDangerCount;         //高风险地区
    private Integer midDangerCount;          //低风险地区
private String datetime;                 //时间
}
/**
 * 城市实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CityCovid implements Serializable {
    private Integer cityId;                     //城市id
    private Integer provinceId;                 //所属省份id
    private String cityName;                    //城市名
    private Integer locationId;                 //城市的位置id
    private Integer currentConfirmedCount;      //当前确诊人数
    private Integer confirmedCount;             //累记确诊人数
    private Integer suspectedCount;             //疑似病例人数
    private Integer curedCount;                 //治愈人数
    private Integer deadCount;                  //死亡人数
    private Integer highDangerCount;            //高风险地区
    private Integer midDangerCount;             //中风险地区
private String datetime;                    //时间
}
/*
    统计数据实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StatisticsData implements Serializable {
    private Integer psId;                    //统计数据id
    private String provinceName;             //所属省份名
    private Integer confirmedCount;          //累计确诊人数
    private Integer confirmedIncr;           //新增累计确诊人数
    private Integer curedCount;              //治愈人数
    private Integer curedIncr;               //新增治愈人数
    private Integer currentConfirmedCount;   //当前确诊人数
    private Integer currentConfirmedIncr;    //新增当前确诊人数
    private Integer deadCount;               //死亡人数
    private Integer deadIncr;                //死亡人数新增
    private Integer highDangerCount;         //高风险地区
    private Integer midDangerCount;          //中风险地区
    private Integer suspectedCount;          //疑似病例
    private Integer suspectedCountIncr;      //新增疑似病例
private String dateId;                   //时间格式:yyyyMMdd,如20200119
}

5.5、JSON与实体类映射

Fastjson工具包提供了一个抽象类JSON,该类提供了两个方法,parseArray(String jsonStr)和parseObject(),根据需要调用即可。

//将JSON字符串转化为Class类实例化对象的List集合。
public static <T> java.util.List<T> parseArray(@Nullable String text, Class<T> clazz);

//将JSON字符串转化为Class类的实例化对象。
public static <T> T parseObject(@Nullable String text,Class<T> clazz);

5.6、数据持久化和可视化

至5.5,数据已经封装到Java的实体类中了,接下来就是常规的MyBatis持久化存储,然后通过Controller提供接口给前端进行数据可视化展示。

也就是常规的Controller、Service、Dao层代码的编写,这部分就不说了,不是重点,也没难度。

接下来以StatisticsDataController.java为例简单演示。

@Controller
@RequestMapping("/statisticsData")
public class StatisticsDataController {
    private final static Logger logger = LoggerFactory.getLogger(CityCovidController.class);

    @Autowired
    private ProvinceCovidService provinceCovidService;

    @GetMapping("/getCountrySumDeadCount")
    @ResponseBody
    public String getCountrySumDeadCount() {
        Integer sumDeadCount = provinceCovidService.getSumDeadCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumDeadCount", sumDeadCount);
        jsonObject.put("comment", "全国累计死亡人数");
        logger.info("/statisticsData/getCountrySumDeadCount");
        logger.info("已获取到全国累计死亡人数:{}", sumDeadCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumCuredCount")
    @ResponseBody
    public String getCountrySumCuredCount() {
        Integer sumCuredCount = provinceCovidService.getSumCuredCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumCuredCount", sumCuredCount);
        jsonObject.put("comment", "全国累计治愈人数");
        logger.info("/statisticsData/getCountrySumCuredCount");
        logger.info("已获取到全国累计治愈人数:{}", sumCuredCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumCurrentConfirmedCount")
    @ResponseBody
    public String getCountrySumCurrentConfirmedCount() {
        Integer sumCurrentConfirmedCount = provinceCovidService.getSumCurrentConfirmedCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumCurrentConfirmedCount", sumCurrentConfirmedCount);
        jsonObject.put("comment", "全国现存确诊人数");
        logger.info("/statisticsData/getCountrySumCurrentConfirmedCount");
        logger.info("已获取到全国现存确诊人数(当前确诊人数):{}", sumCurrentConfirmedCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumConfirmedCount")
    @ResponseBody
    public String getCountrySumConfirmedCount() {
        Integer sumConfirmedCount = provinceCovidService.getSumConfirmedCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumConfirmedCount", sumConfirmedCount);
        jsonObject.put("comment", "全国累计确诊人数");
        logger.info("/statisticsData/getCountrySumConfirmedCount");
        logger.info("已获取到全国累计确诊人数:{}", sumConfirmedCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumHighDangerCount")
    @ResponseBody
    public String getCountrySumHighDangerCount() {
        Integer sumHighDangerCount = provinceCovidService.getSumHighDangerCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumHighDangerCount", sumHighDangerCount);
        jsonObject.put("comment", "全国现存高风险地区的数量");
        logger.info("/statisticsData/getCountrySumHighDangerCount");
        logger.info("已获取到全国现存高风险地区的数量:{}", sumHighDangerCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumMidDangerCount")
    @ResponseBody
    public String getCountrySumMidDangerCount() {
        Integer sumMidDangerCount = provinceCovidService.getSumMidDangerCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumMidDangerCount", sumMidDangerCount);
        jsonObject.put("comment", "全国现存中风险地区的数量");
        logger.info("/statisticsData/getCountrySumMidDangerCount");
        logger.info("以获取到全国现存中风险地区的数量:{}", sumMidDangerCount);
        return jsonObject.toJSONString();
    }

    @GetMapping("/getCountrySumSuspectedCount")
    @ResponseBody
    public String getCountrySumSuspectedCount() {
        Integer sumSuspectedCount = provinceCovidService.getSumSuspectedCount();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sumSuspectedCount", sumSuspectedCount);
        jsonObject.put("comment", "全国现存疑似病例");
        logger.info("/statisticsData/getCountrySumSuspectedCount");
        logger.info("全国现存疑似病例:{}", sumSuspectedCount);
        return jsonObject.toJSONString();
    }
}

至此,这个小项目算是完成了……

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1322804.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存