python网络协议
本文摘要:HTTP与HTTPS的核心区别在于数据传输安全性,HTTPS通过SSL/TLS加密确保数据安全。HTTP状态码分为五大类,帮助开发者快速定位问题。TCP三次握手和四次挥手确保可靠连接建立与释放。RESTful API规范通过URL名词和HTTP动词实现清晰资源操作。GET和POST方法在参数传递、数据大小和安全性上存在显著差异,GET适合获取数据,POST适合提交数据。理解这些网络基础概
1、HTTP和HTTPS的区别
一、 核心概念速览
-
HTTP (超文本传输协议):
- 它是互联网上应用最广泛的网络协议,用于在 Web 浏览器和服务器之间传递信息。
- 致命弱点:明文传输。在 HTTP 下,你的数据(包括密码、银行卡号)就像是在寄一张没有任何包装的明信片,网络链路上的任何人(路由器、基站、网络运营商、黑客)都可以轻易截获并看到上面的内容。
-
HTTPS (安全超文本传输协议):
- 简单来说,HTTPS = HTTP + SSL/TLS 协议。
- 它在 HTTP 和底层的 TCP 请求之间加了一层安全层(SSL/TLS)。数据在发送前会被加密,接收后才会被解密,相当于给明信片加了一个上了锁的坚固保险箱。
二、 从网络底层模型看区别(OSI 模型层级)
要理解它们的底层区别,首先要知道网络通信是分层的。
-
HTTP 的传输过程: 应用层 (HTTP: 组装明文报文) -> 传输层 (TCP: 建立三次握手,分发数据) -> 网络层 (IP) -> 物理链路。 在这里,HTTP 直接把明文数据交给了 TCP 通道。
-
HTTPS 的传输过程: 应用层 (HTTP) -> 安全层 (SSL/TLS: 对明文进行加密) -> 传输层 (TCP) -> 网络层 (IP) -> 物理链路。 HTTP 被夹在中间,它不知道数据被加密了,TCP 也不知道它传输的是加密数据。核心的魔法全在中间的 SSL/TLS 层。
三、 HTTPS 底层加密原理:SSL/TLS 握手(核心!)
如果单纯用一种加密方式,会有很多问题。HTTPS 底层非常聪明地结合了对称加密、非对称加密和哈希算法。它的底层建立连接的过程(TLS 握手)是这样的:
1. 为什么不能只用一种加密?
- 对称加密(如 AES):加密解密用同一把钥匙。速度极快,适合传输大量数据。但问题是:你怎么安全地把这把钥匙送给对方?(如果钥匙被拦截,加密就废了)。
- 非对称加密(如 RSA):有公钥(公开)和私钥(只有自己有)。公钥加密的数据,只有私钥能解。极其安全,但计算极其缓慢,用来传输大文件电脑会卡死。
2. HTTPS 的神仙操作:混合加密机制
HTTPS 把这两种结合了起来:用缓慢的“非对称加密”去安全地协商出一把“对称加密的钥匙”,然后用这把“对称加密的钥匙”去飞速加密后续聊天内容。
具体步骤如下(底层握手过程):
- 打个招呼 (Client Hello / Server Hello):
- 客户端(浏览器/Python 程序):向服务器打招呼,发送自己支持的加密算法列表和一个随机数 A。
- 服务器:选定一种加密算法,生成一个随机数 B,连同自己的数字证书(里面包含公钥)一起发给客户端。
- 验证身份并生成“第三个随机数”:
- 客户端:验证你的证书是不是合法的(有没有过期,是不是伪造的)。验证通过后,客户端生成一个随机数 C(Pre-master secret)。
- 客户端:使用证书里的公钥,把随机数 C 加密,发送给服务器。
- 计算真正的对称密钥:
- 服务器:用自己死死捏在手里的私钥,解密得到了随机数 C。
- 这个时候,客户端和服务器手里都同时拥有了:随机数 A、随机数 B、随机数 C。双方使用相同的算法,把这三个随机数混合,生成一把最终的**“对称密钥”(Session Key)**。
- 安全加密通讯开始:
- 从此以后,双方就用这把新生成的、且只有他俩知道的**“对称密钥”**,使用对称加密算法对 HTTP 明文进行极其快速的加密和解密。
四、 HTTP 与 HTTPS 的直观数据对比
| 特性 | HTTP | HTTPS |
|---|---|---|
| 底层协议 | 建立在 TCP 之上 | 建立在 SSL/TLS 之上,再建立在 TCP 之下 |
| 端口号 | 默认 80 端口 | 默认 443 端口 |
| 安全性 | 明文传输,极不安全(易被窃听、篡改、伪造) | 密文传输,极高安全性(防窃听、防篡改、防伪造) |
| 资源消耗 | 较小(只需要 TCP 三次握手) | 较大(TCP 三次握手 + TLS 多次握手带来 CPU 和时间消耗) |
| 成本 | 免费 | 数字证书通常需要向 CA 机构购买(现在也有 Let's Encrypt 等免费证书) |
五、 在 Python 中是如何体现底层的?
既然你要学习 Python,我们可以看看在代码层面,这两者的底层差异。
1. Python 的 Socket 层(最底层)
在 Python 中,所有的网络请求最终都会调用内置的 socket 模块。
- HTTP: 就是建立一个普通的 TCP Socket 连接对方的 80 端口。
import socket # 原生 TCP 建立 HTTP 连接 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('example.com', 80)) s.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") # 发送的就是这段明文字符串 - HTTPS: 在普通的 TCP Socket 外面,必须套一层
ssl模块的包装。import socket import ssl # 1. 先建立普通的 TCP 连接 (443端口) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('example.com', 443)) # 2. 核心区别:用 ssl 模块对这个普通 socket 进行包装,进行 TLS 握手! context = ssl.create_default_context() secure_socket = context.wrap_socket(s, server_hostname='example.com') # 3. 现在再发送数据,底层会自动用协商好的密钥加密数据 secure_socket.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
2. Python 的高级库(如 requests)
在平时写 Python (比如爬虫、Web 后端) 时,你通常会用到 requests 这个强大的库。
- 当你请求
http://时,requests底层使用的就是普通 socket。 - 当你请求
https://时,requests底层会自动调用ssl模块帮你完成那些复杂的证书验证、密钥交换等握手过程。 - 这也是为什么初学 Python 爬虫时,如果遇到自签名证书的 HTTPS 网站会报错
SSLError,因为底层的ssl模块验证证书失败了。你通常需要加上requests.get('https://...', verify=False)来强行关闭证书验证(实际开发中不推荐,因为这会失去 HTTPS 防伪造的意义)。
总结: 理解 HTTP 和 HTTPS 的本质,其实就是理解网络数据是不是在“裸奔”。在后续学习 Python 的 requests 库、urllib 库或者开发 Django/Flask/FastAPI 后端时,你虽然不用自己手写加密算法,但理解了这层原理,你就能轻易看懂为什么有时会报证书错误,为什么 HTTPS 爬虫比 HTTP 稍微慢一点点,以及为什么要配置 SSL 证书。
2、HTTP状态码
HTTP 状态码是客户端(比如你的 Python 爬虫/浏览器)和服务器之间的“暗号”。它是3位数,分为五大类(五大门派)。
一、 核心分类速记(五大门派)
- 1xx (信息类):“请求我已收到,还在处理中,请稍等。”(在 Python 开发中几乎不可见,底层 TCP/HTTP 处理掉了)。
- 2xx (成功类):“没问题,搞定了!”这是你最希望看到的。
- 3xx (重定向):“你要找的东西搬家了,去新的地址找吧。”
- 4xx (客户端错误):“你的请求有问题,是你弄错了!”(比如网址错了、没权限)。
- 5xx (服务端错误):“哎呀,服务器崩溃了 / 出 Bug 了!”(对面的错,你的请求没问题)。
二、 Python 爬虫/后端 开发中最常遇到的状态码剖析
🟢 1. 成功门派 (2xx)
200 OK(一切正常)- 底层含义:服务器成功处理了请求,并且把你想要的数据(网页 HTML、JSON 数据等)放在了响应体里发给你了。
- Python 应对:当你看到
response.status_code == 200,意味着爬虫拿到了数据,可以开始用 BeautifulSoup 或正则去解析了。如果是写后端,这意味着你成功地向用户返回了页面。
🔵 2. 搬家门派 (3xx)
301 Moved Permanently(永久重定向)- 底层含义:你要找的网址 A 已经永久作废,以后请去网址 B 找。搜索引擎看到这个会更新自己的索引。
302 Found / Temporary Redirect(临时重定向)- 底层含义:你要找的东西暂时放在了网址 B,以后可能还会回网址 A。比如你在未登录状态下访问淘宝购物车页面,它会给你一个 302,把你临时重定向到登录页面。
- Python 应对:极其省心!在
requests库中,遇到 3xx 状态码默认是自动处理的(allow_redirects=True)。Python 会自动默默去请求那个新网址,最终给你返回 200。如果你想拦截并看看它重定向到哪了,可以设置allow_redirects=False,然后通过response.headers['Location']查看新网址。
🟠 3. 你的锅 (4xx) - 最常因为反爬或代码写错遇到
400 Bad Request(错误的请求)- 底层含义:服务器看不懂你发过来的数据。通常是因为你的参数格式不对。
- Python 应对:检查你
requests.post()里面传的data或json字典,是不是少了必填字段,或者类型错了(人家要数字你给了字符串)。
403 Forbidden(禁止访问) - 爬虫的噩梦- 底层含义:服务器懂你的请求,地址也对,但服务器果断拒绝了你,不给你看!
- Python 应对:在爬虫中这是最典型的被反爬拦截了!
- 原因 1:你没带
User-Agent,服务器一眼看出你是没穿马甲的 Python 脚本。解决:伪装请求头headers={'User-Agent': '...'}。 - 原因 2:需要登录(你的 Cookie 没带或者过期了)。
- 原因 3:你的 IP 抓取太快,被服务器拉黑了。解决:使用代理 IP 或放慢速度 (
time.sleep())。
- 原因 1:你没带
404 Not Found(未找到)- 底层含义:服务器上根本没有这个 URL 对应的资源。也许是文章被删了,也许是你把网址拼错了。
- Python 应对:检查你的代码逻辑,是不是拼接 URL 时多了一个斜杠
/或者少了一个字母。
🔴 4. 对方的锅 (5xx)
500 Internal Server Error(服务器内部错误)- 底层含义:你的请求完全合法,但对面的服务器(比如对面的 Python Django 后端程序)在处理你的请求时,代码出 Bug 报错崩溃了。
- Python 应对:如果是你自己的网站,赶紧去服务器上看 Python 报错日志(Traceback)。如果是爬别人的网站,你无能为力,这说明这网站本身就有 Bug,只能等对方程序员修复。
502 Bad Gateway/503 Service Unavailable- 底层含义:网关错误或服务不可用。通常是因为对方服务器流量太大被挤爆了、正在被 DDos 攻击、或者后端程序挂掉正在重启。
- Python 应对:这通常是临时性故障。在爬虫代码里,遇到 502/503 应该等待几秒后再次重试。
三、 Python 代码中的终极杀招
如果你用的是最常用的 requests 库,可以通过一行非常优雅的代码来处理这些错综复杂的状态码,避免写一大堆 if-else:
import requests
response = requests.get("https://example.com/api/data")
# 终极杀招:只有遇到 4xx 或 5xx 的错误时,才会直接主动报错 (抛出 HTTPError 异常)!
# 如果是 200 或 300,就当什么事都没发生,继续往下执行。
response.raise_for_status()
# 如果没报错,说明一定是成功的,你可以放心大胆地提取数据了
print(response.json())
3、TCP三次握手和四次挥手
一、 三次握手 (建立连接) —— “你在吗?我在。好,我开始说了。”
为什么要握手? TCP 是一种**“可靠”**的协议。在发送任何真正的数据(比如 HTTP 报文里的网页内容)之前,客户端(你的电脑)必须百分之百确定:对面的服务器活着,并且它准备好接我的数据了。。
假设客户端是 A(你),服务器是 B(淘宝服务器)。
- 第一次握手(A -> B): “喂,能听见吗?”
- 专业术语:客户端发送
SYN(Synchronize Sequence Numbers) 包。 - 大白话:A 问 B:“哥们,你在吗?我想跟你建立连接传数据了。”
- 专业术语:客户端发送
- 第二次握手(B -> A): “能听见,你准备好发了吗?”
- 专业术语:服务器发回
SYN + ACK包。 - 大白话:B 收到 A 的消息了,并且回答:“我在的!能听见!你随时可以发!”(ACK 是确认 A 的消息,SYN 是 B 反过来问 A 准备好没)。
- 专业术语:服务器发回
- 第三次握手(A -> B): “我也准备好了,咱们开始吧!”
- 专业术语:客户端回一个
ACK包。 - 大白话:A 收到 B 的回复了,A 回复 B:“好的!我也准备好了,连接正式建立!”
- 专业术语:客户端回一个
为什么一定是三次?两次不行吗? 绝对不行。你想想如果只有两次: A 喊了一句“能听见吗?”,B 回了一句“能听见”。 这时候 B 知道“A 能发消息,我能收消息”。但是 B 根本不知道 A 到底有没有听见自己的那句回复啊! 万一 B 的回复在网络上丢了呢?B 傻傻地在那等 A 传数据,A 却因为没收到回复而放弃了。 所以,只有 A 再回一句“我听见了你的回复”,双方才能 100% 确认彼此都能发消息并且能听到对方的声音。
Python 视角: 当你写下
s = socket.socket()然后s.connect(('taobo.com', 80))时,这看似只有一毫秒的代码,底层就是在疯狂进行这三次握手的过程!如果握手失败(比如淘宝服务器崩了或者没开 80 端口),Python 就会在那行代码直接抛出ConnectionRefusedError报错。
二、 四次挥手 (断开连接) —— “我讲完了,我要挂了。”
既然数据传完了,为了不占用宝贵的服务器资源,通道必须关掉。断开连接的过程比建立连接要复杂一点,需要四次。
咱们还是拿 A(客户端)和 B(服务器)举例。第一次挥手(A -> B): “B 哥,我说完了,我要挂断了啊。”
-
- 专业术语:A 发出
FIN(Finish) 包。
- 专业术语:A 发出
- 第二次挥手(B -> A): “好的,我知道你要挂了。(等我一下哈,我这还有点数据没传完呢)”
- 专业术语:B 回复
ACK包。 - 注意:此时,A 已经不能再发送新的数据给 B 了,但 B 还可以继续把没发完的数据发给 A。这叫做*“半关闭状态”**。*
- 专业术语:B 回复
- 第三次挥手(B -> A): “A 老弟,我这边的数据终于也发完了,我这边也要挂断了啊。”
- 专业术语:B 等自己数据传完后,也发出一个
FIN包。
- 专业术语:B 等自己数据传完后,也发出一个
- 第四次挥手(A -> B): “收到,再见!”
- 专业术语:A 回复
ACK包。 - 此时,挥手全部结束!连接彻底关闭。
- 专业术语:A 回复
为什么挥手需要四次,而握手只要三次? 核心原因出在 “半关闭状态”。 建立连接时,B 收到 A 的请求后,可以把 SYN(我想连接你)和 ACK(我收到了你的请求)合并成一个包发回去(就是第二次握手)。 但是在断开连接时,A 说完“我要挂了”,B 只能先赶紧回复一个 ACK(“我知道了”),因为 B 可能还有没传完的网页数据。B 必须等自己把所有最后的数据顺着管子都传干净了,才能再发那个 FIN(“我也要挂了”)。所以确认包(ACK)和断开包(FIN)不能合并,也就分成了四步。
Python 视角: 在爬虫或者 Web 开发中,通常在你调用
socket.close()或者response.close()时,底层网络栈就会默默地为你执行这优雅的四次挥手过程,把连接干干净净地释放掉,不留下一片云彩。
4.RESTful API
在 Python 开发里(尤其是用 Flask、Django、FastAPI 写后端,或者用 requests 写爬虫),你每天都会和它打交道。
原理篇:它到底是什么?
首先,API (应用程序接口) 就像是餐厅里的服务员。 你要点菜(前端请求数据),你不能直接冲进后厨自己炒菜(直接操作数据库),你得通过服务员(API)把你的需求传给后厨,然后服务员再把做好的菜端给你。
而 RESTful 不是一种新技术,也不是一种协议。它是一种约定俗成的“江湖规矩”,或者说是一种代码设计风格。
大家为了不让各种乱七八糟的 API 把人搞疯,就商量了一套极其优雅的规范:把网络上的所有东西,都看作是“资源(Resource)”,然后用 HTTP 自带的动词去操作它们。
核心规矩(三原则)
如果一个 API 遵循了 RESTful 风格,它必须符合这三个最明显的特点:
1. 网址(URL)只代表“名词”(这是什么资源)
在非 RESTful 时代,大家起名字很随意:
- ❌ 坏例子:
- 获取所有用户:
https://api.com/getAllUsers - 创建一个用户:
https://api.com/createUser - 删除一个用户:
https://api.com/deleteUser?id=1
- 获取所有用户:
在 RESTful 时代,URL 里绝对不能包含动词,只能是复数名词!
- ✅ 好例子:
- 资源就是用户:
https://api.com/users - 某一个特定的用户:
https://api.com/users/1
- 资源就是用户:
2. 用 HTTP “动词”来代表动作(增删改查)
既然 URL 里没有动词了,服务器怎么知道你是想查数据,还是想删数据呢? 这就用到了 HTTP 协议自带的 4 个核心请求方法(也是你在 Python 爬虫里最常用的):
GET(查):获取资源。POST(增):新建资源。PUT(改):更新整个资源(有时候也用PATCH局部更新)。DELETE(删):删除资源。
神仙配合来了! 结合前面两点,看看 RESTful 有多优雅:
| 客户端发出的请求 (HTTP 动词 + URL 名词) | 这句话的含义 | 后端数据库操作 |
|---|---|---|
GET /users |
服务员,给我看看全部用户名单 | SELECT * FROM users |
GET /users/1 |
服务员,我要看 ID=1 的那个用户 | SELECT * FROM users WHERE id=1 |
POST /users |
服务员,我要新建一个用户(数据在请求体里) | INSERT INTO users ... |
PUT /users/1 |
服务员,把 ID=1 的用户资料全部替换掉 | UPDATE users SET ... WHERE id=1 |
DELETE /users/1 |
服务员,把 ID=1 的用户给我删了! | DELETE FROM users WHERE id=1 |
你看!同样的网址 /users/1,你换一个请求动词,它执行的就是完全不同的操作。这就是 RESTful 的魅力:极度清晰、优雅。
3. 必须精准使用 HTTP 状态码(我们刚学过的!)
服务器处理完后,不应该随便返回一个 "操作失败" 的文字。它必须依靠我们上一节提到的 HTTP 状态码 来汇报。
如果客户端发送了 POST /users(新建用户):
- 成功了:应该返回状态码
201 Created(专门表示新建成功),而不是只返回 200。 - 如果前端少传了必填参数:应该返回
400 Bad Request。 - 如果前端没带 Token 没权限:应该返回
403 Forbidden。 - 如果想删除
DELETE /users/99,但早就没这个人了:应该返回404 Not Found。
实战篇:在 Python 里怎么体现?
视角 1:如果你是写爬虫的(客户端,用 requests)
遇到 RESTful 的接口,你的感受会极度舒适。如果你想抓取文章,又想给文章点赞(假设点赞是新建一个 like 关系),你只需要:
import requests
# 查文章:用 GET
res = requests.get('https://api.example.com/articles/123')
print("文章内容:", res.json())
# 给这篇文章点赞(新建一个资源):换成 POST!URL不变!
my_data = {'user_id': 999}
res_like = requests.post('https://api.example.com/articles/123/likes', json=my_data)
if res_like.status_code == 201:
print("点赞成功!")
视角 2:如果你是写后端的(服务端,用 FastAPI)
现在最火的 Python 后端框架 FastAPI,天生就是强迫你写出完美的 RESTful 接口的。
from fastapi import FastAPI
app = FastAPI()
# 当有人向 /users 发送 GET 请求时,执行这个函数(查)
@app.get("/users")
def get_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
# 当有人向 /users 发送 POST 请求时,执行这个函数(增)
@app.post("/users", status_code=201) # 严格规范回复 201 状态码!
def create_user():
return {"message": "用户创建成功!"}
总结一下: RESTful API 就是一套“有教养的接口规范”。 它强制要求:利用 URL 讲清楚“目标是谁”,利用 HTTP 动词讲清楚“你要干嘛”,利用 HTTP 状态码讲清楚“结果如何”。
这就是现代前端(Vue/React)和后端(Python/Java/Go)分离开发时,最核心的交流语言!
5.GET和POST的区别
这是最常被问到、也是平时写爬虫和做后端开发最常用的两个 HTTP 方法。
一、 核心区别大白话
| 特性 | GET (获取资源) | POST (提交/新建资源) |
|---|---|---|
| 主要目的 | 向服务器要数据 (查网页内容、读文章) | 向服务器塞数据 (注册账号、发帖、点赞) |
| 参数放哪? | 全暴露在网址 (URL) 里 例子: ?name=root&page=1 |
藏在 HTTP 报文里的“身体(Body)”里 网址里干干净净,外人看不见参数。 |
| 能传多少数据? | 非常少! (受浏览器 URL 长度限制,通常最多 2KB) | 无限大! (几 GB 的超大视频文件也能通过 POST 发) |
| 能不能传文件? | 绝对不行!只能传极短的文本参数。 | 能! 这是它最重要的绝招之一。 |
| 安不安全? | 超级危险! 万万不可用 GET 传密码,别人看一眼你浏览器的网址栏就知道密码了。 | 相对安全。(参数不在网址上,但这不代表绝对安全,必须配合 HTTPS 才能真正防抓包窃听) |
| 收藏与刷新 | 可以放进浏览器收藏夹;随便刷新也不会有提示。 | 不能收藏为书签;如果你按 F5 刷新一个 POST 页面(比如刚付完款),浏览器会弹窗警告:“确认要重新提交表单吗?” |
二、 在 Python 爬虫里,这到底意味着什么?
当你用 Python 写爬虫的时候,这是你必须要跨过的第一道坎。
1. 爬 GET 请求的网页
import requests
# 比如你在百度搜 Python,网址是 https://www.baidu.com/s?wd=python
url = "https://www.baidu.com/s"
params = {
"wd": "python", # 搜索词 kwargs
"pn": 10 # 第2页
}
# requests 的 get 方法,参数用 params= 传进去,它会自动帮你拼接到 URL 后面
response = requests.get(url, params=params)
2. 爬 POST 请求的接口
一旦涉及“登录提交账号密码”,或者“提交大段文字去翻译”,网站绝不会用 GET,绝对是用 POST!因为数据量大,而且要藏在请求体(Body)里。
import requests
url = "https://api.example.com/login"
# 你的账号密码数据,要装进字典里
user_data = {
"username": "admin",
"password": "super_secret_password"
}
# requests 的 post 方法,核心不同点:参数用 data= 或者 json= 传进去!
# 它会把这块数据塞进 HTTP 报文的心脏(Body)里,静悄悄地发过去。
response = requests.post(url, data=user_data)
# (如果对方接口要求 JSON 格式,就把 data= 换成 json=)
三、 经典误区纠正
很多新手会被一些过时的教程误导。咱们说两个真实的底层原理:
-
“POST 比 GET 更安全吗?”
- 错! 如果你不套上咱们之前讲的 HTTPS(SSL锁),它俩一样都是明文裸奔。
- GET 的数据只不过是写在前额头(URL)上,POST 的数据写在肚皮(Body)上。如果不穿衣服 (没有 HTTPS) ,黑客抓包工具一打开,不论额头还是肚皮上的明文,一览无余。
-
“GET 和 POST 在底层的 TCP 连接上有区别吗?”
- 网上有些八股文会说:“GET 请求只发一个 TCP 数据包,POST 会发两个 TCP 数据包(先发头部,再发身体)”。
- 这也是错的/过时的! HTTP 是一层协议,底层的 TCP 怎么打包纯粹看浏览器或客户端的具体实现(比如 requests 库发 POST 就只用一个包发完,根本不发两次)。不要死记硬背这个。本质上,它们都是通过那一条建立好的 TCP 通道在传递数据罢了。
更多推荐
所有评论(0)