第2章:安装配置与第一个HTTP请求 1. 项目背景业务场景某初创公司的后端团队刚刚成立CTO 决定技术栈采用 Python Django对外对接支付、短信、物流等十余个第三方 API。新人小王入职第一天就被分配了任务跑通第一个接口——调用快递鸟物流查询 API传入运单号返回物流轨迹。小王打开 PyCharm信心满满地写下import requests等待他的却是一连串红色报错ModuleNotFoundError: No module named requests。接着他四处搜索安装了 requests却又遇到SSLError、代理干扰、编码乱码等问题。一个本该 10 分钟搞定的接口调用硬是折腾了一上午。痛点问题一环境混乱。开发直接在系统 Python 中装包导致不同项目间的依赖版本冲突。项目 A 需要 requests 2.25项目 B 需要 2.32系统只能装一个版本要么降级要么升级总有一个项目会莫名其妙报错。问题二第一个请求就失败。很多新人写完requests.get()后遇到的不是 200 而是异常。SSL 证书问题Windows 最常见、代理配置问题公司内网必备、编码问题中文响应乱码成为新人三座大山。问题三只知其然。开发能跑通requests.get(url)就算过关但Response.text和Response.content的区别是什么Response.json()报错了怎么办这些问题在第一次 code review 时就会暴露。典型的新人坑总结pip install requests ↓ No module named requests ← 装到另一个 Python 了 ↓ (解决) SSLError ← Windows 系统 CA 证书问题 ↓ (解决) ProxyError ← 公司内网代理拦截 ↓ (解决) 乱码 ← response.text 的 encoding 猜错了 ↓ (解决) 终于拿到正确数据! (半天过去了...)2. 项目设计小胖瘫在椅子上一脸沮丧“大师救救我我 pip install 了 requests然后import requests代码三行搞定了结果报了一堆错。先是说没有这个模块然后 SSL 证书有问题然后代理又报错。我就想调个物流查询接口怎么跟唐僧取经一样要经历九九八十一难”大师看了一眼小胖的终端截图“你这个截图里pip install requests装到了系统 Python 3.8但 PyCharm 用的是 conda 环境的 Python 3.11——你装错地方了。这就好比你去前门买了票却想从后门进电影院检票员当然不让你进。”小胖恍然大悟“啊Python 也有好几个不是装一个就行了吗”大师“你可以把 Python 环境想象成厨房。每个虚拟环境就是一个独立的厨房——里面有自己的锅碗瓢盆Python 解释器、调料第三方库。你在厨房 A 买了盐厨房 B 当然用不了。虚拟环境就是用来隔离的——项目 A 用 requests 2.25项目 B 用 2.32互不干扰。”小白认真记笔记突然抬头“大师那我装好了 requests为什么还是报 SSL 错误我记得我把 URL 写成https://了没写错啊。”大师“这个锅得甩给 Windows。Python 在 Windows 上默认找不到系统信任的 CA 证书列表。HTTPS 通信需要验证对方的证书是否由受信任的 CA 签发——但 Python 说’我不知道该信谁’于是拒绝连接。这就像你要验证一个人的身份证但你手里没有公安局的印章样本你没法鉴定真伪。”小胖“那我用verifyFalse跳过验证不就行了我在网上搜的教程都这么写的”小白严厉地“绝对不行verifyFalse相当于把你所有的 HTTPS 通信都暴露在中间人攻击之下。攻击者可以在你公司出口路由器上伪造一个假的快递鸟服务器截获你所有的 API Key 和物流数据——而你完全感知不到。”大师“小白说得对。verifyFalse是方便但它是把家门钥匙放在门垫下面——方便是方便了小偷也方便了。正确做法是安装 CA 证书包。”生活比喻技术映射独立的厨房Python 虚拟环境venv/conda厨房里的调料虚拟环境中的第三方库公安局的印章样本SSL CA 证书包certifi把钥匙放在门垫下verifyFalse跳过证书验证验证身份证真伪SSL 证书校验小白追问“还有个问题。我看到response.text有时候中文是正常的有时候全是乱码。requests 怎么判断编码的”大师这又是一个新人高频踩坑区。requests 判断编码的逻辑是这样的优先看响应头里的Content-Type: text/html; charsetutf-8如果响应头没有声明 charsetrequests 会用chardet库去「猜」编码猜对了就万事大吉猜错了就是乱码举个例子有些老旧的中文网站响应头写的charsetISO-8859-1但实际内容是 GBK 编码的中文。requests 老老实实按照 ISO-8859-1 解码于是你看到的是满屏的æˆ‘è¦æ±‚头——这就是典型的编码猜错。解决方法是手动指定response.encoding gbk然后再取response.text。小胖“所以第一天的正确姿势是先建虚拟环境 → 确认 Python 版本 → pip install requests → 确认装进去了 → 再写requests.get()对吗”大师我再补充一个最重要的习惯——每当你requests.get()之后先别急着 print先检查这三个东西response.status_code—— 是不是 200不是 200 的话后面都是白做response.headers[Content-Type]—— 返回的是什么格式json? html? 图片?response.encoding—— requests 猜的编码对不对这三个检查做完再处理响应体。这个习惯能帮你少踩 80% 的坑。3. 项目实战环境准备虚拟环境创建三种方式任选其一# 方式一Python 内置 venv推荐无需额外安装python-mvenv requests-lab# Windows 激活requests-lab\Scripts\activate# Mac/Linux 激活sourcerequests-lab/bin/activate# 方式二conda适合数据科学/机器学习项目conda create-nrequests-labpython3.11conda activate requests-lab# 方式三pipenv自带虚拟环境和依赖锁定pipinstallpipenv pipenvinstallrequests pipenv shell安装 requests 并验证# 确认当前 Python 路径这一步很重要!python--version# 应输出: Python 3.11.xwhere python# Windows: 确认路径指向虚拟环境whichpython# Mac/Linux: 确认路径指向虚拟环境# 安装最新稳定版pipinstallrequests# 验证安装python-cimport requests; print(requests.__version__)# 输出: 2.32.4分步实现步骤一第一个 GET 请求——从Hello World开始目标发起第一个 HTTP 请求理解 Response 对象的基本结构和属性。importrequests# # Step 1: 最简单的 GET 请求# responserequests.get(https://httpbin.org/get)# 这个 Response 对象是一个宝藏包含了一次 HTTP 交互的全部信息print(f状态码:{response.status_code})print(f状态描述:{response.reason})print(f请求耗时:{response.elapsed})print(f最终 URL:{response.url})print(fHTTP 版本:{response.raw.versionifresponse.rawelseN/A})# # Step 2: 三种方式获取响应体# # 方式 A二进制原始字节未解码raw_bytesresponse.contentprint(f\n二进制内容 ({len(raw_bytes)}bytes):)print(raw_bytes[:50])# b{\n args: {}, \n headers: {\n Accept# 方式 B文本自动解码后的字符串textresponse.textprint(f\n文本内容 ({len(text)}个字符):)print(text[:200])# 方式 CJSON直接解析为 Python dictdataresponse.json()print(f\nJSON 解析:)print(f args:{data[args]})print(f origin:{data[origin]})print(f url:{data[url]})运行输出状态码: 200 状态描述: OK 请求耗时: 0:00:00.823456 最终 URL: https://httpbin.org/get HTTP 版本: 11 二进制内容 (350 bytes): b{\n args: {}, \n headers: {\n Accept 文本内容 (350 个字符): { args: {}, headers: { Accept: */*, Accept-Encoding: gzip, deflate, ... JSON 解析: args: {} origin: 123.45.67.89 url: https://httpbin.org/get关键认知response.textvsresponse.contentvsresponse.json()属性/方法返回类型何时用response.contentbytes下载图片/PDF/二进制文件response.textstr获取 HTML 页面/纯文本 APIresponse.json()dict/listRESTful JSON API步骤二请求一个需要参数的真实 API目标调用真实的开放 API理解参数传递方式。importrequests# 调用 OpenWeatherMap 天气 API免费注册获取 API Key# 这里用 httpbin 模拟带参数的请求responserequests.get(https://httpbin.org/get,params{q:北京,appid:YOUR_API_KEY,# 实际项目从环境变量读取units:metric,lang:zh_cn})print(f完整 URL:{response.url})# 输出: https://httpbin.org/get?q%E5%8C%97%E4%BA%ACappidYOUR_API_KEYunitsmetriclangzh_cnprint(f服务端看到的参数:{response.json()[args]})# 输出: {appid: YOUR_API_KEY, lang: zh_cn, q: 北京, units: metric}步骤三处理中文编码问题目标解决 requests 自动编码检测失败导致的乱码问题。importrequests# 模拟某些服务器未正确声明 charset导致乱码# 场景访问一个实际编码为 gb2312 但响应头声明为 ISO-8859-1 的中文网站responserequests.get(https://www.baidu.com)# 查看 requests 猜测的编码print(f自动检测编码:{response.encoding})# 可能输出: ISO-8859-1# 检查响应头的 Content-Typecontent_typeresponse.headers.get(Content-Type,)print(fContent-Type:{content_type})# 如果发现乱码手动修正编码# response.encoding utf-8# 或使用 apparent_encodingchardet 的检测结果print(fapparent_encoding(chardet 推测):{response.apparent_encoding})# 强制使用 chardet 检测的编码response.encodingresponse.apparent_encodingprint(f修正后文本(前100字符):{response.text[:100]})编码问题决策树response.text 是乱码吗 ├── 否 → 直接用不用管 └── 是 → 检查 response.headers[Content-Type] ├── 有明确的 charset → 对比实际内容不一致则手动设置 encoding └── 无 charset 或错误 → response.encoding response.apparent_encoding └── 还不行 → 直接看原始字节 response.content[:500]肉眼判断编码步骤四编写健壮的请求包装函数目标封装一个函数把前面学到的坑都填上。importrequestsfromtypingimportOptional,Dict,Anyimportlogging logging.basicConfig(levellogging.INFO)loggerlogging.getLogger(__name__)defsafe_get(url:str,params:Optional[Dict]None,headers:Optional[Dict]None,timeout:float10.0,retries:int1)-Dict[str,Any]:健壮的 GET 请求包装函数 返回格式: {success: bool, data: Any, error: str} forattemptinrange(retries1):try:logger.info(f[Attempt{attempt1}] GET{url})responserequests.get(url,paramsparams,headersheaders,timeouttimeout,verifyTrue# 生产环境必须为 True)# 先检查 HTTP 状态码response.raise_for_status()# 尝试 JSON 解析try:dataresponse.json()exceptValueError:dataresponse.text elapsedresponse.elapsed.total_seconds()logger.info(f →{response.status_code}({elapsed:.2f}s))return{success:True,data:data,error:None}exceptrequests.exceptions.Timeout:logger.warning(f 请求超时 (timeout{timeout}s))ifattemptretries:return{success:False,data:None,error:timeout}exceptrequests.exceptions.SSLErrorase:logger.error(f SSL 证书错误:{e})return{success:False,data:None,error:fssl_error:{e}}exceptrequests.exceptions.ConnectionErrorase:logger.error(f 连接失败:{e})ifattemptretries:return{success:False,data:None,error:fconnection_error:{e}}exceptrequests.exceptions.HTTPErrorase:logger.error(f HTTP 错误:{e})return{success:False,data:None,error:fhttp_error:{e}}exceptExceptionase:logger.exception(f 未知错误:{e})return{success:False,data:None,error:str(e)}# 测试 if__name____main__:# 正常请求resultsafe_get(https://httpbin.org/get,params{test:hello})print(result)# 超时测试请求一个无法连接的主机resultsafe_get(https://10.255.255.1,timeout1.0,retries0)print(result)可能遇到的坑及解决方法坑1ModuleNotFoundErrorModuleNotFoundError: No module named requests原因装到了错误的 Python 环境。解决# 确认你当前用的 Pythonpython--version# 用完整路径安装到当前 Pythonpython-mpipinstallrequests坑2SSLErroron WindowsSSLError: [SSL: CERTIFICATE_VERIFY_FAILED]原因Windows 上 Python 找不到 CA 证书包。解决pipinstallpip-system-certs或手动指定importcertifi requests.get(url,verifycertifi.where())坑3代理配置混乱ProxyError: Cannot connect to proxy原因系统/环境变量中配置了代理但你不需要。解决# 临时绕过代理requests.get(url,proxies{http:None,https:None})# 或正确配置requests.get(url,proxies{https:http://proxy.company.com:8080})坑4编码乱码# response.text 输出: 我是中文解决response.encoding response.apparent_encoding或utf-8/gbk测试验证importpytestimportrequestsclassTestChapter2FirstRequests:验证第2章安装配置与第一个HTTP请求deftest_requests_version(self):验证 requests 已正确安装importrequests versionrequests.__version__assertversion2.32deftest_get_hello_world(self):最简单的 GET 请求验证resprequests.get(https://httpbin.org/get)assertresp.status_code200assertisinstance(resp.json(),dict)deftest_params_encoding(self):验证 params 参数的 URL 编码resprequests.get(https://httpbin.org/get,params{name:小胖,age:25})dataresp.json()assertdata[args][name]小胖assertdata[args][age]25deftest_text_vs_content(self):验证 text 和 content 的区别resprequests.get(https://httpbin.org/get)assertisinstance(resp.content,bytes)assertisinstance(resp.text,str)deftest_encoding_handling(self):验证编码处理resprequests.get(https://www.baidu.com)# encoding 应该是 requests 自动检测的结果assertisinstance(resp.encoding,str)# apparent_encoding 应有值assertresp.apparent_encodingisnotNone4. 项目总结优点 缺点对比维度requestsurllib.requesthttp.client安装难度★★★★★pip 一行★★★★★内置★★★★★内置第一个请求代码量2 行5 行15 行编码自动处理★★★★☆★★☆☆☆☆☆☆☆☆SSL 配置友好度★★★★☆★★★☆☆★★☆☆☆错误信息可读性★★★★★★★☆☆☆★☆☆☆☆虚拟环境兼容★★★★★★★★★★★★★★★适用场景新手学习 HTTP 编程API 简洁异常清晰快速脚本一次性数据拉取、API 测试自动化运维健康检查、监控数据采集数据分析和爬虫配合 BeautifulSoup/Pandas不适用场景无网络环境requests 本身无离线模式需额外配合缓存层极致性能要求requests 是同步的高并发需异步方案注意事项虚拟环境是必修课永远不要在系统 Python 中pip install项目依赖verifyFalse是毒药测试环境也不行用自签证书 certifi 替代timeout必须设置默认None意味着请求可能会永远卡住response.text和response.content不同前者是解码后的文本后者是原始字节常见踩坑经验案例一pip install装到系统 Python。某新人用 PyCharm 打开项目terminal 中执行pip install requests成功PyCharm 中依然报ModuleNotFoundError。根因PyCharm terminal 用的是系统 PythonPyCharm 运行环境是 venv。修复在 PyCharm 中打开 terminal 后先用which python确认路径。案例二Docker 镜像中 SSL 报错。CI 流水线中构建的 Docker 镜像运行时报CERTIFICATE_VERIFY_FAILED。根因Dockerfile 使用python:3.11-slim精简镜像缺少 CA 证书包。修复Dockerfile 中加入RUN apt-get update apt-get install -y ca-certificates rm -rf /var/lib/apt/lists/*。案例三线上编码乱码。调用一个老旧内部系统的接口开发环境一切正常生产环境打印日志全是乱码。根因该接口响应头没有Content-Typerequests 猜测编码时受环境变量LC_ALL影响。修复明确定义response.encoding gbk该系统的实际编码。思考题编码题假设你遇到一个响应response.content的前几个字节是b\xef\xbb\xbf...UTF-8 BOM但response.text输出的第一个字符是一个不可见字符。这是什么原因如何正确处理带 BOM 的 UTF-8 文本提示查阅 codecs 模块的utf-8-sig编码安全题某同事在代码评审时说测试环境verifyFalse没关系生产环境再改成 True。这个说法有什么问题如果你在测试环境使用verifyFalse你的测试用例真的能在生产环境复现吗提示思考verifyFalse跳过了 TLS 握手的哪些步骤延伸阅读与资源后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析