一:题目要求
二:项目分析
数据爬取:借助python爬虫语句,将疫情数据爬取存储到MySQL;
web端:结合前两周的项目,添加世界范围的疫情数据,以及简单的查询功能;
移动端:Android开发,连接MySQL数据库,实现简单的查询功能;
三:项目结构
mysql:
pycharm:
eclipse:
Android studio
四:源代码
python:
import json import numpy as np import pymysql import requests from bs4 import BeautifulSoup import datetime url = 'https://ncov.dxy.cn/ncovh5/view/pneumonia?from=timeline&isappinstalled=0' #请求地址 headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'}#创建头部信息 response = requests.get(url,headers = headers) #发送网络请求 #print(response.content.decode('utf-8'))#以字节流形式打印网页源码 content = response.content.decode('utf-8') #print(content) soup = BeautifulSoup(content, 'html.parser') listA = soup.find_all(name='script',attrs={"id":"getAreaStat"}) #世界确诊getAreaStat listB = soup.find_all(name='script',attrs={"id":"getListByCountryTypeService2true"}) #listA = soup.find_all(name='div',attrs={"class":"c-touchable-feedback c-touchable-feedback-no-default"}) account = str(listA) world_messages = str(listB)[95:-21] messages = account[52:-21] messages_json = json.loads(messages) print(world_messages) world_messages_json = json.loads(world_messages) valuesList = [] cityList = [] worldList = [] now_time = datetime.datetime.now().strftime('%Y-%m-%d') for k in range(len(world_messages_json)): worldvalue = (now_time, world_messages_json[k].get('countryType'),world_messages_json[k].get('continents'),world_messages_json[k].get('provinceId'),world_messages_json[k].get('provinceName'), world_messages_json[k].get('provinceShortName'),world_messages_json[k].get('cityName'),world_messages_json[k].get('currentConfirmedCount'),world_messages_json[k].get('confirmedCount'), world_messages_json[k].get('suspectedCount'),world_messages_json[k].get('curedCount'),world_messages_json[k].get('deadCount'),world_messages_json[k].get('locationId'), world_messages_json[k].get('countryShortCode'),) worldList.append(worldvalue) for i in range(len(messages_json)): #value = messages_json[i] value = (now_time,messages_json[i].get('provinceName'),messages_json[i].get('provinceShortName'),messages_json[i].get('currentConfirmedCount'),messages_json[i].get('confirmedCount'),messages_json[i].get('suspectedCount'),messages_json[i].get('curedCount'),messages_json[i].get('deadCount'),messages_json[i].get('comment'),messages_json[i].get('locationId'),messages_json[i].get('statisticsData')) valuesList.append(value) cityValue = messages_json[i].get('cities') #print(cityValue) for j in range(len(cityValue)): cityValueList = (cityValue[j].get('cityName'),cityValue[j].get('currentConfirmedCount'),cityValue[j].get('confirmedCount'),cityValue[j].get('suspectedCount'),cityValue[j].get('curedCount'),cityValue[j].get('deadCount'),cityValue[j].get('locationId'),messages_json[i].get('provinceShortName')) #print(cityValueList) cityList.append(cityValueList) #cityList.append(cityValue) db = pymysql.connect("localhost", "root", "123456", "mytest", charset='utf8') cursor = db.cursor() array = np.asarray(valuesList[0]) #sql_clean_world = "TRUNCATE TABLE world_map" sql_clean_city = "TRUNCATE TABLE city_map" sql_clean_json = "TRUNCATE TABLE province_data_from_json" sql_clean_province = "TRUNCATE TABLE province_map" sql_clean_world = "TRUNCATE TABLE world_map" sql1 = "INSERT INTO city_map values (%s,%s,%s,%s,%s,%s,%s,%s)" sql_world = "INSERT INTO world_map values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" #sql = "INSERT INTO province_map values (0,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s') " sql = "INSERT INTO province_map values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) " #sql = "INSERT INTO province_map (provinceName,provinceShortName,correntConfirmedCount,confirmedCount,suspectedCount,curedCount,deadCount,comment,locationId,statisticsData) values (0,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s') " #sql = """INSERT INTO province_map (provinceName,provinceShortName,correntConfirmedCount,confirmedCount,suspectedCount,curedCount,deadCount,comment,locationId,statisticsData) values ('湖北省', '湖北', 43334, 64786, 0, 18889, 2563, '', 420000, 'https://file1.dxycdn.com/2020/0223/618/3398299751673487511-135.json')""" value_tuple = tuple(valuesList) cityTuple = tuple(cityList) worldTuple = tuple(worldList) print(worldTuple) print(tuple(value_tuple)) try: #cursor.execute(sql_clean_city) #cursor.execute(sql_clean_province) #cursor.executemany(sql, value_tuple) #cursor.executemany(sql1,cityTuple) db.commit() except: print('执行失败,进入回调1') db.rollback() try: #cursor.execute(sql_clean_city) #cursor.execute(sql_clean_province) #cursor.execute(sql_clean_world) #cursor.executemany(sql, value_tuple) #cursor.executemany(sql1,cityTuple) cursor.executemany(sql_world, worldTuple) db.commit() except: print('执行失败,进入回调2') db.rollback() try: #cursor.execute(sql_clean_city) #cursor.execute(sql_clean_province) cursor.executemany(sql, value_tuple) #cursor.executemany(sql1,cityTuple) db.commit() except: print('执行失败,进入回调3') db.rollback() try: #cursor.execute(sql_clean_city) #cursor.execute(sql_clean_province) #cursor.executemany(sql, value_tuple) #cursor.executemany(sql1,cityTuple) db.commit() except: print('执行失败,进入回调4') db.rollback() #print(messages_json) #print(account[52:-21]) # soupDiv = BeautifulSoup(listA,'html.parser') # listB = soupDiv.find_all(name='div',attrs={"class":"c-gap-bottom-zero c-line-clamp2"}) #for i in listA: #print(i) #listA[12] #print(listA) db.close()
android:
package com.example.myapplication; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * 数据库工具类:连接数据库用、获取数据库数据用 * 相关操作数据库的方法均可写在该类 */ public class DBUtils { private static String driver = "com.mysql.jdbc.Driver";// MySql驱动 private static String user = "root";// 用户名 private static String password = "123456";// 密码 private static Connection getConn(String dbName) { Connection connection = null; try { Class.forName(driver);// 动态加载类 String ip = "192.168.0.101";// 写成本机地址,不能写成localhost,同时手机和电脑连接的网络必须是同一个 // 尝试建立到给定数据库URL的连接 connection = DriverManager.getConnection("jdbc:mysql://" + ip + ":3306/" + dbName, user, password); } catch (Exception e) { e.printStackTrace(); } return connection; } public static List<information> search(String condition, String country_name){ List<information> list = new ArrayList<>(); Connection connection = getConn("mytest"); String sql = ""; //System.out.println(condition); //选择条件 if(condition.equals("国家")){ //模糊查询 sql = "select * from world_map where provinceName like ?"; } if(condition.equals("时间")){ sql = "select * from world_map where date like ?"; } System.out.println(country_name); if(connection !=null){ try { PreparedStatement ps = connection.prepareStatement(sql); if(ps!=null){ ps.setString(1,"%"+country_name+"%"); ResultSet rs = ps.executeQuery(); if(rs!=null){ while(rs.next()){ information worldData = new information(); worldData.setCountryname(rs.getString("provinceName")); worldData.setConfirmed(rs.getString("confirmedCount")); worldData.setSuspected(rs.getString("suspectedCount")); worldData.setDead(rs.getString("deadCount")); worldData.setHealed(rs.getString("curedCount")); worldData.setLastUpdateTime(rs.getString("date")); list.add(worldData); } connection.close(); ps.close(); return list; }else{ return null; } }else{ return null; } } catch (SQLException e) { e.printStackTrace(); return null; } }else{ return null; } } }
package com.example.myapplication; public class information { private int id; private String countryname; private String confirmed; private String suspected; private String dead; private String healed; private String lastUpdateTime; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCountryname() { return countryname; } public void setCountryname(String countryname) { this.countryname = countryname; } public String getConfirmed() { return confirmed; } public void setConfirmed(String confirmed) { this.confirmed = confirmed; } public String getSuspected() { return suspected; } public void setSuspected(String suspected) { this.suspected = suspected; } public String getDead() { return dead; } public void setDead(String dead) { this.dead = dead; } public String getHealed() { return healed; } public void setHealed(String healed) { this.healed = healed; } public String getLastUpdateTime() { return lastUpdateTime; } public void setLastUpdateTime(String lastUpdateTime) { this.lastUpdateTime = lastUpdateTime; } }
package com.example.myapplication; import android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; import java.util.List; @SuppressLint("Registered") public class MainActivity extends Activity { private EditText et_name; private Button btn_get_data; private TextView tv_data; private RadioGroup rg_check; private RadioButton rb_date; private RadioButton rb_country; private String condition; @SuppressLint("HandlerLeak") private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 0x11: String s = (String) msg.obj; tv_data.setText(s); break; case 0x12: String ss = (String) msg.obj; tv_data.setText(ss); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 控件的初始化 btn_get_data = findViewById(R.id.btn_get_data); tv_data = findViewById(R.id.tv_data) ; et_name = findViewById(R.id.et_name); rb_date = findViewById(R.id.rb_date); rb_country = findViewById(R.id.rb_country); rg_check = findViewById(R.id.rg_select); rg_check.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { public void onCheckedChanged(RadioGroup group, int checkedId) { //如果‘时间’这个单选按钮被选中了 if(rb_date.getId()==checkedId){ //弹出吐司通知 //Toast.makeText(MainActivity.this, rb_date.getText().toString(), Toast.LENGTH_LONG).show(); //获取选中按钮对应的文本信息 condition = rb_date.getText().toString().trim(); }else if(rb_country.getId()==checkedId){ //Toast.makeText(MainActivity.this, rb_country.getText().toString(), Toast.LENGTH_LONG).show(); condition = rb_country.getText().toString().trim(); } } }); //如果没有选择默认按时间查询 if (condition == null){ condition = rb_date.getText().toString().trim(); } setListener(); } /** * 设置监听 */ private void setListener() { // 按钮点击事件 btn_get_data.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 创建一个线程来连接数据库并获取数据库中对应表的数据 new Thread(new Runnable() { @Override public void run() { String name = et_name.getText().toString().trim(); //调用数据库帮助类中的方法取数据 List<information> list = DBUtils.search(condition,name); Message message = handler.obtainMessage(); if (list != null) { String s = ""; for (int i = 0; i < list.size(); i++) { s += "国家:" + list.get(i).getCountryname() + " "; s += "最新更新时间:" + list.get(i).getLastUpdateTime() + " "; s += "确诊人数为: " + list.get(i).getConfirmed() + " "; s += "治愈人数为: " + list.get(i).getHealed() + " "; s += "死亡人数为: " + list.get(i).getDead() + " " + " "; } //0x11、0x12消息的定位标志 message.what = 0x12; message.obj = s; } else { message.what = 0x11; message.obj = "查询结果为空"; } handler.sendMessage(message); // 发消息通知主线程更新UI } }).start(); } }); } }
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:padding="15dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <RadioGroup android:id="@+id/rg_select" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingLeft="20dp" android:layout_marginTop="20dp"> <RadioButton android:id="@+id/rb_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="时间" android:textSize="20sp" android:checked="true"/> <RadioButton android:id="@+id/rb_country" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="国家" android:textSize="20sp"/> </RadioGroup> <EditText android:id="@+id/et_name" android:padding="10dp" android:textSize="16sp" android:gravity="center" android:hint="输入要查询的内容" android:layout_below="@id/rg_select" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_get_data" android:layout_margin="15dp" android:textSize="16sp" android:text="查询" android:layout_below="@id/et_name" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_data" android:padding="10dp" android:textSize="16sp" android:gravity="center" android:text="内容" android:layout_below="@+id/btn_get_data" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout> </ScrollView>
五:运行截图
六:遇到的问题
1:AVD模拟器无法启动,出现 emulator:PANIC:cannot find AVD system path.please define ANDROID_SDK_ROOT 错误。
解决方法: 确保环境变量正确,若仍出现此错误采取以下方法:
删除 AVD 里所有的镜像。在系统环境变量里设置:
- 新增 ANDROID_SDK_HOME 环境变量。其值为 D 盘一个新建的目录
- ANDROID_HOME 原来就有,无需修改
然后重启 Android Studio,使环境变量生效。
再次打开 Tools -> AVD Manager 安装一个镜像,启动即可。
然后就可以正常启动模拟器了。
2:Android无法连接到数据库 :Communications link failure
解决方法:将ip地址改为本机ip地址。若仍出现此错误,将jdbc 的jar包更换为低版本。
七:时间表
阶段 | 预估时间 | 实耗时间 | 出现问题 | 解决方案 |
爬取数据 | 30min | 25min | 获取数据无法转换为json格式 | 打印输出爬取数据,删除多余字符 |
编写web查询 | 15min | 18min | ||
绘制世界地图 | 1h | 2h | 找不到world.js资源;地图无法显示 | 借助网络查询 |
Android连接MySQL | 1h | 6h | 连接数据库失败 | 将jdbc-connector-java.jar驱动换为低版本 |
编写Android查询 |
1h |
45min |