【Python】基础语法入门(二十五)—— 网络编程入门:用 socket 与 requests 联网

一、为什么要学网络编程

回想一下你日常用电脑和手机做的事情:

  • 打开天气 App,看今天多少度;
  • 在浏览器里下载一个安装包;
  • 用 Python 调用 ChatGPT 的 API 自动生成文案;
  • 写个爬虫把豆瓣电影 Top250 抓下来存到数据库。

这些操作有一个共同点:数据不在你本地,而在远端的服务器上。程序要拿到这些数据,就必须通过网络和服务器"对话"。网络编程,就是教你的程序如何:

  1. 发起请求:告诉服务器"我要什么";
  2. 接收响应:把服务器返回的数据读进来;
  3. 解析数据:把字节流变成我们能用的字符串、JSON、图片等。

可以说,现代应用几乎没有不联网的 。学会网络编程,你才能写出真正"有用"的程序,而不是只会在本地算算 1+1。再举几个更贴近开发的例子:你写个脚本每天定时从公司内网 API 拉取销售数据生成报表;你做个钉钉机器人,出 bug 时自动发消息到群里;你训练机器学习模型时从对象存储下载几十 G 的训练数据。这些场景背后全是网络请求。理解了网络通信的原理,你才能在请求失败时知道是 DNS 解析问题、是连接被拒、还是超时,而不是对着一个报错两眼一抹黑。

Python 在网络编程方面非常强大:标准库自带 socket、urllib、http 等模块,第三方库 requests、aiohttp 更是把 HTTP 请求简化到一两行代码。本篇我们就从底层到高层,把它们逐一讲清楚。


二、网络通信基础概念

在写代码之前,先搞懂几个最基础的概念,否则后面会一头雾水。

2.1 IP 地址

IP 地址是网络上每台设备的"门牌号"。比如 192.168.1.100 是你家里路由器分配给电脑的局域网 IP,142.250.190.46 是 Google 服务器的公网 IP。程序要和某台机器通信,首先得知道它的 IP。

小知识:127.0.0.1 是本机回环地址,也叫 localhost,永远指向自己。调试时经常用到。

2.2 端口(Port)

一台机器上可能同时跑很多服务:Web 服务、SSH 服务、数据库服务......光有 IP 还不够,还要知道是哪个"门"进去,这就是端口。端口是一个 0~65535 的数字,常见端口:

  • 80:HTTP
  • 443:HTTPS
  • 22:SSH
  • 3306:MySQL
  • 8080:常用开发端口

所以一个网络通信地址通常写成 IP:端口,例如 127.0.0.1:8080。

2.3 TCP 与 UDP

这是两个最底层的传输协议:

协议 特点 适用场景
TCP 面向连接、可靠、有序、慢一点 网页、文件传输、数据库
UDP 无连接、不可靠、可能丢包、快 视频直播、DNS 查询、游戏

通俗讲:TCP 像打电话 ,先拨通建立连接,然后一句句说,对方没听懂还会重说;UDP 像寄明信片,写好地址扔出去就不管了,可能丢、可能乱序,但快。

2.4 HTTP 协议简介

HTTP(超文本传输协议)是建立在 TCP 之上的应用层协议,也是我们日常联网最常打交道的协议。一次 HTTP 通信大致是:

  1. 客户端(你的程序)通过 TCP 连到服务器 IP:端口(默认 80);
  2. 发送一个"请求",包含方法(GET/POST)、路径、头信息、正文;
  3. 服务器返回一个"响应",包含状态码(200/404/500)、头信息、正文;
  4. 连接关闭(或保持)。

常见 HTTP 方法:

  • GET:请求数据(最常用);
  • POST:提交数据(表单、JSON);
  • PUT/DELETE:更新/删除资源。

常见状态码:

  • 200 成功;301/302 跳转;404 找不到;403 没权限;500 服务器内部错误。

