爬取全国天气
需求
从网站里爬取全国城市对应的最低温度,保存到csv文件里。
先要找到数据在哪,在页面点右键,检查网页源码,搜索“北京”,如下图
可以看到需要的数据在网页源码中,基本确定是静态加载出来的。我们向网页对应的url发起请求,就可以得到一串网页源码,用我们学到的方法进行解析就可以了,这样的话就可以确定目标url。
目标url
http://www.weather.com.cn/textFC/hb.shtml
页面分析
在页面表格城市“北京”处,点击右键,可以看到光标定位到<td 下的<a标签里,刚已经基本确定数据在网页源码中,虽然elements里的数据跟网页源码中有所区别,我们可以借助elements来进行页面分析和数据分析,定位的“北京”是在<td标签里面的,在往上折叠会发现他们都是存放于<tr标签中,前两个<tr代表的是表头,下面的每个<tr标签代表一个城市的数据,而所有的<tr标签都存放于<tbody标签里。<tbody是后来由网页渲染出来的,而在网页源码中搜索不到<tbody,不存在于网页源码中。<tbody标签存放于<table标签中,光标位于<table标签时,整个北京的数据都会被选中,再往上光标放到<div class="conMidtab2"时,不同的省/直辖市都会被选中;当光标定位到<div class="conMidtab"时,所有省/直辖市的表格都会被选中,而多个<div class="conMidtab"代表不同日期的天气情况。
从根目录往下展开,可以看到"海淀"位于,table --> tbody --> 第四个tr的文本(前两个tr是表头)的第一个td标签里;最低气温的20位于tr标签下倒数第二个td标签的文本里;我们可以用find找到第一个<div class="conMidtab"爬取第一天的天气,再用findall找到<div class="conMidtab"里所有<table标签,找到每个表格,在用findall找到所有的tr标签(过滤掉前两个tr),下面的每个tr代表的是一个城市的数据,而每个tr中的第一个td代表的是城市,倒数第二个td代表的是最低温度。
总结:
- 找到class="conMidtab"的div标签 里面存放了当天所有的的天气数据 用find
- 找到table标签 一个table对应一张表格 也就是一个省或者是直辖市的天气数据
- 找到table标签下面的所有tr 但是要过滤掉前两个tr标签(表头)
- 找到tr标签下面的所有的td 其中第一个td标签是城市名 倒数td第二个存放的是最低温
- 保存到csv文件中
工具:requests(发请求) bs4(解析数据) csv(保存数据)
实现步骤
import requests
from bs4 import BeautifulSoup
import csv
import time
# 发送请求,获取相应
def req_data(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
res = requests.get(url, headers=headers)
html = res.content.decode('utf-8')
return html
# 解析数据
def parse_data(html):
soup = BeautifulSoup(html, 'lxml')
# 1.找到class_="conMidtab"的div标签,只找一个
div_class = soup.find(class_="conMidtab")
# 2. 找到table标签,有好几个表格
lis = []
tables =div_class.find_all('table')
for table in tables:
# print(table)
# 3.找到所有的tr标签,过滤掉前两个
tr_tags = table.find_all('tr')[2:]
for tr in tr_tags:
d = {}
# print(tr)
# print('*'*50)
# 4.在所有的tr标签下取出td标签
tds = tr.find_all('td')
# 其中第一个td标签是城市名,倒数td第二个存放的是最低温
# 第一个td标签有两个文本,第一个是空格,所以用tds[0].stripped_strings) 返回的是生成器对象
cities = list(tds[0].stripped_strings)[0]
temps = list(tds[-2].stripped_strings)[0]
# print(cities, temps)
d['city'] = cities
d['temp'] = temps
lis.append(d)
# print(lis)
return lis
# 保存数据
def save_csv(lis, header):
with open('weather.csv', 'a', encoding='utf-8', newline="") as f:
writ = csv.DictWriter(f, header)
if i == 0: # 利用全局变量i
writ.writeheader() # 只在第一页打印表头
writ.writerows(lis)
if __name__ == '__main__':
# 把几个网页中不同的地方放到列表中,进行遍历取出,实现翻页处理
lis1 = ['hb', 'db', 'hd', 'hz', 'hn', 'xb', 'xn', 'gat']
global i # 定义i为全局变量,实现去除重复表头
for i in lis1:
time.sleep(5)
url = f'http://www.weather.com.cn/textFC/{i}.shtml'
h = req_data(url)
lis = parse_data(h)
header = ('city', 'temp')
save_csv(lis, header)
程序运行完成之后,会出现每个表格(省/直辖市)的第一条数据显示的城市是省/直辖市的名字,比如河北省,第一条数据显示的是河北,而不是石家庄;山西省第一条数据显示的是山西,而不是大同。在网页源码中我们可以看到,经过过滤表头后,第一个td显示的是省/直辖市的名字,第二个td显示的是城市名字,所以要对每个表格判断一下,是否为第一条数据(城市),如果是就返回第二个td里的内容(因为第一个td显示的是省/直辖市),如果不是还是按照以前页面分析的返回第一个td标签(城市名),可以用enumerate()获取的索引值对获取到的td标签进行判断。
a = ['x', 'y', 'z']
# print(enumerate(a)) # 返回enumerate对象,<enumerate object at 0x000002AFB0E896C0>
# for i in enumerate(a):
# print(i) # 返回两个数据,第一个数据是元素的索引,第二个数据是元素本身
for index, i in enumerate(a):
print(index, i)
# index接收到的是元素的索引值,0,1,2
# i接收的是元素,x,y,z
可以对def parse_data()函数中for tr in tr_tags进行一下判断
for index, tr in enumerate(tr_tags):
d = {}
# print(tr)
# print('*'*50)
# 4.在所有的tr标签下取出td标签
tds = tr.find_all('td')
# 其中第一个td标签是城市名,倒数td第二个存放的是最低温
# 第一个td标签有两个文本,第一个是空格,所以用tds[0].stripped_strings) 返回的是生成器对象
if index == 0:
cities = list(tds[1].stripped_strings)[0]
else:
cities = list(tds[0].stripped_strings)[0]
temps = list(tds[-2].stripped_strings)[0]
在最后一页,港澳台页面,显示的数据会有异常
这个时候回到网页源码中,查找<table标签,可以看到其实<table标签其实是不完整的,只有开头,没有结尾,这个时候就要把网页的解析器"lxml"换为"html5lib",这个解析器可以完善标签的内容,对所有残缺的标签进行补全,就是运行比较慢。
soup = BeautifulSoup(html, 'html5lib')
如果出现下图所示的内容 说明没有安装"html5lib",需要用pip install 安装一下就可以了
完整版代码如下:
import requests
from bs4 import BeautifulSoup
import csv
import time
# 发送请求,获取相应
def req_data(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
res = requests.get(url, headers=headers)
html = res.content.decode('utf-8')
return html
# 解析数据
def parse_data(html):
soup = BeautifulSoup(html, 'html5lib')
# 1.找到class_="conMidtab"的div标签,只找一个
div_class = soup.find(class_="conMidtab")
# 2. 找到table标签,有好几个表格
lis = []
tables =div_class.find_all('table')
for table in tables:
# print(table)
# 3.找到所有的tr标签,过滤掉前两个
tr_tags = table.find_all('tr')[2:]
for index, tr in enumerate(tr_tags):
d = {}
# print(tr)
# print('*'*50)
# 4.在所有的tr标签下取出td标签
tds = tr.find_all('td')
# 其中第一个td标签是城市名,倒数td第二个存放的是最低温
# 第一个td标签有两个文本,第一个是空格,所以用tds[0].stripped_strings) 返回的是生成器对象
if index == 0:
cities = list(tds[1].stripped_strings)[0]
else:
cities = list(tds[0].stripped_strings)[0]
temps = list(tds[-2].stripped_strings)[0]
# print(cities, temps)
d['city'] = cities
d['temp'] = temps
lis.append(d)
# print(lis)
return lis
# 保存数据
def save_csv(lis, header):
with open('weather.csv', 'a', encoding='utf-8', newline="") as f:
writ = csv.DictWriter(f, header)
if i == 0: # 利用全局变量i
writ.writeheader() # 只在第一页打印表头
writ.writerows(lis)
if __name__ == '__main__':
# 把几个网页中不同的地方放到列表中,进行遍历取出,实现翻页处理
lis1 = ['hb', 'db', 'hd', 'hz', 'hn', 'xb', 'xn', 'gat']
global i # 定义i为全局变量,实现去除重复表头
for i in lis1:
time.sleep(5)
url = f'http://www.weather.com.cn/textFC/{i}.shtml'
h = req_data(url)
lis = parse_data(h)
header = ('city', 'temp')
save_csv(lis, header)