python爬虫----全国天气预报的获取

这次是通过全国天气预报的网站去抓取实时天气和预测未来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

猜你喜欢

转载自blog.csdn.net/weixin_36605200/article/details/82831718