Administrator
Administrator
发布于 2026-10-31 / 0 阅读
0
0

Python 从入门到初级(15):HTTP 与接口:读取商品并发送 JSON

本篇是《Python 从入门到初级》第 15 / 20 篇。示例使用 Python 3.12 兼容语法,完整目录结构与数据见配套源码包。

Python 不只处理本地文件,也能与其他系统通信。本篇在本机运行一个教学接口,练习 GET 读取商品、POST 发送 JSON,以及将响应转回列表和字典。

Python 请求 HTTP 接口,解析 JSON,再使用业务字段

1. 请求与响应先看四件事

请求包含方法、URL、请求头和可能的正文;响应包含状态码、响应头和正文。GET 通常用于读取,POST 常用于提交数据,真实业务中的写入行为必须按接口约定判断。

收到 HTTP 200 也不代表正文一定符合预期,仍需检查 JSON 能否解析、字段是否存在及含义是否正确。

本例只连接 127.0.0.1:18084,无需外部账号。服务是教学程序,不作为生产 Web 服务使用。

2. 先启动配套教学服务

将源码包中的 api_demo.py 保留在示例目录,在终端 A 运行:

python api_demo.py

服务提供两个端点:GET /api/products 返回商品列表;POST /api/echo 回显 title,明确返回 stored=false,不会写数据库。

服务端完整代码如下,初次学习可以先运行,把注意力放在后面的客户端:

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class Handler(BaseHTTPRequestHandler):
    def send_json(self, status, payload):
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        try:
            self.wfile.write(body)
        except ConnectionError:
            pass

    def do_GET(self):
        if self.path != "/api/products":
            self.send_json(404, {"error": "not found"})
            return
        self.send_json(200, {"items": [
            {"name": "键盘", "price_cents": 19900},
            {"name": "鼠标", "price_cents": 5900},
        ]})

    def do_POST(self):
        if self.path != "/api/echo":
            self.send_json(404, {"error": "not found"})
            return
        try:
            size = int(self.headers.get("Content-Length", "0"))
            if not 0 < size <= 4096:
                raise ValueError("invalid body size")
            payload = json.loads(self.rfile.read(size).decode("utf-8"))
            if not isinstance(payload, dict) or not isinstance(payload.get("title"), str):
                raise ValueError("title must be text")
        except (ValueError, UnicodeDecodeError):
            self.send_json(400, {"error": "invalid JSON or title"})
            return
        self.send_json(200, {"received": payload["title"], "stored": False})


if __name__ == "__main__":
    ThreadingHTTPServer(("127.0.0.1", 18084), Handler).serve_forever()

3. 完整客户端:读取和提交 JSON

终端 B 运行 python lesson15.py:

import json
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


def main(base_url="http://127.0.0.1:18084"):
    try:
        with urlopen(base_url + "/api/products", timeout=3) as response:
            payload = json.loads(response.read(65536).decode("utf-8"))
        for item in payload["items"]:
            print(f"{item['name']}:{item['price_cents'] / 100:.2f} 元")

        data = json.dumps({"title": "学习 Python 调用接口"}, ensure_ascii=False).encode("utf-8")
        request = Request(base_url + "/api/echo", data=data,
                          headers={"Content-Type": "application/json"}, method="POST")
        with urlopen(request, timeout=3) as response:
            result = json.loads(response.read(65536).decode("utf-8"))
        print("POST 回显:", result["received"])
        print("写入数据库了吗:", result["stored"])
    except HTTPError as exc:
        print("HTTP 请求失败:", exc.code)
        exc.close()
        return 1
    except (URLError, TimeoutError) as exc:
        print("连接或等待失败:", type(exc).__name__)
        return 1
    except (ValueError, KeyError, TypeError) as exc:
        print("响应结构不符合教学约定:", type(exc).__name__)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

预期显示键盘 199.00 元、鼠标 59.00 元,POST 回显学习标题,stored 为 False。

response.read() 得到字节;decode 按约定变成文字;json.loads 再把 JSON 文字解析成 Python 数据结构。请求方向则相反:字典先 json.dumps,再 encode 成字节提交。

4. 请求头和超时在做什么

POST 设置 Content-Type: application/json,告诉接收端正文采用什么格式。不同接口也可能需要 Authorization 等身份信息,但本地示例没有这一需求,不要给它随意加入真实令牌。

timeout 限制相关阻塞操作的等待,不是严格覆盖 DNS、重定向和完整处理的总时限。示例最多读取 65536 字节,适合小型教学响应;更大的 API 需要分页或其他受控读取方式。

urllib 的 URL 打开、代理和重定向行为见 官方文档。访问 HTTPS 时保持默认的证书验证,不要为了消除证书报错直接跳过校验。

5. 报错时按层看

服务器没启动或端口不对,通常是连接阶段失败。路径不对,可能收到 404;POST 内容不符合约定,教学服务返回 400;响应不是约定的 JSON,则可能在解析或访问字段时失败。

客户端先捕获 HTTPError,再处理 URLError,因为前者属于后者的子类。保留状态码有助于区分“对方返回错误”和“没拿到有效响应”。

这些异常并不自动证明具体业务根因,仍要结合 URL、服务日志和接口文档。站内 HTTP 巡检脚本文章 提供了更多失败路径的练习。

6. 练习与收尾

把 title 改成自己的学习目标,预期回显同步变化;把请求路径改成不存在的地址,观察 HTTP 状态错误;停止终端 A 的服务后再运行客户端,观察连接失败。

实验结束时在终端 A 按 Ctrl+C。真实接口中的 POST 可能创建资源,不应像本地回显一样随意重复发送;是否可重试,需要按接口的幂等约定决定。


上一篇:类与对象:建立带行为的设备档案 · 系列目录 · 下一篇:SQLite 数据库:保存订单与参数化查询


评论