理解了这些,下面就能看懂代码了。再补充一个容易混淆的点:我们常说的"客户端/服务端"是相对的角色。你的爬虫对网站来说是客户端,但如果你写了一个本地 Web 服务接收别人的请求,那它就是服务端。一次通信中,主动发起连接的一方是客户端,被动等待并响应的一方是服务端。socket 编程里,服务端要 bind+listen+accept,客户端只要 connect,就是这个道理。


三、socket:最底层的网络通信

socket(套接字)是操作系统提供的网络通信接口,Python 标准库 socket 模块对它做了封装。它非常底层,你要自己处理连接、收发字节流,但也最灵活。

3.1 TCP 服务端与客户端完整示例

先写一个 TCP 服务端,它监听本机 9999 端口,收到客户端发来的消息后回一句"已收到"。

服务端 server.py:

复制代码
import socket

# 1. 创建一个 IPv4(TCP) 的 socket 对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP

# 2. 绑定 IP 和端口('' 表示本机所有网卡)
server.bind(('0.0.0.0', 9999))

# 3. 开始监听,参数 5 表示允许最多 5 个连接在队列里等待
server.listen(5)
print("服务端启动,等待连接...")

while True:
    # 4. 接受一个连接,返回 (连接对象, 客户端地址)
    conn, addr = server.accept()
    print(f"客户端 {addr} 连上来了")

    while True:
        # 5. 接收数据,每次最多读 1024 字节
        data = conn.recv(1024)
        if not data:          # 收到空数据说明客户端断开了
            print(f"客户端 {addr} 断开")
            break
        print(f"收到: {data.decode('utf-8')}")

        # 6. 回复客户端
        conn.sendall("已收到你的消息\n".encode('utf-8'))

    conn.close()              # 关闭这个连接

客户端 client.py:

复制代码
import socket

# 1. 创建 socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 连接服务端
client.connect(('127.0.0.1', 9999))

# 3. 发送数据(必须是字节,所以要 encode)
client.sendall("你好,我是客户端".encode('utf-8'))

# 4. 接收回复
response = client.recv(1024)
print(f"服务端回复: {response.decode('utf-8')}")

# 5. 关闭
client.close()

运行说明:

  1. 先在一个终端运行 python server.py,看到"服务端启动,等待连接...";
  2. 再开一个终端运行 python client.py;
  3. 客户端输出:服务端回复: 已收到你的消息;
  4. 服务端输出:客户端 ('127.0.0.1', xxxxx) 连上来了 和 收到: 你好,我是客户端。

注意:send/recv 收发的都是字节(bytes) ,所以字符串要用 .encode() 编码成字节再发,收到后用 .decode() 解码成字符串。这是新手最容易忘的一点。

3.2 UDP 示例

UDP 不需要建立连接,直接发就行。

UDP 服务端:

复制代码
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # SOCK_DGRAM 表示 UDP
server.bind(('0.0.0.0', 9998))

print("UDP 服务端启动...")
while True:
    data, addr = server.recvfrom(1024)   # 接收数据,同时拿到对方地址
    print(f"来自 {addr}: {data.decode('utf-8')}")
    server.sendto("UDP回复".encode('utf-8'), addr)

UDP 客户端:

复制代码
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto("Hello UDP".encode('utf-8'), ('127.0.0.1', 9998))

data, _ = client.recvfrom(1024)
print(f"收到: {data.decode('utf-8')}")

UDP 没有连接、没有重传,发出去就不管了,所以代码更短,但不可靠。

3.3 socket 常用方法表

方法 说明
socket() 创建套接字
bind(addr) 绑定地址(服务端用)
listen(n) 开始监听
accept() 接受连接,返回 (conn, addr)
connect(addr) 连接服务器(客户端用)
send(data) / sendall(data) 发送数据
recv(bufsize) 接收数据
sendto(data, addr) UDP 发送
recvfrom(bufsize) UDP 接收
close() 关闭
settimeout(t) 设置超时时间

