从 urllib 到 requests,从发一个 GET 到起一个 Web 服务;这篇把「客户端请求」和「服务端响应」两头都讲清楚,并补上 HTTP 与 REST 的常识。
你将学到
urllib的最小用法,以及为什么实际都用requestsrequests的 GET/POST、参数、headers、超时、重试、session 复用- 处理 JSON 响应、状态码与异常
- 解析 HTML 的思路:正则的边界与 BeautifulSoup 简介
- 用标准库
http.server或 Flask 写一个最小 Web 服务 - HTTP 状态码与 REST 概念扫盲
前置知识
需要 JSON 处理与异常基础,见 上一篇:数据处理 json/csv/dataclasses。
从 urllib 说起
标准库 urllib.request 能发请求,不用装任何东西,但 API 偏啰嗦。
# 标准库版:能跑,但样板代码多
import urllib.request, json
with urllib.request.urlopen("", timeout=5) as resp:
print(resp.status) # 输出: 200
print(json.loads(resp.read().decode("utf-8"))["args"]) # 输出: {'name': 'python'}
要发 POST、带表单、加 header,还得手动构造 Request 和编码 body,很快就烦了。这就是 requests 存在的原因,先 pip install requests。
requests:GET 与参数
import requests
# 参数交给 params,requests 负责编码和拼接
resp = requests.get(
"",
params={"q": "python 教程", "page": 2},
timeout=5, # ⚠️ 永远设超时,否则可能永久挂起
)
print(resp.status_code) # 输出: 200
print(resp.url) # 输出:
print(resp.json()["args"]) # 输出: {'q': 'python 教程', 'page': '2'}
别自己拼 URL 字符串——中文、空格、& 都会出问题,params 帮你转义。
headers 与 POST
import requests
headers = {"User-Agent": "Mozilla/5.0 (compatible; MyBot/1.0)", "Accept": "application/json"}
# 表单格式(application/x-www-form-urlencoded):用 data=
r = requests.post("",
data={"username": "alice", "password": "secret"},
headers=headers, timeout=5)
print(r.json()["form"]) # 输出: {'username': 'alice', 'password': 'secret'}
# JSON 格式:用 json=,自动设置 Content-Type: application/json
r = requests.post("",
json={"title": "hi", "tags": ["a", "b"]}, headers=headers, timeout=5)
print(r.json()["json"]) # 输出: {'title': 'hi', 'tags': ['a', 'b']}
注意区分:data= 是表单,json= 是 JSON body。用错了服务端会返回 400 或解析失败。
超时、重试与 session
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# ✅ 推荐做法:session + 自动重试
session = requests.Session()
retry = Retry(
total=3, # 最多重试 3 次
backoff_factor=0.5, # 退避:0.5s, 1s, 2s
status_forcelist=[429, 500, 502, 503, 504], # 这些状态码才重试
allowed_methods=["GET", "POST"],
)
session.mount("https://", HTTPAdapter(max_retries=retry))
# session 自动复用 TCP 连接、保持 cookies,比每次 requests.get 快
resp = session.get("", timeout=(3.05, 10)) # (连接超时, 读取超时)
print(resp.status_code) # 输出: 200
timeout 传单值表示连接和读取都用这个值;传元组 (connect, read) 更精确。重试要小心:只对幂等的 GET 重试,POST 乱重试可能重复下单。
状态码与异常
import requests
from requests.exceptions import (
HTTPError, Timeout, ConnectionError, RequestException,
)
try:
resp = requests.get("", timeout=5)
resp.raise_for_status() # 4xx / 5xx 抛 HTTPError
data = resp.json()
except HTTPError as e:
print("HTTP 错误:", e.response.status_code) # 输出: HTTP 错误: 404
except Timeout:
print("请求超时")
except ConnectionError:
print("连接失败")
except RequestException as e:
print("其他请求异常:", e)
raise_for_status() 是把「HTTP 状态码错误」转成「异常」的关键一步,否则 resp.json() 可能在错误页面上抛更莫名其妙的错。
处理 JSON 响应:
resp = requests.get("", timeout=5)
resp.encoding = "utf-8" # 服务端没声明对编码时可手动纠正
try:
print(resp.json()["slideshow"]["title"]) # 输出: Sample Slide Show
except ValueError: # Content-Type 不是 JSON 时抛 ValueError
print("响应不是合法 JSON,原始内容:", resp.text[:200])
简单 HTML 解析
拿到 HTML 后想提取内容,两条路:
import re
import requests
html = requests.get("", timeout=5).text
# ✅ 正则适合结构规整、目标单一的场景(如提取 <title>)
m = re.search(r"<title>(.*?)</title>", html, re.S)
print(m.group(1) if m else "无标题")
# ❌ 别用正则解析嵌套 HTML —— 结构一变就崩,属于经典反模式
# 复杂提取请用解析库
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "lxml") # pip install beautifulsoup4 lxml,lxml 比内置 html.parser 快
print(soup.title.string) # 输出: Example Domain
for a in soup.find_all("a"): # 按标签查找并取属性
print(a.get("href"), "->", a.text)
原则:正则提取单点、结构库解析整体。真要抓大站还要考虑 robots.txt、频率限制和法律问题,别乱来。
最小 Web 服务:http.server
标准库自带一个 HTTP 服务器,几行就能起一个接口。
# server.py —— 标准库实现,零依赖
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/health":
body = json.dumps({"status": "ok"}).encode("utf-8")
self.send_response(200) # 状态码
self.send_header("Content-Type", "application/json; charset=utf-8")
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404); self.end_headers(); self.wfile.write(b"not found")
def log_message(self, *args):
pass # 静音默认日志
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
python server.py # 另一个终端执行:
curl # 输出: {"status": "ok"}
http.server 适合本地调试和小工具,不要用于生产——它是单线程的,没有并发、安全和性能加固。
最小 Web 服务:Flask
先 pip install flask。
# app.py —— Flask hello world,含一个 JSON 接口
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.get("/")
def index():
return "Hello, Web!"
@app.get("/api/greet")
def greet():
name = request.args.get("name", "world") # 取查询参数
return jsonify(message=f"Hello, {name}!")
@app.post("/api/echo")
def echo():
payload = request.get_json(silent=True) or {} # 解析 JSON body
return jsonify(received=payload), 201 # 返回 201 Created
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
python app.py # 另开终端:
curl "" # 输出: {"message":"Hello, py!"}
curl -X POST -H "Content-Type: application/json" -d '{"a": 1}'
# 输出: {"received":{"a":1}}
连起来看:你的客户端用 requests 发 JSON,服务端用 Flask 收 JSON——这就是绝大多数前后端交互的最小单元。
HTTP 状态码与 REST 扫盲
状态码分段记忆:
1xx信息,基本见不到2xx成功:200 OK、201 Created、204 No Content3xx重定向:301永久、302临时、304未修改(配合缓存)4xx客户端错:400参数错、401未认证、403无权限、404找不到、429限流5xx服务端错:500内部错误、502网关错、503不可用、504网关超时
REST 的核心约定(务实版,不必教条):
- 用名词复数表示资源:
/users、/orders/42 - 用HTTP 方法表达动作:
GET查、POST建、PUT全量改、PATCH局部改、DELETE删 - 用状态码表达结果,而不是在 body 里返回
{"code": 200} - 无状态:每个请求自带认证信息,服务端不依赖上次请求的会话
GET /users 列出用户
POST /users 新建用户
GET /users/42 查看某个用户
PUT /users/42 整体更新
DELETE /users/42 删除
常见坑
# ❌ 不设 timeout,网络卡住时程序永远挂着
requests.get("")
# ✅ 显式设置超时
requests.get("", timeout=5)
# ❌ 把查询参数直接拼进 URL,中文/空格/& 全乱
requests.get(" world&x=1")
# ✅ 用 params
requests.get("", params={"q": "hello world", "x": 1})
# ❌ 不看状态码就 .json(),404 页面解析失败抛奇怪的错
data = requests.get(url).json()
# ✅ 先 raise_for_status,再解析
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
# ❌ 循环里每次 requests.get,反复建连接、还容易被限流
for u in urls:
requests.get(u, timeout=5)
# ✅ 用 Session 复用连接,或加 Retry 处理偶发失败
sess = requests.Session()
for u in urls:
sess.get(u, timeout=5)
# ❌ 把 http.server 拿去跑生产接口
HTTPServer(("0.0.0.0", 80), Handler).serve_forever()
# ✅ 生产用成熟框架 + WSGI 服务器:Flask + gunicorn/waitress,或 FastAPI + uvicorn
小结
requests是事实标准;params管查询参数,json=发 JSON,data=发表单。timeout必设;重试只对幂等请求,退避用Retry;循环请求用Session复用连接。- 先
raise_for_status()再解析;把RequestsException分门别类捕获。 - HTML 提取:正则适合单点,整体解析用 BeautifulSoup/lxml。
- 起服务:
http.server只用于本地调试,Flask 是入门 Web 的顺手起点。 - 记住状态码分段与 REST 的名词 + 方法约定,接口设计就八九不离十。
延伸阅读
- 官方/项目文档:
requests、http.server、Flask、BeautifulSoup - 想要现代异步 Web,看 08-asyncio 异步编程全解
- 下一篇进入上手篇 18-面向对象编程
上一篇:数据处理 json/csv/dataclasses · 下一篇:面向对象编程
文章回复
0 条公开回复