这次是通过全国天气预报的网站去抓取实时天气和预测未来24小时的天气,抓取的数据包括:地区的区号、地区名称、实时气温、湿度、风向、风力、预测的最高温和最低温、晚间风向、日间的风向和各自的风力大小,把这些数据存储到mysql的数据库当中,并利用pygal库来对数据进行可视化操作。
通过分析可以看到一个数据的接口,可以获取全国的地区码,或者点击地图上的区域也能找到 http://forecast.weather.com.cn/napi/h5map/city/10113/jQuery1537792324377?callback=jQuery1537792324377,city后面的数字是要查询的地区码,只要知道地区码就可以获取到该地区的天气预报。
http://www.weather.com.cn/static/html/weather.shtml
目标链接:
http://forecast.weather.com.cn/napi/h5map/city/101/jQuery1537791723170?callback=jQuery1537791723170
从该链接可以获取到所有地区的地区号和地区名称,参数jQuery1537791723170后面的数字1537791723170是当前的时间戳,可以通过本机的当前时间来替代,实现实时性。
http://forecast.weather.com.cn/napi/h5map/city/10113/jQuery1537792324377?callback=jQuery1537792324377
该链接是跟上面获取到的地区码去通过字符串的拼接得到新的url,再去抓取该地区的天气预报。
import pymsql---数据库的操作
import requests---网页抓取
import re----正则表达式的使用
import json----数据格式的转换
import time----获取当前的时间
import pygal----数据可视化操作
编写一个类来实现网页的抓取。
# coding:utf-8
import requests
import re
import json
import time
class WeatherSpider():
"""docstring for WeatherSpider"""
def __init__(self):
self.headers={
'Accept-Encoding':'gzip, deflate',
'Connection':'keep-alive',
'Cookie': 'vjuids=-9fcc3e115.165947a9a2a.0.48bc4b39849d4; UM_distinctid=165947a9ae1324-0ae8457e8abdee-47e1039-100200-165947a9ae219e; f_city=%E5%B9%BF%E5%B7%9E%7C101280101%7C; returnUrl=%2Fweb%2Fdashboard%2Fmobile%2Findex.do; vjlast=1535794387.1536491754.11; Hm_lvt_080dabacb001ad3dc8b9b9049b36d43b=1536491927,1536491942,1536492078,1536492148; Hm_lpvt_080dabacb001ad3dc8b9b9049b36d43b=1536492148; Wa_lvt_1=1536491927,1536491942,1536492079,1536492148; Wa_lpvt_1=1536492148',
'Host':'forecast.weather.com.cn',
'Upgrade-Insecure-Requests':'1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
self.url='http://forecast.weather.com.cn/napi/h5map/city/101/jQuery'
self.city_url='http://forecast.weather.com.cn/napi/h5map/city/'
def get_page(self,url):
# 请求头的设置
try:
response=requests.get(url,headers=self.headers)
# 获取内容,byte类型
return response.content
except Exception as e:
print(e)
return None
def parase_page(self,url):
# 返回bytes类型
json_content=self.get_page(url)
# python3的字符串默认为unicode格式(无编码)
# 先进行解码再进行编码再解码
# encode就是对字符串进行编码作为字节对象返回
if json_content !=None:
try:
# decode就是对字节进行解码作为字符串对象返回
data=json_content.decode('utf-8').encode('utf-8').decode('utf-8')
# 正则表达式匹配
detact=re.search('jQuery[0-9]{13}',data).group()
except Exception as e:
print("出错:"+e)
print(json_content)
pass
else:
# 去除jQuery1536491942006跟括号
data=data.replace(detact,'')[1:-1]
# 转换为字典格式
results=json.loads(data)['result']
return results
# 获取系统当前的时间
def get_current_time(self):
# 得到当前时间的时间戳,float类型
cur_time=time.time()
return str(int(cur_time*1000))
# 获取每一个省份、自治区、特别行政区、直辖市的areaid
def get_areaid(self,results):
for area,value in results.items():
yield{
'area_name':area,
'area_id':value['area']['areaid']
}
# print("区域名:"+area)
# print(value['area']['areaid'])
# 根据数据库中每一个省份、自治区、特别行政区、直辖市的areaid再去查询他们每个城市的气温
def get_weather(self,city_url):
city_weathers=self.parase_page(city_url)
'''
forecast24h是预测未来24小时的天气:maxtem为最高气温,mintep为最低气温,
dayweather、nightweather均为天气代码,dayws为白天风力大小,daywd为白天风向,
nightws为晚间风力大小,nightwd为晚间风向
obs里面是实时天气:tem为气温,rh为相对湿度,ws为风力大小,wd为风向
'''
# print(city_weathers)
if city_weathers != None:
return city_weathers
else:
return None
# 解析未来24小时的城市天气和实时天气
def parse_city_weather(self,city_weathers,column,area_name):
'''
city_weathers:字典格式的天气数据,
column:传入的是未来24小时的天气还是实时天气
area_name:是地区属于的省份或者直辖市或者其他的
'''
if 'forecast24h' in column:
if city_weathers !=None:
for key,forecast_data in city_weathers.items():
if forecast_data !=None:
yield{
'city_name':key,
'city_id':forecast_data.get('area').get('areaid'),
'maxtem':forecast_data.get('forecast24h').get('maxtem'),
'mintem':forecast_data.get('forecast24h').get('mintem'),
'dayws':forecast_data.get('forecast24h').get('dayws'),
'daywd':forecast_data.get('forecast24h').get('daywd'),
'nightws':forecast_data.get('forecast24h').get('nightws'),
'nightwd':forecast_data.get('forecast24h').get('nightwd'),
'factime':forecast_data.get('forecast24h').get('fctime'),
'area_name':area_name
}
elif 'obs' in column:
if city_weathers !=None:
for key,forecast_data in city_weathers.items():
if forecast_data !=None:
yield{
'city_name':key,
'city_id':forecast_data.get('area').get('areaid'),
'tem':forecast_data.get('obs').get('tem'),
'rh':forecast_data.get('obs').get('rh'),
'ws':forecast_data.get('obs').get('ws'),
'wd':forecast_data.get('obs').get('wd'),
'factime':forecast_data.get('obs').get('fctime'),
'area_name':area_name
}
问题一:
其中在使用接口去获取信息,返回的数据全部是乱码。由于编写的get_page(self,url)方法的返回值是byte类型的,所以先进行解码、编码、再进行解码,去解决乱码问题。
问题二:
在进行数据解析的时候,因为香港、台湾和澳门之前不存在一些字段,所以无法获取到信息,而选择在解析的时候去除它们,它们的地区码分别为:'101320101' and 101330101' and '101340101'。
数据库的操作全部封装到一个类里面去。
地区表的结构:id varchar(10),areaId varchar(10),areaName varchar(20)
不同地区的实时天气的表结构:id VARCHAR(20),cityName VARCHAR(20), tem VARCHAR(10), rh VARCHAR(10),wd VARCHAR(10),ws VARCHAR(10),factime VARCHAR(20),areaName VARCHAR(20)。
# coding:utf-8
import pymysql
class DbMysql():
# 初始化参数
def __init__(self):
self.host='127.0.0.1'
self.user='root'
self.password='root'
self.db='mysql'
self.port=3306
self.charset='utf8'
self._conn=self.get_connect()
if self._conn:
self._cur=self._conn.cursor()
# 创建连接
def get_connect(self):
conn=False
try:
conn=pymysql.connect(
host=self.host,
user=self.user,
password=self.password,
db=self.db,
port=self.port,
charset=self.charset
)
except Exception as e:
print('连接数据库出错,%s' % e)
return None
else:
return conn
# 创建db_area数据库里面的实时天气表,根据areaId来创建表名
def creata_db_area_table(self,area_id):
try:
# 选择数据库
self._cur.execute('USE db_area')
# 创建表,用obs+地区号码来作为表名
sql_create_table='CREATE TABLE obs'+area_id+''' (id VARCHAR(20),
cityName VARCHAR(20),
tem VARCHAR(10),
rh VARCHAR(10),
wd VARCHAR(10),
ws VARCHAR(10),
factime VARCHAR(20),
areaName VARCHAR(20)
)'''
self._cur.execute(sql_create_table)
except Exception as e:
print('创建表出错,%s' % e)
# 出现错误-->%d format: a number is required, not str
# 存储地区名称跟areaid
def save_area(self,areas):
# 选择数据库
self._cur.execute('USE db_area')
i=0
try:
for area in areas:
# 插入数据
i+=1
print("正在插入")
# 由于 在插入的时候,python默认插入的字符串类型,字段都是字符串类型
self._cur.execute('insert into areas values(%s,%s,%s)',(str(i),area['area_id'],area['area_name']))
print("插入结束")
except Exception as e:
print('存储地区出错,%s' % e)
pass
finally:
self._conn.commit()
# 存储实时天气
def save_obs_weather(self,weather,city_id):
# 选择数据库
self._cur.execute('USE db_area')
try:
self._cur.execute('insert into obs'+city_id+' values(%s,%s,%s,%s,%s,%s,%s,%s)',(weather['city_id'],weather['city_name'],weather['tem'],
weather['rh']+"%",weather['wd'],weather['ws'],weather['factime'],weather['area_name']))
print("插入一条")
except Exception as e:
print("存储实时天气出错,%s" % e)
pass
finally:
self._conn.commit()
# 存储未来24小时的天气
def save_forecast24h_weather(self,weather):
# 选择数据库
self._cur.execute('USE db_area')
try:
self._cur.execute('insert into city_forecast_weather values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)',(weather['city_id'],weather['city_name'],weather['maxtem'],
weather['mintem'],weather['daywd'],weather['dayws'],weather['nightwd'],weather['nightws'],weather['factime'],weather['area_name']))
except Exception as e:
print('存储未来24小时天气出错,%s' % e)
pass
finally:
self._conn.commit()
# 查询得到全部地区的areaid
def research_for_sql(self):
try:
self._cur.execute('USE db_area')
self._cur.execute('select * from areas')
# 查询全部,返回元组
results=self._cur.fetchall()
return [result[1] for result in results]
except Exception as e:
print('查询全部出错,%s' % e)
return None
# 查询一条数据
def read_areas_sql(self,Id):
try:
# 选择数据库
self._cur.execute('USE db_area')
self._cur.execute('select * from areas where areaId='+str(Id))
# 返回一条数据,数据类型是元组
result=self._cur.fetchone()
msgs,area_name=result[1][0:5],result[2]
return msgs,area_name
except Exception as e:
print('查询一条信息出错,%s' % e)
return None
# 读取实时/预测天气
def read_Weather(self,db_pass):
self._cur.execute('USE db_area')
try:
# 查询天气
self._cur.execute(db_pass)
# 获取所有的数据
results=self._cur.fetchall()
return [result for result in results]
except Exception as e:
print('读取实时/预测天气,%s' % e)
return None
# 析构函数
def __del__(self):
self._cur.close()
self._conn.close()
问题一:
# 出现错误-->%d format: a number is required, not str,尝试存储id字段为整形的数据,却报错,修改该字段类型为varchar。
可视化的操作都编写到一个类里面去,从数据库里面获取到实时气温和湿度用pygal库画出各个地区的实时天气,预测天气图只做了北京的最高温和最低温,其他的没有做。
import pygal
class WeatherPygal():
"""docstring for WeatherPygal"""
def __init__(self):
self.sql="select * from city_forecast_weather where areaName="
# 制作实时天气图表
def show_weather(self,db_pass,sql):
cityNames,tems,rhs=[],[],[]
areaName=''
for result in sql.read_Weather(db_pass):
cityNames.append(result[1])
tems.append(result[2])
rhs.append(result[3])
areaName=result[7]
line=pygal.Line(x_label_rotation=20)
line.title=areaName+'实时天气'
line.x_labels=cityNames
line.add('气温°',[int(tem) for tem in tems if tem !=''])
line.add('湿度%',[int(float(rh[:-1])) for rh in rhs if rh !=''])
line.render_to_file(areaName+'实时天气.svg')
# 预测24小时的最高和最低气温
def show_forecast_weather(self,term_area,sql):
cityNames,maxTems,minTems=[],[],[]
areaName=''
conditio=self.sql+"'"+term_area+"'"
if sql.read_Weather(condition):
for result in sql.read_Weather(condition):
cityNames.append(result[1])
maxTems.append(result[2])
minTems.append(result[3])
areaName=result[9]
line=pygal.Line(x_label_rotation=20)
line.title=areaName+'预测天气'
line.x_labels=cityNames
line.add('最高气温°',[int(maxTem) for maxTem in maxTems])
line.add('最低气温°',[int(minTem) for minTem in minTems])
line.render_to_file(areaName+'预测天气.svg')
项目没有用到多线程,所以相对来说慢了点,大概5分钟之内都可以完成了,有需要的伙伴可以自己优化,下一步的自己也要提高程序的效率,对程序进行优化。
链接: https://pan.baidu.com/s/1T-SHGi5haf2HoHRDiBuGgQ 密码:wzgj