3.4 注意事项

  • 收发都是字节 ,记得 encode/decode;
  • recv(1024) 一次最多读 1024 字节,大数据要循环读;
  • 服务端要 bind 端口,客户端一般不用(系统自动分配);
  • 端口被占用会报 OSError: [Errno 48] Address already in use,换个端口或加 socket.SO_REUSEADDR;
  • 长时间不收发,连接可能被防火墙断掉,记得加 settimeout。

socket 太底层,日常开发很少直接用。下面看看标准库提供的更高层工具。


四、urllib:标准库的 HTTP 工具

Python 标准库自带 urllib 模块,不用安装就能发 HTTP 请求,适合简单场景或没有第三方库的环境。

复制代码
from urllib import request, parse

# GET 请求
url = "http://httpbin.org/get"
with request.urlopen(url) as resp:
    print(resp.status)        # 200
    print(resp.read().decode('utf-8'))   # 响应正文

# POST 请求(带表单数据)
data = parse.urlencode({'name': '陈序员', 'age': 18}).encode('utf-8')
with request.urlopen("http://httpbin.org/post", data=data) as resp:
    print(resp.read().decode('utf-8'))

urllib 能用,但 API 比较啰嗦,要手动 urlencode、手动 decode、加 header 要构造 Request 对象......所以实际开发中,大家几乎都用第三方库 requests。urllib 这里只作为过渡了解。


五、requests:日常联网首选

requests 是 Python 世界最流行的 HTTP 库,号称"为人类设计的 HTTP 库"。它的 API 简洁优雅,几行代码就能完成复杂请求。

5.1 安装

复制代码
pip install requests

5.2 GET 请求

复制代码
import requests

# 最简单的 GET
resp = requests.get("https://httpbin.org/get")
print(resp.status_code)   # 200
print(resp.text)           # 响应文本

# 带 query 参数(自动拼到 URL 后面)
params = {'name': 'chen', 'age': 18}
resp = requests.get("https://httpbin.org/get", params=params)
print(resp.url)            # https://httpbin.org/get?name=chen&age=18

# 带请求头
headers = {'User-Agent': 'MyPythonBot/1.0'}
resp = requests.get("https://httpbin.org/get", headers=headers)

# 设置超时(秒),避免一直卡住
resp = requests.get("https://httpbin.org/get", timeout=5)

运行结果:resp.text 会返回一段 JSON 字符串,里面能看到 args 字段里带着你传的参数。

5.3 POST 请求

复制代码
import requests

# 1. 提交表单数据(application/x-www-form-urlencoded)
resp = requests.post("https://httpbin.org/post", data={'username': 'admin', 'pwd': '123'})
print(resp.text)

# 2. 提交 JSON(application/json),用 json= 参数
resp = requests.post("https://httpbin.org/post", json={'title': 'hello', 'body': 'world'})
print(resp.text)

# 3. 上传文件
files = {'uploadfile': open('test.txt', 'rb')}
resp = requests.post("https://httpbin.org/post", files=files)
print(resp.status_code)

区分 data= 和 json=:data 发的是表单格式,json 发的是 JSON 格式。调 API 时大多用 json=。

5.4 会话 Session

如果要在多次请求之间保持 Cookie、复用 TCP 连接(更快),就用 Session:

复制代码
import requests

s = requests.Session()
s.headers.update({'User-Agent': 'MyBot/1.0'})   # 给所有请求统一加 header

# 第一次请求登录,Cookie 会自动保存
s.post("https://httpbin.org/cookies/set", data={'token': 'abc123'})

# 后续请求自动带上 Cookie
resp = s.get("https://httpbin.org/cookies")
print(resp.text)   # 能看到 cookies 里有 token=abc123

s.close()          # 用完关闭

5.5 响应对象详解

requests.get() 返回一个 Response 对象,常用属性和方法:

属性/方法 说明
status_code 状态码,如 200、404
text 响应正文(字符串,自动猜测编码)
content 响应正文(字节,适合下载二进制)
json() 把正文当 JSON 解析成字典
headers 响应头(字典)
cookies 响应 Cookie
url 最终请求的 URL(可能经过跳转)
encoding 字符编码
raise_for_status() 状态码不是 200 就抛异常

