1 HTTP 与 REST —— 程序之间的「普通话」
是什么?为什么学?
HTTP(超文本传输协议)是程序之间通信的通用语言;REST 是设计 API 的风格约定——把服务器上的资源(天气、用户、订单)用 URL 表示,用 HTTP 方法表示操作。看懂这两个概念,任何 API 文档在你面前都是「打开即用」。昨天爬网页拿的是 HTML——一堆给人看的标签;今天换一种思路:API(接口)是专门给程序用的「服务」,服务器直接返回结构化数据(通常是 JSON),不用再费劲从 HTML 里挑。
底层原理
HTTP 方法规定请求「想干什么」:GET 要数据(读,参数放 URL 里),POST 提交数据(写,数据放请求体里),还有 PUT(整体更新)、DELETE(删除)。URL 结构:协议://域名/路径?键=值&键=值。REST 风格下,GET /v1/forecast 就是「读预报资源」。GET 参数会暴露在 URL 里,别放密码;POST 数据在请求体里相对安全。
生活类比
像餐厅点餐:菜名是资源(URL),「来一份」是 GET(只读)、「加一份特制」是 POST(提交数据)、「退掉」是 DELETE。你说的话(HTTP 方法)决定了服务员(服务器)怎么处理。
注意事项总结
1 浏览器访问 URL 就是一次 GET 请求(需要联网):
import requests
url = "https://api.open-meteo.com/v1/forecast?latitude=39.9042&longitude=116.4074"
resp = requests.get(url, timeout=10)
print("状态码:", resp.status_code)
print("返回类型:", resp.headers.get("Content-Type"))
print("返回前 120 个字符:")
print(resp.text[:120])
resp.headers.get("Content-Type") 看返回类型——application/json 说明返回 JSON。
2 URL 结构拆解:
https://api.open-meteo.com/v1/forecast?latitude=39.9042&longitude=116.4074
│____________________________│ │________│ │_________________________│
协议 + 域名 路径 查询参数(问号 ? 之后)
查询参数 键=值 用 & 连接。
易错点
- 错误写法:GET 请求里放密码 → 问题:参数在 URL 里,会被日志/历史记录暴露 → 正确写法:敏感信息用 POST 放请求体。
- 错误写法:参数值含特殊字符(
/、&) → 问题:直接拼会破坏 URL 结构 → 正确写法:用params字典传参,自动编码。 - 错误写法:把 POST 当 GET 用 → 问题:语义混乱,服务器可能拒绝 → 正确写法:读数据用 GET,产生变化用 POST。
记忆口诀:GET 要数据,POST 提数据;资源用 URL,操作靠方法。
2 JSON —— API 的通用语言
是什么?为什么学?
JSON(JavaScript Object Notation)是一种轻量级数据交换格式,长得和 Python 的字典/列表几乎一样:{"键": 值, ...}、[元素, 元素]。几乎所有现代 API 都用 JSON 返回数据,学会解析 JSON 就等于学会读 API 的「回信」。
底层原理
JSON 字符串和 Python 对象的互转:json.loads(字符串) 把 JSON 转成字典/列表(json.dumps 反向);requests 更贴心——resp.json() 一步到位自动解析,内部就是 json.loads(resp.text)。JSON 里的键是字符串,值可以是数字、字符串、布尔、数组、嵌套对象。解析失败会抛 json.JSONDecodeError。
生活类比
像填写国际快递单:各国填写格式不同(JSON 是「国际通用格式」),快递公司(requests)帮你翻译成中文(Python 字典),你直接读就行,不用自己学外语。
注意事项总结
1 JSON 字符串与 Python 对象互转(离线可跑):
import json
text = '{"name": "北京", "weather": "晴", "temp": 32, "hours": ["8时", "14时"]}'
data = json.loads(text) # 字符串 -> 字典
print(data["name"]) # 北京
print(data["temp"] + 1) # 数字可以直接计算:33
print(data["hours"][0]) # 嵌套列表按下标取:8时
back = json.dumps({"city": "上海"}, ensure_ascii=False) # 字典 -> 字符串
print(back) # {"city": "上海"}
运行结果:
北京
33
8时
{"city": "上海"}
json.loads(text) 把 JSON 字符串转成字典;data["temp"] + 1 说明数字转成 int 可直接运算;json.dumps(..., ensure_ascii=False) 让中文不变成转义码。
2 resp.json() 一步解析 API 返回(需要联网):
import requests
resp = requests.get("https://api.open-meteo.com/v1/forecast?latitude=39.9042&longitude=116.4074",
timeout=10)
data = resp.json() # 自动把 JSON 解析成 Python 字典
print(type(data)) # <class 'dict'>
print("时区:", data["timezone"])
易错点
- 错误写法:JSON 键名记错 → 问题:报
KeyError→ 正确写法:先print(data)看真实结构再取值。 - 错误写法:忘了
ensure_ascii=False→ 问题:中文被转成反斜杠加 u 的转义 → 正确写法:json.dumps(data, ensure_ascii=False)。 - 错误写法:返回的不是 JSON 却调
resp.json()→ 问题:抛json.JSONDecodeError→ 正确写法:先print(resp.text)看返回了什么。
记忆口诀:JSON 像字典,loads 变对象;dumps 转回去,ensure_ascii 保中文。
3 GET 请求与查询参数 —— 用 params 优雅传参
是什么?为什么学?
调用 API 经常要带参数(经纬度、城市、页数)。手拼 URL 容易错、容易漏转义;requests.get(url, params={...}) 用字典传参,requests 自动完成 URL 编码(比如把 / 变成 %2F),代码又干净又不容易错。
底层原理
params 字典里每个键值对会变成 ?键=值&键=值 拼到 URL 后面;值里的特殊字符自动编码。Open-Meteo 核心参数:latitude / longitude(经纬度)、daily(要哪些每日数据,逗号分隔)、timezone(时区,必须写 Asia/Shanghai,否则返回 UTC 时间)、forecast_days(预测天数)。
生活类比
像便利店点单:params 是点单纸,写上「大杯、少冰、加珍珠」(键=值),店员(服务器)按单子做。点单纸比口头喊(手拼 URL)不容易出错。
注意事项总结
1 用 params 传参调用天气 API(需要联网):
import requests
params = {
"latitude": 39.9042,
"longitude": 116.4074,
"daily": "temperature_2m_max,temperature_2m_min",
"timezone": "Asia/Shanghai",
"forecast_days": 3,
}
resp = requests.get("https://api.open-meteo.com/v1/forecast",
params=params, timeout=10)
resp.raise_for_status() # 状态码非 2xx 直接抛异常
data = resp.json()["daily"] # 解析并直接取 daily 部分
for date, tmax, tmin in zip(data["time"],
data["temperature_2m_max"],
data["temperature_2m_min"]):
print(f"{date}:最高 {tmax}℃ / 最低 {tmin}℃")
params 字典列出参数,requests.get(url, params=params) 自动拼 URL 并编码;resp.raise_for_status() 状态码不是 2xx 就抛异常;resp.json()["daily"] 解析后直接取 daily 键;zip(...) 把日期、最高温、最低温三个列表按位置打包遍历。
易错点
- 错误写法:忘写
timezone参数 → 问题:返回 UTC 时间,日期可能「差一天」 → 正确写法:传"timezone": "Asia/Shanghai"。 - 错误写法:参数名拼错(如
lat不是latitude) → 问题:参数不生效,返回默认位置数据 → 正确写法:对照 API 文档检查参数名。 - 错误写法:
daily参数不指定 → 问题:只返回current_weather,没有每日数据 → 正确写法:"daily": "temperature_2m_max,temperature_2m_min"。
记忆口诀:params 传参最优雅,编码转义全自动;timezone 要写对,不然日期差一天。
4 POST 请求 —— 用 data 和 json 提交数据
是什么?为什么学?
GET 只能拿数据,想「提交」(登录、下单、发消息)必须用 POST。requests 提供两种提交格式:data={...} 按表单格式(application/x-www-form-urlencoded)提交,json={...} 按 JSON 格式提交。
底层原理
requests.post(url, data={"city": "北京"}) 把字典编码成表单格式放进请求体;requests.post(url, json={"city": "北京"}) 把字典转成 JSON 字符串并自动设置 Content-Type: application/json。区别就在请求体格式和 Content-Type 头——服务器按头里声明的格式解析。httpbin.org 是免费的回显测试接口,你 POST 什么它原样返回什么。
生活类比
像寄快递填单:data 是「普通表单」(纸质单),json 是「电子单」(扫码录入)。两种都能寄到(服务器都能收到),但格式不同,别混着写。
注意事项总结
1 POST 的两种提交格式(需要联网):
import requests
# 格式一:data 表单
r1 = requests.post("https://httpbin.org/post",
data={"city": "北京", "days": 3}, timeout=10)
d1 = r1.json()
print("表单收到的数据:", d1["form"]) # {'city': '北京', 'days': '3'}
print("Content-Type:", d1["headers"]["Content-Type"]) # 表单格式
# 格式二:json
r2 = requests.post("https://httpbin.org/post",
json={"city": "北京", "days": 3}, timeout=10)
d2 = r2.json()
print("JSON 收到的数据:", d2["json"]) # {'city': '北京', 'days': 3}
两次 POST 都发到 https://httpbin.org/post(回显接口);data= 提交时数据放在 form 字段返回,注意 days 变成字符串 '3';json= 提交时数据放在 json 字段返回,days 保持数字 3——这是两种格式最直观的区别。
易错点
- 错误写法:POST 不加
timeout→ 问题:服务器不响应时无限卡住 → 正确写法:requests.post(url, data=..., timeout=10)。 - 错误写法:想发 JSON 却用
data=→ 问题:服务器按表单解析,嵌套数据会丢 → 正确写法:发 JSON 用json=参数。 - 错误写法:把 GET 参数拼在 POST 的 URL 里 → 问题:数据没进请求体,服务器读不到 → 正确写法:数据放
data=或json=。
记忆口诀:POST 提交两种姿:data 表单 json 电子单;数据要进请求体,timeout 别忘加。
5 错误处理 —— 三层保险,调用不崩
是什么?为什么学?
API 调用失败很常见:断网、超时、参数错(400)、接口不存在(404)、服务器故障(500)、返回不是 JSON。不加保护的调用,一个异常就崩溃。学会 timeout + raise_for_status + try/except 三层保险,是调用任何 API 的职业习惯。
底层原理
三层分工:**timeout=10 管「等多久」——10 秒没响应就放弃;raise_for_status() 管「HTTP 层」——状态码非 2xx 抛 HTTPError(400、404、500 都算);try/except 管「网络与解析层」**——ConnectionError 连不上、Timeout 超时、ValueError JSON 解析失败(JSONDecodeError 是 ValueError 的子类)。异常从内到外,一网打尽。
生活类比
像外卖三层保障:timeout 是「30 分钟不送到我就取消」;raise_for_status 是「商家说没这个菜」(状态明确告知);try/except 是「骑手迷路了也先安慰你别崩溃」(出了岔子程序照样活着)。
注意事项总结
1 三层保护的天气查询函数(需要联网):
import requests
def fetch_weather(latitude, longitude):
params = {
"latitude": latitude,
"longitude": longitude,
"daily": "temperature_2m_max,temperature_2m_min",
"timezone": "Asia/Shanghai",
"forecast_days": 3,
}
try:
resp = requests.get("https://api.open-meteo.com/v1/forecast",
params=params, timeout=10)
resp.raise_for_status()
return resp.json()
except requests.exceptions.ConnectionError:
print("网络连接失败:请检查网络")
except requests.exceptions.Timeout:
print("请求超时:服务器响应太慢")
except requests.exceptions.HTTPError as e:
print(f"HTTP 错误:{e}") # 如 400 参数错误、404 接口不存在
except ValueError:
print("返回内容不是有效的 JSON")
return None
data = fetch_weather(39.9042, 116.4074)
if data:
print("调用成功,最高温:", data["daily"]["temperature_2m_max"])
else:
print("调用失败,未拿到数据")
函数封装请求;try 里三步走(请求 → 检查状态码 → 解析 JSON);四个 except 分别接住连接失败、超时、HTTP 错误、JSON 解析失败——任何环节出错都不崩溃,返回 None 由调用方判断。
易错点
- 错误写法:只
try/except不写raise_for_status()→ 问题:404 页面也能「成功」返回,解析出错 → 正确写法:状态码检查与异常处理两层都要。 - 错误写法:
except Exception一把抓 → 问题:连代码 bug 都吞掉,难排查 → 正确写法:按异常类型分别接住。 - 错误写法:不加
timeout→ 问题:服务器不响应时 try 里无限等 → 正确写法:timeout=10必须写。
记忆口诀:timeout 管等待,raise 管状态,except 管兜底;三层保险齐,调用永不崩。
6 动手实践:城市天气查询工具
做一个「天气查询工具」:内置几个城市和经纬度,输入城市名,调用 Open-Meteo 返回未来 3 天天气并打印。
# weather_api.py —— 城市天气查询工具
import requests
# 城市名 -> (纬度, 经度)
CITIES = {
"北京": (39.9042, 116.4074),
"上海": (31.2304, 121.4737),
"广州": (23.1291, 113.2644),
"深圳": (22.5431, 114.0579),
"成都": (30.5728, 104.0668),
}
def query_weather(city):
if city not in CITIES:
print(f"没有「{city}」的坐标,可选:{'、'.join(CITIES)}")
return
lat, lon = CITIES[city]
params = {
"latitude": lat,
"longitude": lon,
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum",
"timezone": "Asia/Shanghai",
"forecast_days": 3,
}
try:
resp = requests.get("https://api.open-meteo.com/v1/forecast",
params=params, timeout=10)
resp.raise_for_status()
data = resp.json()["daily"]
print(f"\n{city} 未来 3 天天气:")
for date, tmax, tmin, rain in zip(
data["time"], data["temperature_2m_max"],
data["temperature_2m_min"], data["precipitation_sum"]):
print(f" {date} 最高 {tmax}℃ / 最低 {tmin}℃ 降水 {rain}mm")
except requests.exceptions.RequestException as e:
print(f"请求失败:{e}")
except (KeyError, ValueError):
print("返回数据格式异常")
if __name__ == "__main__":
city = input("输入城市名(北京/上海/广州/深圳/成都):").strip()
query_weather(city)
运行效果:
输入城市名(北京/上海/广州/深圳/成都):上海
上海 未来 3 天天气:
2026-08-03 最高 33.2℃ / 最低 26.8℃ 降水 0.0mm
2026-08-04 最高 31.5℃ / 最低 25.9℃ 降水 5.2mm
2026-08-05 最高 34.0℃ / 最低 26.2℃ 降水 0.0mm
输入一个不存在的城市名,程序也能优雅提示,不会崩溃。这就是「工具」该有的样子:能用的功能 + 合理的容错。
升级挑战:把查询结果保存成 JSON 文件(回忆 Day 12 的 json.dump),并顺手把 forecast_days 改成 7,看看一周的数据长什么样。
今日总结
HTTP 与 REST:GET 要数据、POST 提数据;URL = 协议 + 域名 + 路径 + 查询参数;GET 参数别放密码
JSON:json.loads / json.dumps(ensure_ascii=False)、resp.json() 一步解析,嵌套结构按层级取值
GET 传参:params 字典自动编码,timezone=Asia/Shanghai 别忘写
POST 提交:data= 表单 vs json= JSON,httpbin 回显接口实测两种格式的区别
三层保护:timeout + raise_for_status() + try/except(ConnectionError / Timeout / HTTPError / ValueError)
城市天气查询工具能查、能容错,API 调用全流程闭环
| 报错 | 原因 | 修复 |
|---|---|---|
requests.exceptions.ConnectionError | 连不上 API 服务器(断网、域名拼错) | 检查网络;确认 URL 完整且能直接在浏览器打开 |
requests.exceptions.Timeout | 服务器响应太慢,超过等待时间 | 加 timeout=10 并调大,或稍后重试 |
requests.exceptions.HTTPError: 400 Client Error | 参数写错或缺少必填参数 | 对照 API 文档检查参数名、格式、单位 |
KeyError: 'temperature_2m_max' | 返回结构里没有这个键(参数没生效或接口改版) | 先 print(data) 看实际返回结构,再调整取值路径 |
json.decoder.JSONDecodeError(或 ValueError: Expecting value) | 返回内容不是 JSON(比如 404 页面、空响应) | 先打印 resp.text 看返回了什么;raise_for_status() 前置拦截错误状态码 |