查看全课大纲(第 48 / 51 节)
- 1.为什么人人都该学点编程?
- 2.计算机与程序设计:你的电脑到底在“想”什么?
- 3.Python 这门语言,到底有什么魔力?
- 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
- 5.如何安装一个更强大的代码编辑器?
- 6.汇率兑换1.0:你的第一个“钱生钱”小程序
- 7.你的第一行代码,藏着哪些秘密?
- 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
- 9.汇率兑换程序,如何让它“聪明”地判断币种?
- 10.汇率兑换程序能一直运行,直到我喊停吗?
- 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
- 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
- 13.怎么用 Python 画一个会动的五角星?
- 14.如何让程序自动画出不同大小的五角星?
- 15.函数自己调用自己,会发生什么?
- 16.用递归画出神奇的分形树
- 17.Python 怎么计算你一天躺着不动能消耗多少热量?
- 18.如何让程序“活”起来,持续为你服务?
- 19.如何让 BMR 计算器更“聪明”?
- 20.当程序遇到“不按套路出牌”的用户怎么办?
- 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
- 22.如何用列表记录每一周的存款?
- 23.如何让程序自己数数?—— 用 for 循环解放你的双手
- 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
- 25.52周存钱挑战:怎么根据日期自动查存款?
- 26.Python 怎么判断一年中的第几天?
- 27.列表 vs 元组:哪个更适合存储月份天数?
- 28.用集合给月份分个队,换个思路算天数
- 29.把月份和天数“锁”在一起:用字典给数据配个对
- 30.Python 怎么判断密码强弱?
- 31.Python 怎么给密码设置“防沉迷”系统?
- 32.Python 怎么把密码保存到文件里?
- 33.Python 怎么读取文件里的密码?
- 34.Python 怎么把密码检查工具“打包”起来?
- 35.Python 怎么把文件操作也“打包”起来?
- 36.计算机怎么“扔骰子”?——用Python模拟随机事件
- 37.如何用 Python 模拟掷两个骰子?
- 38.怎么用 Python 把骰子轨迹画出来?
- 39.骰子点数分布长啥样?用直方图一眼看穿!
- 40.用科学计算库让骰子程序“起飞”
- 41.空气质量指数怎么算?手把手教你写一个AQI计算器
- 42.空气质量指数计算:从零开始,让程序看懂空气
- 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
- 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
- 45.Python 怎么让程序自己判断文件类型?
- 46.如何让程序自己上网查空气质量?
- 47.用美味的汤来解析网页:Beautiful Soup 入门
- 48.如何让爬虫自动抓取全国所有城市的空气质量?
- 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
- 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
- 51.空气质量数据怎么“洗”干净,还能画成图?
如何让爬虫自动抓取全国所有城市的空气质量?
约 9 分钟
小象实战讲义 · Python 入门:8 个小项目边玩边学
上一节我们学会了用 BeautifulSoup 解析单个城市的空气质量数据,但每次都要手动输入城市拼音,效率太低了!这一节,我们要让爬虫变得更聪明——自动找到网站上所有城市的链接,然后一口气把全国城市的空气质量全部抓取下来!
学完这一节,你将得到一个真正的实用爬虫:只要运行一次程序,就能看到全国几百个城市的实时空气质量数据在屏幕上滚动输出,就像天气预报一样酷!
💡 核心导读
- 本节新增:编写一个函数,自动从网站首页提取所有城市的中文名和拼音链接
- 爬虫升级:从「单点抓取」升级到「批量抓取」,实现真正的自动化
- 实战技巧:如何定位网页中的特定区域,提取我们需要的数据
- 程序结构:把不同功能拆分成独立的函数,让代码更清晰
爬虫的「导航系统」:先找到所有城市
我们的目标:让爬虫自己「看地图」
想象一下,你要去一个陌生的城市旅游,但手里只有一张标着「景点」的地图,却不知道具体有哪些景点。我们的爬虫现在就是这种情况——我们知道怎么获取单个城市的空气质量(上节课的 get_city_aqi 函数),但不知道有哪些城市可以抓取。
本节我们要做的:给爬虫装上一个「导航系统」,让它先到网站首页,找到「所有城市」这个列表,然后记下每个城市的名字和对应的链接地址。
import requests
from bs4 import BeautifulSoup
def get_all_cities():
"""
获取网站上所有城市的中文名和拼音链接
返回一个列表,每个元素是 (城市名, 城市拼音) 的元组
"""
# 空气质量网站的首页,这里列出了所有城市
url = 'http://pm25.in/'
# 发送请求获取网页内容
r = requests.get(url, timeout=30)
# 用BeautifulSoup解析网页
soup = BeautifulSoup(r.text, 'lxml')
# 准备一个空列表,用来存放城市信息
city_list = []
# 关键步骤1:找到包含所有城市的那个div区域
# 网站上有两个class为'bottom'的div,我们要取第二个
city_div = soup.find_all('div', {'class': 'bottom'})[1]
# 关键步骤2:在这个div里找到所有的<a>标签(每个城市都是一个链接)
city_link_list = city_div.find_all('a')
# 关键步骤3:遍历每个链接,提取我们需要的信息
for city_link in city_link_list:
# 获取城市的中文名称(链接的文本内容)
city_name = city_link.text
# 获取城市的拼音(链接的href属性,去掉开头的斜杠)
# 例如:href="/beijing" → 取"beijing"
city_pinyin = city_link['href'][1:]
# 把城市名和拼音作为一个元组,添加到列表中
city_list.append((city_name, city_pinyin))
return city_list
# 测试一下我们的函数
cities = get_all_cities()
print(f"找到了 {len(cities)} 个城市")
print("前5个城市:", cities[:5])找到了 381 个城市
前5个城市: [('阿坝州', 'aba'), ('阿克苏地区', 'akesu'), ('阿拉尔', 'alaer'), ('阿拉善盟', 'alashanmeng'), ('阿勒泰地区', 'aletai')]看!我们的爬虫已经成功找到了381个城市,而且正确提取了每个城市的中文名和拼音。这个「导航系统」工作正常!
注:上面的数字与城市名是课程录制时的一次示例输出。城市总数和排序可能随网站调整而变化,以你运行时的实时结果为准。
组装完整的爬虫:批量抓取所有城市
把两个功能「拼接」起来
现在我们有:
- 导航系统:
get_all_cities()- 知道有哪些城市 - 数据抓取器:
get_city_aqi()- 知道怎么抓取单个城市的数据
接下来就像组装乐高积木一样,把这两个功能拼接起来:
import requests
from bs4 import BeautifulSoup
# 1. 数据抓取器(上节课的函数)
def get_city_aqi(city_pinyin):
"""
获取指定城市的空气质量数据
"""
url = 'http://pm25.in/' + city_pinyin
r = requests.get(url, timeout=30)
soup = BeautifulSoup(r.text, 'lxml')
# 找到所有空气质量指标的div
div_list = soup.find_all('div', {'class': 'span1'})
city_aqi = []
# 提取前8个指标(PM2.5, PM10, CO等)
for i in range(8):
div_content = div_list[i]
caption = div_content.find('div', {'class': 'caption'}).text.strip() # 指标名称
value = div_content.find('div', {'class': 'value'}).text.strip() # 指标数值
city_aqi.append((caption, value))
return city_aqi
# 2. 导航系统(本节新增的函数)
def get_all_cities():
"""
获取所有城市信息
"""
url = 'http://pm25.in/'
city_list = []
r = requests.get(url, timeout=30)
soup = BeautifulSoup(r.text, 'lxml')
city_div = soup.find_all('div', {'class': 'bottom'})[1]
city_link_list = city_div.find_all('a')
for city_link in city_link_list:
city_name = city_link.text
city_pinyin = city_link['href'][1:]
city_list.append((city_name, city_pinyin))
return city_list
# 3. 主程序:把两个功能组合起来
def main():
"""
主函数:批量抓取所有城市的空气质量
"""
print("开始获取城市列表...")
city_list = get_all_cities()
print(f"共找到 {len(city_list)} 个城市")
print("\n开始抓取空气质量数据(这可能需要几分钟)...")
print("=" * 50)
# 遍历每个城市,获取其空气质量
for city in city_list:
city_name = city[0] # 城市中文名
city_pinyin = city[1] # 城市拼音
try:
# 调用数据抓取器
city_aqi = get_city_aqi(city_pinyin)
# 只显示PM2.5的数据(你也可以显示全部8个指标)
pm25_value = None
for item in city_aqi:
if "PM2.5" in item[0]:
pm25_value = item[1]
break
# 输出结果
if pm25_value:
print(f"{city_name}: PM2.5 = {pm25_value}")
else:
print(f"{city_name}: 数据获取失败")
except Exception as e:
# 如果某个城市抓取出错,跳过继续下一个
print(f"{city_name}: 抓取出错 - {str(e)[:30]}...")
# 运行主程序
main()运行后会逐行打印每个城市的 PM2.5(示例输出,数值随网站实时变化;抓取几百个城市需要几分钟):
开始获取城市列表...
共找到 381 个城市
开始抓取空气质量数据(这可能需要几分钟)...
==================================================
阿坝州: PM2.5 = 12
阿克苏地区: PM2.5 = 33
阿拉尔: PM2.5 = 27
阿拉善盟: PM2.5 = 17
阿勒泰地区: PM2.5 = 8
阿里地区: PM2.5 = 4
安康: PM2.5 = 28
安庆: PM2.5 = 30
安顺: PM2.5 = 16
安阳: PM2.5 = 35
鞍山: PM2.5 = 28
...
(后续城市数据继续输出)太酷了! 我们的爬虫现在可以自动:
- 访问网站首页,找到所有城市
- 依次访问每个城市的页面
- 提取并显示PM2.5数据
- 即使某个城市出错,也会继续处理下一个
程序设计的艺术:为什么要把功能拆开?
像搭积木一样写代码
你可能注意到了,我们的程序被分成了三个部分:
get_city_aqi()- 专门负责「怎么抓一个城市」get_all_cities()- 专门负责「有哪些城市」main()- 负责「指挥」这两个函数工作
这种设计有三大好处:
1. 代码更清晰:每个函数只做一件事,容易理解和维护
# 就像乐高说明书:
# 步骤1:拼装轮子(get_all_cities)
# 步骤2:拼装车身(get_city_aqi)
# 步骤3:组合成车(main)2. 容易调试:如果某个城市数据抓取失败,我们知道问题出在 get_city_aqi;如果城市列表获取失败,我们知道问题在 get_all_cities
3. 方便复用:下次我们想抓取其他网站的数据,可以直接复用 get_all_cities 的思路,只需要修改解析网页的部分
网页解析的「精确制导」
在 get_all_cities() 函数中,有一个关键技巧:
# 网页里有两个 class="bottom" 的 div,城市列表在第二个里,所以用 [1]:
city_div = soup.find_all('div', {'class': 'bottom'})[1]
city_link_list = city_div.find_all('a') # 再在这个区域里找所有城市链接为什么是 [1](取第二个)而不是 [0](取第一个)?
如果你打开网站首页查看源代码,会发现有两个 class="bottom" 的div:
- 第一个:可能是网站的页脚、版权信息等
- 第二个:才是真正包含所有城市列表的区域
这就像在人群中找人——如果只说「找穿红衣服的人」,可能会找到好几个;我们需要更精确的描述:「找穿红衣服、戴眼镜、手里拿书的那个人」。
爬虫的「容错机制」:让程序更健壮
现实世界的网络并不完美
在实际运行中,你可能会遇到:
- 某个城市页面暂时打不开
- 网络连接突然中断
- 网站结构发生变化
我们的程序通过 try...except 来处理这些意外:
# 在批量抓取的 for 循环里,用 try...except 包住每个城市的抓取动作:
for city in city_list:
city_name = city[0]
city_pinyin = city[1]
try:
city_aqi = get_city_aqi(city_pinyin) # 正常抓取
print(f"{city_name}: PM2.5 = {city_aqi}")
except Exception as e:
# 单个城市失败不影响其他城市,记录后继续
print(f"{city_name}: 抓取出错 - {str(e)[:30]}...")这就像给爬虫戴上了「安全帽」——即使撞到障碍物(出错),也不会整个程序崩溃,而是记录下来继续前进。
📝 动手练一练
现在轮到你来改进这个爬虫了!试试下面这些挑战:
速度优化:现在的程序是一个城市接一个城市地抓取,速度比较慢。你能想到什么办法让程序运行更快吗?(提示:可以同时处理多个城市)
数据丰富:现在只显示了PM2.5数据,修改程序让它显示所有8个空气质量指标(PM2.5、PM10、CO、NO2、O3、SO2、温度、湿度)
结果筛选:只显示PM2.5数值超过50的城市(空气质量较差的城市)
👉 点击查看第3题的参考答案
# 思路:在输出前判断PM2.5值是否大于50
import requests
from bs4 import BeautifulSoup
# 为了示例简洁,这里只写关键部分
# 假设我们已经有了城市列表和获取AQI的函数
# 模拟几个城市的数据(实际使用时用真实的get_city_aqi函数)
def get_city_aqi_demo(city_pinyin):
"""模拟函数,返回模拟的AQI数据"""
# 这里用固定数据模拟,实际应该从网站抓取
demo_data = {
'beijing': [('PM2.5', '65'), ('PM10', '120')],
'shanghai': [('PM2.5', '45'), ('PM10', '80')],
'guangzhou': [('PM2.5', '55'), ('PM10', '90')],
'shenzhen': [('PM2.5', '38'), ('PM10', '70')]
}
return demo_data.get(city_pinyin, [('PM2.5', '0')])
# 模拟城市列表
city_list = [('北京', 'beijing'), ('上海', 'shanghai'),
('广州', 'guangzhou'), ('深圳', 'shenzhen')]
print("PM2.5超过50的城市:")
print("=" * 30)
for city_name, city_pinyin in city_list:
city_aqi = get_city_aqi_demo(city_pinyin)
# 找到PM2.5的值
pm25_value = 0
for item in city_aqi:
if "PM2.5" in item[0]:
pm25_value = int(item[1]) # 转换为整数以便比较
break
# 只显示PM2.5 > 50的城市
if pm25_value > 50:
print(f"{city_name}: PM2.5 = {pm25_value} (空气质量较差)")PM2.5超过50的城市:
==============================
北京: PM2.5 = 65 (空气质量较差)
广州: PM2.5 = 55 (空气质量较差)本章小结
恭喜!在这一节中,你的爬虫完成了重要升级:
🎯 本节成果
- ✅ 学会了批量抓取:从只能抓一个城市,升级到能自动抓取全国所有城市
- ✅ 掌握了网页解析的精确定位:用
find_all()[1]精确找到需要的网页区域 - ✅ 理解了函数拆分的好处:把大任务拆成小函数,让代码更清晰、更易维护
- ✅ 实现了简单的容错处理:用
try...except让程序更健壮
📋 行动清单
学完立刻可以做的3件事:
- 运行完整程序:把本节最后的完整代码复制运行,看看你的城市空气质量如何
- 尝试改进:完成「动手练一练」的第2题,让程序显示更多空气质量指标
- 观察思考:运行程序时观察输出,哪些城市的空气质量比较好?哪些比较差?
预告:现在数据只是在屏幕上显示,下一节我们要把这些宝贵的数据保存到文件中,这样就能随时查看、分析,甚至做成图表!
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。