示例:

复制代码
import requests

resp = requests.get("https://httpbin.org/json")
print(resp.status_code)        # 200
print(resp.headers['Content-Type'])   # application/json
data = resp.json()             # 直接拿到字典
print(data['slideshow']['title'])

5.6 下载文件(流式下载大文件)

下载小文件直接 resp.content 写到文件即可,但下载几个 G 的电影就不能一次性读进内存,要用流式下载:

复制代码
import requests

url = "https://example.com/bigfile.zip"
resp = requests.get(url, stream=True)   # stream=True 不立即下载正文

with open("bigfile.zip", "wb") as f:
    for chunk in resp.iter_content(chunk_size=8192):   # 每次读 8KB
        if chunk:
            f.write(chunk)

print("下载完成")

还可以加进度条(配合 tqdm):

复制代码
from tqdm import tqdm
total = int(resp.headers.get('content-length', 0))
with open("bigfile.zip", "wb") as f, tqdm(total=total, unit='B', unit_scale=True) as bar:
    for chunk in resp.iter_content(8192):
        f.write(chunk)
        bar.update(len(chunk))

5.7 代理设置

爬虫经常要挂代理:

复制代码
import requests

proxies = {
    'http': 'http://127.0.0.1:7890',
    'https': 'http://127.0.0.1:7890',
}
resp = requests.get("https://www.google.com", proxies=proxies, timeout=10)
print(resp.status_code)

六、异常处理

网络不稳定,请求随时可能失败。requests 提供了清晰的异常体系:

复制代码
import requests
from requests.exceptions import Timeout, ConnectionError, RequestException

try:
    resp = requests.get("https://httpbin.org/get", timeout=3)
    resp.raise_for_status()    # 4xx/5xx 会抛 HTTPError
except Timeout:
    print("请求超时,请检查网络")
except ConnectionError:
    print("连接失败,可能是网址不对或断网")
except RequestException as e:
    print(f"其他请求异常: {e}")
else:
    print("成功:", resp.text[:100])

常用异常:

  • Timeout:超时;
  • ConnectionError:连接失败(DNS 解析失败、拒绝连接等);
  • HTTPError:状态码 4xx/5xx(需 raise_for_status() 触发);
  • TooManyRedirects:重定向过多;
  • RequestException:所有 requests 异常的基类。

实际开发中,建议总是加 try-except,否则一个网络抖动就让程序崩溃。


七、实战案例:简单的天气查询工具

我们用免费的 wttr.in 服务(无需注册、无需 key)写一个命令行天气查询工具:

复制代码
import requests

def get_weather(city: str) -> str:
    """查询指定城市的天气,返回格式化字符串"""
    url = f"https://wttr.in/{city}"
    params = {'format': 'j1'}   # 返回 JSON
    try:
        resp = requests.get(url, params=params, timeout=10)
        resp.raise_for_status()
        data = resp.json()
    except requests.RequestException as e:
        return f"查询失败: {e}"

    current = data['current_condition'][0]
    temp = current['temp_C']
    desc = current['weatherDesc'][0]['value']
    humidity = current['humidity']
    wind = current['windspeedKmph']
    return (f"【{city} 天气】\n"
            f"温度: {temp}°C\n"
            f"天气: {desc}\n"
            f"湿度: {humidity}%\n"
            f"风速: {wind} km/h")

if __name__ == '__main__':
    while True:
        city = input("请输入城市名(英文,如 Beijing/Shanghai,输入 q 退出): ").strip()
        if city.lower() == 'q':
            break
        print(get_weather(city))
        print("-" * 30)

运行示例:

复制代码
请输入城市名(英文,如 Beijing/Shanghai,输入 q 退出): Beijing
【Beijing 天气】
温度: 18°C
天气: Partly cloudy
湿度: 45%
风速: 12 km/h
------------------------------

