查看全课大纲(第 46 / 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.空气质量数据怎么“洗”干净,还能画成图?
如何让程序自己上网查空气质量?
约 20 分钟
小象实战讲义 · Python 入门:8 个小项目边玩边学
还记得我们之前做的空气质量查询程序吗?数据都是预先准备好的文件。但空气质量是实时变化的,昨天的数据今天就没用了。这一节,我们要让程序自己上网查——输入城市名,它就能告诉你此刻的空气质量!就像给你的程序装上了“千里眼”,让它能实时看到全国各地的空气状况。
💡 核心导读
- 网络爬虫是什么:让程序自动上网抓取信息的“机器人”
- requests 模块:Python 里最简单的上网工具,一行代码就能获取网页
- GET 请求:像在浏览器地址栏输入网址一样访问网页
- 网页内容解析:从一大堆“代码”中找到我们需要的空气质量数值
从“看文件”到“看网页”
之前我们的程序都是读取本地文件(JSON 或 CSV),数据是静态的。但真实的空气质量数据每时每刻都在更新,我们需要实时获取。
怎么获取?当然不是让你每天手动去网站复制粘贴——那样太慢了,而且容易出错。我们需要的是网络爬虫。
什么是网络爬虫?
想象一下,你有一个不知疲倦的助手,它能:
- 按照你给的网址去访问网页
- 把网页内容全部“复制”下来
- 从里面找出你需要的信息
这个助手就是网络爬虫。它本质上是一个自动抓取互联网信息的程序。
# 对比一下两种方式:
# 旧方式:读取本地文件(数据可能已经过时)
import json
import os
# 本节需要 beijing_aqi.json(课程 resources 附件提供);为便于单独运行,先用示例数据建一份
with open('beijing_aqi.json', 'w', encoding='utf-8') as f: # 示例数据:每次运行都重建,保证输出一致
f.write('{"aqi": "51", "area": "北京", "pm25": "32", "pm10": "46"}')
with open('beijing_aqi.json', 'r', encoding='utf-8') as f:
data = json.load(f)
print('北京AQI(文件数据):', data['aqi'])
# 新方式:实时从网上获取(数据永远是最新的)
# 这就是我们这节要学的!北京AQI(文件数据): 51上网第一步:requests 模块
Python 里有个超级好用的上网工具叫 requests(请求),它让我们用最简单的方式访问网页。
# 首先需要安装 requests(如果你还没安装的话)
# 在命令行输入:pip install requests
import requests
# 访问一个网页,简单到不可思议!
url = 'http://www.example.com'
response = requests.get(url, timeout=30) # timeout=30 表示最多等30秒
print('状态码:', response.status_code) # 200表示成功,404表示找不到网页
print('网页内容前500字符:', response.text[:500])状态码: 200
网页内容前500字符: <!doctype html><html lang=en><head><meta charset=utf-8><title>Example Domain</title> …(后面还有几百字符,全是类似的标签和 CSS 样式,这里省略展示)本节新增了什么? 我们引入了第一个新工具:requests 模块,它是 Python 里最简单的上网方式。
HTTP 状态码小知识
当你访问网页时,服务器会返回一个状态码告诉你结果:
- 200:成功!网页正常打开了
- 400:请求有问题(比如参数错了)
- 404:最著名的错误——网页不存在
- 500:服务器内部出错
我们主要关心 200,只要看到这个数字,就说明网页获取成功了。
实战:查询任意城市的空气质量
现在我们来做一个实用的工具:输入城市拼音,就能查到实时空气质量。
1. 找到目标网站
我们使用一个公开的空气质量网站作为例子:http://pm25.in/
在网站正常运营时:
- 访问
http://pm25.in/beijing能看到北京的空气质量 - 访问
http://pm25.in/shanghai能看到上海的空气质量 - 访问
http://pm25.in/guangzhou能看到广州的空气质量
规律是:网址后面直接加上城市拼音。
重要的现实情况:公开网站会变化、也会停止服务。课程更新时实测
pm25.in的城市页已经返回410 Gone(该页面已被永久移除),所以下面的代码现在拿不到真实 AQI 数字。这恰好是学习爬虫最好的一课:请求失败、页面变更才是常态。我们仍以这个网站为例走一遍完整流程(你可以把网址换成任何其他可访问的网页来验证),并在最后学会如何让程序在拿不到数据时不崩溃。
import requests
# 让用户输入城市拼音
city_pinyin = 'beijing' # 原程序用 input(),这里用固定值方便演示
# 拼接完整的网址
url = 'http://pm25.in/' + city_pinyin
print('要访问的网址:', url)
# 获取网页内容
def get_html_text(url):
"""获取网页的文本内容"""
r = requests.get(url, timeout=30)
print('访问状态:', r.status_code) # 成功时是 200;该站当前返回 410
return r.text
# 调用函数获取网页
url_text = get_html_text(url)
print('网页内容长度:', len(url_text), '字符')
print('前200字符:', url_text[:200])运行这段代码,你会看到服务器这次返回的 HTML 文本。当前 pm25.in 返回的是一个很短的 410 错误页(见下面输出,正文里写着 “It is gone!”)——这说明请求本身成功送达了,但请求的页面已经不存在。我们正是要学会读懂这种信号。
2. 从“原料”中提取我们需要的信息
网页内容是一大串 HTML 代码,我们需要从中找到空气质量数值。就像从一锅汤里捞出我们想吃的肉块。
先看看“肉块”在哪里
我们当前实际拿到的响应是 410 错误页——这是上面代码真实打印出来的内容:状态码 410、正文里写着 “It is gone!”,整个页面只有 254 个字符:
要访问的网址: http://pm25.in/beijing
访问状态: 410
网页内容长度: 254 字符
前200字符: <html><head><title>ERROR</title><style>body{font-family:"sans";background:#1f1e1a;color:#5f5e5a;text-align:center}h1{font-size:6em;color:#e6462e}</style></head><body><h2><br/>It is gone!<br/>So be gon而在网站正常运营时,空气质量数值是藏在一段固定的 HTML 标签里的。用浏览器打开城市页、右键数值“检查”,能看到它以前的页面结构:
<div class="span12 data">
<div class="span1">
<div class="value">
61 ← 空气质量数值就藏在这里
</div>
</div>
</div>对比一下就明白:当前的 410 错误页里根本没有上面这个“包装盒”,所以接下来的查找必然落空——这正好能验证我们的查找逻辑在“找不到时”的行为。我们要做的,就是让程序在 HTML 里寻找“包装盒”,找到就取出数值,找不到就如实报告。
import requests
def get_html_text(url):
"""获取网页的文本内容"""
r = requests.get(url, timeout=30)
return r.text
def main():
# 城市拼音(原程序用 input(),这里用固定值)
city_pinyin = 'beijing'
url = 'http://pm25.in/' + city_pinyin
# 获取网页内容
url_text = get_html_text(url)
# 定义我们要找的"包装盒"特征
aqi_div = '''<div class="span12 data">
<div class="span1">
<div class="value">
'''
# 在网页内容中查找这个"包装盒"
index = url_text.find(aqi_div)
if index == -1:
print('找不到空气质量数据!')
return
# 计算数值开始的位置(跳过"包装盒"本身)
begin_index = index + len(aqi_div)
# 空气质量一般是2-3位数,我们先取2位
end_index = begin_index + 2
# 切片获取数值
aqi_val = url_text[begin_index:end_index]
print(f'{city_pinyin}的空气质量为:{aqi_val}')
# 运行主程序
main()当前运行结果(网站已 410):
找不到空气质量数据!这个结果是正确且符合预期的:程序没有崩溃,而是走完了“请求→在 HTML 中查找→找不到→友好提示”的完整分支。等你把网址换成一个正常返回数据的网页(或将来该站恢复),同一套代码就会在 else 分支里打印出真实数值。能优雅地处理“拿不到数据”,本身就是爬虫必备的健壮性。
3. 让程序更灵活
不过上面的程序有个小问题:如果空气质量是三位数(比如 120),我们只取两位就错了。让我们改进一下:
import requests
def get_html_text(url):
r = requests.get(url, timeout=30)
return r.text
def main():
# 可以试试不同的城市
cities = ['beijing', 'shanghai', 'guangzhou']
for city in cities:
url = 'http://pm25.in/' + city
url_text = get_html_text(url)
aqi_div = '''<div class="span12 data">
<div class="span1">
<div class="value">
'''
index = url_text.find(aqi_div)
if index == -1:
print(f'{city}:数据获取失败')
continue
begin_index = index + len(aqi_div)
# 改进:找到数值结束的位置(遇到'<'标签结束)
# 从开始位置往后找,找到第一个'<'为止
end_index = url_text.find('<', begin_index)
if end_index == -1:
end_index = begin_index + 3 # 保险起见,最多取3位
aqi_val = url_text[begin_index:end_index].strip() # strip()去掉可能的空格
print(f'{city}的空气质量为:{aqi_val}')
main()当前运行结果(三个城市页都已 410):
beijing:数据获取失败
shanghai:数据获取失败
guangzhou:数据获取失败看!循环、多城市、动态位数处理这些逻辑都写好了;只是因为目标站点当前不可用,每个城市都走进了“数据获取失败”分支。程序如实地逐个报告了失败,而不是带着错误数据往下跑——这种 continue 跳过失败项、继续处理下一个城市的写法,正是批量爬虫的标准做法。
网络爬虫的脆弱性(这一节最重要的体会)
刚才的经历就是最好的例子:目标网站直接返回了 410,我们准备好的提取逻辑一个数字都没拿到。 即便网站正常,这种字符串查找方法本身也很“脆弱”:
- 网站会失效或改版:页面一旦下线(410/404)或调整 HTML 结构,写死的
aqi_div立刻就找不到了——这是爬虫最大的不确定性。 - 处理复杂:用
find()和切片处理 HTML 比较麻烦,容易出错。 - 容错性差:网页稍有变化(比如多个空格),程序就可能失败。
所以写爬虫一定要做到两点:先看 status_code 是不是 200,再用 try...except 兜底(动手练一练第 2 题就是答案)。这就像用菜刀从一整只鸡上切下一块肉——能切下来,但不够优雅,容易切到手。
下一节预告:我们会学习一个更强大的工具——Beautiful Soup(美味的汤),它能像用筷子夹菜一样,优雅地从网页中提取我们需要的信息。
📝 动手练一练
扩展城市列表:修改程序,让它能查询你家乡的空气质量。你需要先到
http://pm25.in/网站上看看你的城市对应的拼音是什么。错误处理:现在的程序如果输入不存在的城市(比如
xxx),会直接报错。请添加错误处理,让程序在找不到城市时友好地提示用户。保存结果:让程序把查询结果保存到一个文本文件中,格式为“城市: 空气质量值”,每行一个城市。
👉 点击查看第2题参考答案
import requests
def get_html_text(url):
try:
r = requests.get(url, timeout=30)
if r.status_code == 200:
return r.text
else:
return None
except:
return None
def main():
city_pinyin = 'xxx' # 试试不存在的城市
url = 'http://pm25.in/' + city_pinyin
url_text = get_html_text(url)
if url_text is None:
print(f'无法访问{city_pinyin}的空气质量页面,请检查网络或城市名')
return
aqi_div = '''<div class="span12 data">
<div class="span1">
<div class="value">
'''
index = url_text.find(aqi_div)
if index == -1:
print(f'找不到{city_pinyin}的空气质量数据,可能城市名错误或网站改版')
return
begin_index = index + len(aqi_div)
end_index = url_text.find('<', begin_index)
if end_index == -1:
end_index = begin_index + 3
aqi_val = url_text[begin_index:end_index].strip()
print(f'{city_pinyin}的空气质量为:{aqi_val}')
main()无法访问xxx的空气质量页面,请检查网络或城市名思路说明:
- 在
get_html_text函数中添加try...except捕获网络错误 - 检查状态码,如果不是 200 就返回
None - 在主程序中判断返回值,给出友好的错误提示
- 如果找不到特定的 HTML 结构,也提示可能的原因
本章小结
这一节我们让程序“活”了起来——它不再只读本地文件,而是能主动向互联网发起请求。虽然示例站点当前已经 410,但你掌握的是可以套用到任何网站的通用流程。
📋 行动清单
- 运行今天的代码:亲眼看到 200(example.com)和 410(pm25.in)两种状态码的区别
- 换个网站试试:把
url换成你自己喜欢的任何网页,打印它的status_code和前 200 字符 - 思考一下:如果网站改版或下线了,程序要怎样才能不崩溃?(提示:状态码判断 + try/except)
我们学会了用 requests 模块发起网络请求、读懂状态码、用字符串查找尝试提取信息,也亲身体会了爬虫的脆弱。下一节,我们会学习更优雅的网页解析方法 Beautiful Soup,并继续强化容错,让程序更强大、更稳定。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。