一、爬取需求
目标网站:http://www.weather.com.cn/textFC/hb.shtml
需求:爬取全国的天气(获取城市以及最低气温) 目标url:http://www.weather.com.cn/textFC/hz.shtml
二、所需第三方库
requestsBeautifulSoup4
安装
requests:pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
BeautifulSoup4:pip install BeautifulSoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
2.1 简介
requests模块
官方文档:https://requests.readthedocs.io/projects/cn/zh-cn/latest/
requests 是 Python 编程语言中一个常用的第三方库,它可以帮助我们向 HTTP 服务器发送各种类型的请求,并处理响应。
- 向 Web 服务器发送 GET、POST 等请求方法;
- 在请求中添加自定义标头(headers)、URL 参数、请求体等;
- 自动处理 cookies;
- 返回响应内容,并对其进行解码;
- 处理重定向和跳转等操作;
- 检查响应状态码以及请求所消耗的时间等信息。
BeautifulSoup4模块
官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/
Beautiful Soup 是一个 可以从 HTML 或 XML 文件中提取数据的 Python 库。它能用你喜欢的解析器和习惯的方式实现 文档树的导航、查找、和修改。
下表描述了几种解析器的优缺点:
注意:如果一段文档格式不标准,那么在不同解析器生成的 Beautiful Soup 数可能不一样。 查看 解析器之间的区别 了解更多细节。
数据提取之CSS选择器:
- 熟悉前端的同学对 css 选择器一定不会陌生,比如 jquery 中通过各种 css 选择器语法进行 DOM 操作等
- 学习网站:
http://www.w3cmap.com/cssref/css-selectors.html
在爬虫中使用css选择器,代码教程:
from requests_html import session | |
# 返回一个Response对象 | |
'https://python.org/') | r = session.get(|
# 获取所有链接 | |
r.html.links | |
{'/users/membership/', '/about/gettingstarted/'} | |
# 使用css选择器的方式获取某个元素 | |
'#about')[0] | about = r.html.find(|
print(about.text) | |
About | |
Applications | |
Quotes | |
Getting Started | |
Help | |
Python Brochure |
三、实战案例
目标网站:http://www.weather.com.cn/textFC/hb.shtml
思路分析:
- 通过find方法,定位的div class=conMidtab2
- 通过find_all方法,找所有的tr标签
函数功能
- 得到网页源码
- 解析数据
- 保存数据
- 主函数
程序框架
import requests | |
from bs4 import BeautifulSoup | |
# 获取网页源码 | |
def get_html(): | |
pass | |
# 解析数据 | |
def parse_html(): | |
pass | |
# 保存数据 | |
def save_data(): | |
pass | |
# 主函数 | |
def main(): | |
pass |
获取网页源码 在主函数中进行传参调用
def get_html(url): | |
html = requests.get(url) | |
html.encoding = 'utf-8' | |
return html.text | |
def main(): | |
url = 'http://www.weather.com.cn/textFC/hz.shtml' | |
html = get_html(url) | |
main() |
解析数据 将get_html函数的返回值(网页源码)作为参数传给parse_html函数
# 主函数 | |
def main(): | |
url = 'http://www.weather.com.cn/textFC/hz.shtml' | |
html = get_html(url) | |
parse_html(html) | |
main() |
对于parse_html函数,要将传入的网页源码进行解析,获取我们想要的数据。
通过观察元素,每一个class="conMidtab2"的div标签就代表一个省份,那么他的父级元素class="conMidtab"的div标签就包含三个省份的天气信息,了解了这些,剩下的我们只需要根据元素之间的关系,一步步提取我们想要的数据即可。
# 解析数据 | |
def parse_html(html): | |
# 创建对象 | |
soup = BeautifulSoup(html, 'lxml') | |
conMidtab = soup.find('div', class_="conMidtab") | |
tables = conMidtab.find_all('table') | |
# print(tables) | |
for table in tables: | |
trs = table.find_all('tr')[2:] | |
# print(trs) | |
for index, tr in enumerate(trs): | |
tds = tr.find_all('td') | |
# print(tds) | |
city = list(tds[1].stripped_strings)[0] # 城市 | |
temp = tds[-2].string # 最低气温 | |
print(city, temp) | |
break |
但是,这里出现了一个问题,那就是我们要打印城市信息的时候,只能打印出第一个城市,后面的城市无法打印出来,通过查看元素后我们会发现,除了第一个城市是在第二个td标签里面,其余城市都在第一个td标签里面,所以在这里我们要将循环改一下,同时还要加一个判断,只要是第一个城市就去第二个td标签,其余的取第一个td标签
想要实现这种效果,我们就要用到一个函数enumerate,这个函数可以将下标和下标对应的值给显示出来。
# 解析数据 | |
def parse_html(html): | |
# 创建对象 | |
soup = BeautifulSoup(html, 'lxml') | |
conMidtab = soup.find('div', class_="conMidtab") | |
tables = conMidtab.find_all('table') | |
# print(tables) | |
for table in tables: | |
trs = table.find_all('tr')[2:] | |
# print(trs) | |
for index, tr in enumerate(trs): | |
tds = tr.find_all('td') | |
# print(tds) | |
if index == 0: # 第一个城市取第二个td标签 | |
city = list(tds[1].stripped_strings)[0] # 城市 | |
else: # 其余的取第一个td标签 | |
city = list(tds[0].stripped_strings)[0] # 城市 | |
temp = tds[-2].string # 最低气温 | |
print(city, temp) |
更换url查看数据信息 在主函数里面去更换url,然后查看打印的数据信息是否正确。运行后发现前面的都是正确的,直到更换到港澳台1地区时就出现了问题。
def main(): | |
# url = 'http://www.weather.com.cn/textFC/hz.shtml' | |
url = 'http://www.weather.com.cn/textFC/gat.shtml' | |
html = get_html(url) | |
parse_html(html) | |
main() |
我们发现,我们无法在元素中发现问题,那么我们现在就应该查看一下网页源代码。
查看网页源代码之后可以发现,他所在的table标签是没有结束标签的,后面的城市的table标签也没有结束标签,这也就导致了数据混乱。 想要解决这个问题,就需要更换一下解析器。上面在提到BeautifulSoup4时的解析器,我们发现html5lib这个解析器拥有最好的容错性。
下载:pip install html5lib
# 解析数据 | |
def parse_html(html): | |
# 创建对象 | |
soup = BeautifulSoup(html, 'html5lib') # 将lxml换成html5lib | |
conMidtab = soup.find('div', class_="conMidtab") | |
tables = conMidtab.find_all('table') | |
# print(tables) | |
for table in tables: | |
trs = table.find_all('tr')[2:] | |
# print(trs) | |
for index, tr in enumerate(trs): | |
tds = tr.find_all('td') | |
# print(tds) | |
if index == 0: # 第一个城市取第二个td标签 | |
city = list(tds[1].stripped_strings)[0] # 城市 | |
else: # 其余的取第一个td标签 | |
city = list(tds[0].stripped_strings)[0] # 城市 | |
temp = tds[-2].string # 最低气温 | |
print(city, temp) |
页面切换 要实现页面切换首先我们要观察一下不同页面的url
华北:http://www.weather.com.cn/textFC/hb.shtml 东北:http://www.weather.com.cn/textFC/db.shtml 华南:http://www.weather.com.cn/textFC/hn.shtml 华中:http://www.weather.com.cn/textFC/hz.shtml 港澳台:http://www.weather.com.cn/textFC/gat.shtml
我们发现,这些url的不同之处就在于后面的字母的不同,而这些字母又恰好是地区的首字母,那么我们只需要将这些地区的首字母存入到一个列表当中,循环之后就可以实现页面的切换。
# 主函数 | |
def main(): | |
district = ['hb', 'db', 'hd', 'hz', 'hn', 'xb', 'xn', 'gat'] | |
# url = 'http://www.weather.com.cn/textFC/hz.shtml' | |
# url = 'http://www.weather.com.cn/textFC/hb.shtml' | |
for dist in district: | |
url = f'http://www.weather.com.cn/textFC/{dist}.shtml' | |
html = get_html(url) | |
parse_html(html) | |
main() |
数据保存 定义一个全局变量的列表list_data,在解析数据的第二层循环中定义一个字典,将城市和最低气温添加到字典中去,最后将字典添加到list_data列表中。
def save_data(): | |
with open('全国天气.csv', 'w', encoding='utf-8-sig', newline='') as f: | |
writer = csv.DictWriter(f, fieldnames=('城市', '最低气温')) | |
writer.writeheader() | |
writer.writerows(list_data) |
四、完整代码
import requests | |
from bs4 import BeautifulSoup | |
import csv | |
list_data = [] | |
# 获取网页源码 | |
def get_html(url): | |
html = requests.get(url) | |
html.encoding = 'utf-8' | |
# print(html.text) | |
return html.text | |
# 解析数据 | |
def parse_html(html): | |
# 创建对象 | |
soup = BeautifulSoup(html, 'html5lib') # 将lxml换成html5lib | |
conMidtab = soup.find('div', class_="conMidtab") | |
tables = conMidtab.find_all('table') | |
# print(tables) | |
for table in tables: | |
trs = table.find_all('tr')[2:] | |
# print(trs) | |
for index, tr in enumerate(trs): | |
dic = {} | |
tds = tr.find_all('td') | |
# print(tds) | |
if index == 0: # 第一个城市取第二个td标签 | |
city = list(tds[1].stripped_strings)[0] # 城市 | |
else: # 其余的取第一个td标签 | |
city = list(tds[0].stripped_strings)[0] # 城市 | |
dic['城市'] = city | |
temp = tds[-2].string # 最低气温 | |
dic['最低气温'] = temp | |
list_data.append(dic) | |
# 保存数据 | |
def save_data(): | |
with open('全国天气.csv', 'w', encoding='utf-8-sig', newline='') as f: | |
writer = csv.DictWriter(f, fieldnames=('城市', '最低气温')) | |
writer.writeheader() | |
writer.writerows(list_data) | |
# 主函数 | |
def main(): | |
district = ['hb', 'db', 'hd', 'hz', 'hn', 'xb', 'xn', 'gat'] | |
# url = 'http://www.weather.com.cn/textFC/hz.shtml' | |
# url = 'http://www.weather.com.cn/textFC/hb.shtml' | |
for dist in district: | |
url = f'http://www.weather.com.cn/textFC/{dist}.shtml' | |
html = get_html(url) | |
parse_html(html) | |
save_data() # 数据保存 | |
main() |