这个例子综合用到了 GET 请求、params、timeout、异常处理、JSON 解析,是一个完整的小工具。你可以把它扩展成定时查询、保存历史、发邮件提醒等。比如结合 schedule 库每天早上 8 点查询一次,把结果写入 SQLite 历史表,温度低于 5 度就调用邮件接口提醒你加衣服------这就从一个脚本变成了一个真正有用的小服务。再进一步,用 FastAPI 把它包成 Web 接口,前端做个页面展示,你就拥有了自己的天气应用。所有这些扩展,底层都离不开本篇学的 requests 这几行代码。


八、最佳实践

  1. 永远设置 timeout:不设超时的请求可能永远卡住,拖垮整个程序。
  2. 用 Session 复用连接:多次请求同一站点时,Session 能复用 TCP 连接,速度快很多。
  3. 总是处理异常:网络不可靠,try-except 是标配。
  4. 大文件用 stream 流式下载:避免内存爆炸。
  5. 加 User-Agent:很多网站会拒绝默认 UA,伪装成浏览器更稳。
  6. 遵守 robots.txt 和频率限制 :爬虫别太猛,加 time.sleep,做个有礼貌的爬虫。
  7. JSON 用 resp.json(),别手动 json.loads(resp.text):前者更安全,编码处理更好。
  8. 敏感信息(token、密码)放 header 或环境变量,别写在 URL 里(URL 会被日志记录)。
  9. HTTPS 优先:HTTP 明文传输,不安全。
  10. 调试时打开日志 :import logging; logging.basicConfig(level=logging.DEBUG),能看到请求详情。

九、小结

本篇我们从最底层的 socket 一路讲到日常最常用的 requests:

  • socket 是操作系统网络接口的封装,能做 TCP/UDP 通信,但要自己处理字节、连接、收发循环;
  • urllib 是标准库的 HTTP 工具,能用但啰嗦;
  • requests 是日常首选,GET/POST/Session/文件上传/流式下载/代理一应俱全;
  • 网络请求一定要处理异常、设超时;
  • 实战写了一个天气查询小工具,把所学串了起来。

下一篇我们将进入并发编程的世界------当你要同时下载 100 个网页时,串行一个个下太慢了,得让程序"同时干多件事",敬请期待。


十、练习题 / 思考题

  1. 用 socket 写一个简单的"聊天室"服务端,支持多个客户端同时连接并互相转发消息(提示:结合下一篇的多线程)。
  2. 用 requests 调用 https://api.github.com/users/你的用户名,打印你的 GitHub 公开信息。
  3. 写一个函数 download(url, path),支持断点续传(提示:用 Range 请求头)。
  4. 用 Session 模拟登录某个需要 Cookie 的网站(注意遵守对方条款)。
  5. 思考:为什么 requests 比 urllib 用起来舒服这么多?它帮你封装了哪些东西?
  6. 挑战:用 socket 手写一个最简单的 HTTP 客户端,向 httpbin.org 发 GET 请求并解析响应(体会一下 requests 帮你省了多少事)。
相关推荐
adminwolf1 小时前
GoLiveChat:Open-Source Live Chat Software for Website
python
工具派2 小时前
PDF签名贴上去为什么发虚?尺寸、DPI与坐标的边界实测
java·python·pdf
谙忆2 小时前
PNG改成ICO为什么不能用:用Python检查多尺寸目录与256px帧
开发语言·python
长空任鸟飞_阿康2 小时前
如何设计支持 Skills 的 Agent?一套可扩展的四层架构
python·ai·架构·skills
滴啦嘟啦哒2 小时前
【具身笔记】【原理探究】一、对PI05源码的实践笔记
笔记·python·vla·具身
miofly2 小时前
GitHub 今日推荐|elephant-goldfish-model:先审查设计草案再写代码
python·开源·github
云和数据.ChenGuang2 小时前
深入剖析 Python 的 MRO 规则:从 C3 线性化到实战踩坑
开发语言·python·1024程序员节
123_不打狼2 小时前
Squidiff:基于条件扩散模型预测细胞发育与扰动响应
开发语言·python·squidiff
databook2 小时前
用Python DSL 写轻量数据质检工具
python·数据分析