前言:无处不在的HTTP
在当今的互联网世界中,HTTP(HyperText Transfer Protocol) 协议是几乎所有Web通信的基础。无论是浏览网页、使用手机App,还是微服务之间的调用,最终大多都转化为HTTP请求。
- Web爬虫:需要模拟浏览器向网站服务器发送请求,获取网页内容。
- API集成:需要调用第三方服务(如天气API、支付API、社交媒体API)提供的HTTP接口。
- 自动化测试:需要测试Web应用的接口是否正确工作。
- 数据采集与分析:需要从各种数据源获取数据。
Python提供了多种方式进行HTTP请求,最主要的是标准库中的urllib和第三方库requests。本章将深入探讨这两者,帮助你根据场景做出最佳选择。
一、 HTTP协议快速回顾
在深入代码之前,有必要快速回顾一下HTTP的基本概念:
- 请求-响应模型:客户端发起请求,服务器返回响应。
- URL(统一资源定位符):标识互联网上资源的地址,如
https://api.github.com/users/octocat - HTTP方法:
GET:获取资源POST:创建资源PUT:更新资源DELETE:删除资源HEAD、PATCH、OPTIONS等
- 状态码:
2xx:成功(如200 OK)3xx:重定向4xx:客户端错误(如404 Not Found)5xx:服务器错误
- 请求头/响应头:包含元数据,如内容类型、认证信息等。
- 请求体/响应体:实际传输的数据内容。
二、 标准库:urllib
Python标准库中的urllib包提供了用于HTTP请求的基本功能。它包含多个模块:
urllib.request:打开和读取URLurllib.error:包含urllib.request引发的异常urllib.parse:用于解析URLurllib.robotparser:用于解析robots.txt文件
1. 基本GET请求
from urllib import request
import urllib.error
url = 'https://httpbin.org/get'
try:
# 发送GET请求
with request.urlopen(url) as response:
# 获取状态码和响应头
print(f"状态码: {response.status}")
print(f"响应头: {dict(response.getheaders())}")
# 读取响应内容
data = response.read()
print(f"响应体: {data.decode('utf-8')}")
except urllib.error.URLError as e:
print(f"请求失败: {e.reason}")
2. 添加请求参数
对于GET请求,参数通常附加在URL的查询字符串中:
from urllib import request, parse
base_url = 'https://httpbin.org/get'
params = {
'name': 'John Doe',
'age': 30,
'city': 'New York'
}
# 将参数字典编码为查询字符串
query_string = parse.urlencode(params)
full_url = f"{base_url}?{query_string}"
print(f"完整URL: {full_url}")
with request.urlopen(full_url) as response:
print(response.read().decode('utf-8'))
3. 添加自定义请求头
有些网站会验证请求头,防止简单的爬虫访问:
from urllib import request
url = 'https://httpbin.org/headers'
# 创建请求对象并添加头部
req = request.Request(url)
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
req.add_header('Accept', 'application/json')
req.add_header('Custom-Header', 'MyValue')
with request.urlopen(req) as response:
print(response.read().decode('utf-8'))
4. 发送POST请求
POST请求通常用于提交表单数据或JSON数据:
from urllib import request, parse
import json
url = 'https://httpbin.org/post'
# 方式1: 表单数据
form_data = {
'username': 'testuser',
'password': 'testpass'
}
encoded_data = parse.urlencode(form_data).encode('utf-8')
# 方式2: JSON数据
json_data = json.dumps({
'title': 'Test Post',
'body': 'This is a test post content',
'userId': 1
}).encode('utf-8')
# 创建请求对象
req = request.Request(url, data=encoded_data, method='POST')
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
# 对于JSON数据
# req = request.Request(url, data=json_data, method='POST')
# req.add_header('Content-Type', 'application/json')
with request.urlopen(req) as response:
result = response.read().decode('utf-8')
print(result)
5. 处理异常
urllib提供了丰富的异常类来处理各种错误情况:
from urllib import request, error
test_urls = [
'https://httpbin.org/status/404',
'https://httpbin.org/status/500',
'https://invalid-domain-abcdefg.org',
'https://httpbin.org/delay/5' # 可能会超时
]
for url in test_urls:
try:
# 设置超时时间为3秒
with request.urlopen(url, timeout=3) as response:
print(f"{url} - 成功: {response.status}")
except error.HTTPError as e:
print(f"{url} - HTTP错误: {e.code} {e.reason}")
except error.URLError as e:
print(f"{url} - URL错误: {e.reason}")
except Exception as e:
print(f"{url} - 其他错误: {e}")
6. 使用代理
在某些网络环境下,可能需要使用代理服务器:
from urllib import request
url = 'https://httpbin.org/ip'
# 设置代理
proxy_handler = request.ProxyHandler({
'http': 'http://proxy.example.com:8080',
'https': 'https://proxy.example.com:8080',
})
# 创建自定义opener
opener = request.build_opener(proxy_handler)
# 安装为默认opener
request.install_opener(opener)
try:
with request.urlopen(url) as response:
print(response.read().decode('utf-8'))
except Exception as e:
print(f"通过代理请求失败: {e}")
7. 处理Cookie
对于需要保持会话的网站,需要处理Cookie:
from urllib import request
from http import cookiejar
# 创建CookieJar来存储cookies
cookie_jar = cookiejar.CookieJar()
# 创建处理器
cookie_handler = request.HTTPCookieProcessor(cookie_jar)
# 创建opener
opener = request.build_opener(cookie_handler)
# 模拟登录
login_url = 'https://httpbin.org/cookies/set/sessioncookie/123456789'
with opener.open(login_url) as response:
print("设置cookie响应:", response.read().decode('utf-8'))
# 再次访问,cookie会自动带上
test_url = 'https://httpbin.org/cookies'
with opener.open(test_url) as response:
print("带cookie的响应:", response.read().decode('utf-8'))
虽然urllib功能强大,但它的API相对底层和繁琐。这也是为什么requests库如此受欢迎的原因。
三、 第三方库:requests
requests是一个优雅而简单的HTTP库,为人类设计。它封装了urllib的复杂细节,提供了更友好直观的API。
首先安装它:pip install requests
1. 基本GET请求
import requests
url = 'https://httpbin.org/get'
# 发送GET请求
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
print("请求成功!")
print(f"状态码: {response.status_code}")
print(f"响应头: {dict(response.headers)}")
print(f"响应内容: {response.text}") # 文本内容
print(f"JSON响应: {response.json()}") # 自动解析JSON
else:
print(f"请求失败,状态码: {response.status_code}")
2. 传递URL参数
import requests
url = 'https://httpbin.org/get'
params = {
'key1': 'value1',
'key2': 'value2',
'key3': ['value3', 'value4'] # 多个值
}
response = requests.get(url, params=params)
print(f"最终URL: {response.url}")
print(response.json())
3. 自定义请求头
import requests
url = 'https://httpbin.org/headers'
headers = {
'User-Agent': 'MyPythonScript/1.0',
'Accept': 'application/json',
'Custom-Header': 'custom-value'
}
response = requests.get(url, headers=headers)
print(response.json())
4. 发送POST请求
import requests
import json
url = 'https://httpbin.org/post'
# 方式1: 表单数据
form_data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post(url, data=form_data)
print("表单POST响应:")
print(response.json())
# 方式2: JSON数据
json_data = {'name': 'John', 'age': 30}
response = requests.post(url, json=json_data)
print("\nJSON POST响应:")
print(response.json())
# 方式3: 原始数据
raw_data = '简单文本数据'
response = requests.post(url, data=raw_data)
print("\n原始数据POST响应:")
print(response.json())
5. 处理超时和异常
import requests
from requests.exceptions import RequestException
urls = [
'https://httpbin.org/status/404',
'https://httpbin.org/status/500',
'https://httpbin.org/delay/10', # 10秒延迟
'https://invalid-domain-xyz.com'
]
for url in urls:
try:
response = requests.get(url, timeout=5) # 5秒超时
response.raise_for_status() # 如果状态码不是200,抛出HTTPError
print(f"{url} - 成功")
except requests.exceptions.Timeout:
print(f"{url} - 请求超时")
except requests.exceptions.HTTPError as e:
print(f"{url} - HTTP错误: {e}")
except requests.exceptions.ConnectionError:
print(f"{url} - 连接错误")
except RequestException as e:
print(f"{url} - 其他请求错误: {e}")
6. 使用会话保持状态
Session对象可以自动处理cookies,保持会话状态:
import requests
# 创建会话
session = requests.Session()
# 第一次请求,设置cookies
url1 = 'https://httpbin.org/cookies/set/sessionid/12345'
response1 = session.get(url1)
print("第一次请求响应:", response1.json())
# 第二次请求,自动携带cookies
url2 = 'https://httpbin.org/cookies'
response2 = session.get(url2)
print("第二次请求响应:", response2.json())
# 会话结束后关闭
session.close()
7. 高级特性:认证、代理、SSL验证
import requests
from requests.auth import HTTPBasicAuth
url = 'https://httpbin.org/basic-auth/user/passwd'
# 基本认证
response = requests.get(url, auth=HTTPBasicAuth('user', 'passwd'))
print("基本认证响应:", response.json())
# 使用代理
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
# response = requests.get('https://httpbin.org/ip', proxies=proxies)
# 禁用SSL验证(不推荐在生产环境使用)
response = requests.get('https://httpbin.org/get', verify=False)
print("禁用SSL验证请求:", response.status_code)
# 设置自定义CA证书
# response = requests.get('https://httpbin.org/get', verify='/path/to/certfile')
四、 urllib vs requests:如何选择?
| 特性 | urllib (标准库) | requests (第三方库) |
|---|---|---|
| 安装 | Python内置,无需安装 | 需要pip安装 |
| API友好度 | 底层,繁琐 | 高级,直观易用 |
| 功能完整性 | 基础功能完备 | 功能丰富,封装完善 |
| 文档和社区 | 官方文档,相对简单 | 文档极佳,社区活跃 |
| 性能 | 稍好(更接近底层) | 稍差(封装开销) |
| 适用场景 | 简单请求,不想引入依赖 | 绝大多数HTTP请求场景 |
建议:
- 对于大多数应用,优先使用requests,它的开发效率更高。
- 只有在不能安装第三方库,或者需要极致的性能控制时,才考虑使用urllib。
五、 综合实战:GitHub API客户端
让我们构建一个实用的GitHub API客户端,展示如何在实际项目中使用requests库。
import requests
import json
from datetime import datetime, timedelta
class GitHubAPIClient:
def __init__(self, token=None):
self.base_url = 'https://api.github.com'
self.headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'Python-GitHub-API-Client'
}
if token:
self.headers['Authorization'] = f'token {token}'
self.session = requests.Session()
self.session.headers.update(self.headers)
def get_user_info(self, username):
"""获取用户信息"""
url = f'{self.base_url}/users/{username}'
response = self.session.get(url)
response.raise_for_status()
return response.json()
def get_user_repos(self, username, sort='updated', direction='desc'):
"""获取用户仓库列表"""
url = f'{self.base_url}/users/{username}/repos'
params = {
'sort': sort,
'direction': direction,
'per_page': 10 # 限制返回数量
}
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def search_repositories(self, query, language=None, stars='>100', sort='stars'):
"""搜索仓库"""
url = f'{self.base_url}/search/repositories'
search_query = query
if language:
search_query += f' language:{language}'
if stars:
search_query += f' stars:{stars}'
params = {
'q': search_query,
'sort': sort,
'order': 'desc'
}
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_trending_repos(self, since='weekly'):
"""获取趋势仓库(通过搜索模拟)"""
# 计算日期范围
if since == 'daily':
date_since = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
elif since == 'weekly':
date_since = (datetime.now() - timedelta(weeks=1)).strftime('%Y-%m-%d')
else: # monthly
date_since = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
query = f'created:>{date_since}'
return self.search_repositories(query, sort='stars')
def create_gist(self, description, files, public=True):
"""创建Gist(需要认证)"""
if 'Authorization' not in self.headers:
raise Exception("需要GitHub token来创建Gist")
url = f'{self.base_url}/gists'
data = {
'description': description,
'public': public,
'files': files
}
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def close(self):
"""关闭会话"""
self.session.close()
def main():
# 创建客户端实例
# 如果需要创建Gist,需要提供GitHub personal access token
# github = GitHubAPIClient('your_github_token_here')
github = GitHubAPIClient()
try:
# 1. 获取用户信息
print("=== 获取用户信息 ===")
user_info = github.get_user_info('octocat')
print(f"用户名: {user_info['login']}")
print(f"姓名: {user_info.get('name', '未知')}")
print(f"粉丝数: {user_info['followers']}")
print(f"仓库数: {user_info['public_repos']}\n")
# 2. 获取用户仓库
print("=== 获取用户最新仓库 ===")
repos = github.get_user_repos('octocat')
for repo in repos[:3]: # 只显示前3个
print(f"{repo['name']}: {repo['description']}")
print(f" 星标数: {repo['stargazers_count']}, 语言: {repo.get('language', '未知')}\n")
# 3. 搜索Python仓库
print("=== 搜索热门Python仓库 ===")
search_results = github.search_repositories('web framework', 'python', '>1000')
for repo in search_results['items'][:5]:
print(f"{repo['name']} - {repo['description']}")
print(f" 星标: {repo['stargazers_count']}, forks: {repo['forks_count']}")
print(f" URL: {repo['html_url']}\n")
# 4. 获取趋势仓库
print("=== 本周趋势仓库 ===")
trending = github.get_trending_repos('weekly')
for repo in trending['items'][:5]:
print(f"{repo['name']} - {repo['description']}")
print(f" 星标: {repo['stargazers_count']}, 创建于: {repo['created_at']}\n")
except requests.exceptions.HTTPError as e:
print(f"HTTP错误: {e}")
except requests.exceptions.RequestException as e:
print(f"请求错误: {e}")
finally:
github.close()
if __name__ == "__main__":
main()
这个实战项目展示了:
- API调用:多种类型的GitHub API请求
- 参数处理:查询参数、认证头部的使用
- 错误处理:完善的异常处理机制
- 会话管理:使用Session保持连接和头部信息
- 数据处理:解析和利用JSON响应
六、 高级话题与最佳实践
1. 重试机制
对于生产环境,应该实现重试机制来处理临时性网络问题:
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_session_with_retries(retries=3, backoff_factor=0.3):
"""创建带重试机制的会话"""
session = requests.Session()
retry_strategy = Retry(
total=retries,
backoff_factor=backoff_factor, # 重试之间的等待时间:{backoff_factor} * (2 ** (重试次数 - 1))
status_forcelist=[429, 500, 502, 503, 504], # 遇到这些状态码时重试
allowed_methods=["GET", "POST"] # 只对这些方法重试
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
# 使用示例
session = create_session_with_retries()
try:
response = session.get('https://httpbin.org/status/500', timeout=5)
print(response.status_code)
except requests.exceptions.RetryError:
print("重试多次后仍然失败")
2. 速率限制处理
许多API都有速率限制,需要合理控制请求频率:
import requests
import time
from collections import deque
class RateLimitedSession(requests.Session):
"""带速率限制的会话"""
def __init__(self, requests_per_second=1):
super().__init__()
self.requests_per_second = requests_per_second
self.request_times = deque()
def request(self, *args, **kwargs):
# 确保不超过速率限制
now = time.time()
# 移除1秒前的请求记录
while self.request_times and now - self.request_times[0] >= 1:
self.request_times.popleft()
# 如果达到限制,等待
if len(self.request_times) >= self.requests_per_second:
sleep_time = 1 - (now - self.request_times[0])
if sleep_time > 0:
time.sleep(sleep_time)
now = time.time() # 更新当前时间
# 记录本次请求时间
self.request_times.append(now)
return super().request(*args, **kwargs)
# 使用示例
session = RateLimitedSession(requests_per_second=2) # 每秒最多2个请求
for i in range(5):
response = session.get('https://httpbin.org/get')
print(f"请求 {i+1}: {response.status_code}")
3. 流式处理大响应
对于大文件或流式响应,应该使用流式处理:
import requests
url = 'https://httpbin.org/stream/20' # 流式API
# 使用流式处理
response = requests.get(url, stream=True)
try:
for line in response.iter_lines():
if line: # 过滤keep-alive空行
print(f"收到数据: {line.decode('utf-8')}")
finally:
response.close()
4. 超时设置的最佳实践
总是设置合理的超时时间,避免请求永远挂起:
import requests
# 连接超时和读取超时分开设置
timeouts = (3.05, 27) # (连接超时, 读取超时)
try:
response = requests.get('https://httpbin.org/delay/5', timeout=timeouts)
print("请求成功")
except requests.exceptions.ConnectTimeout:
print("连接超时")
except requests.exceptions.ReadTimeout:
print("读取超时")
七、 总结
通过本章的学习,你应该已经掌握了:
- HTTP协议基础:理解了HTTP请求-响应模型、方法、状态码等核心概念。
- urllib库:学会了使用Python标准库进行基本的HTTP请求。
- requests库:掌握了更现代、更友好的HTTP客户端库的使用。
- 实战应用:通过GitHub API客户端项目,了解了如何在实际项目中进行API集成。
- 高级特性:了解了重试机制、速率限制、流式处理等生产环境需要的功能。
选择建议:
- 对于大多数项目,直接使用
requests库,它的开发体验更好。 - 只有在不能有外部依赖,或者需要极细粒度控制时,才考虑使用
urllib。
HTTP请求是现代编程的基础技能,无论是Web开发、数据分析还是自动化运维,都离不开与HTTP API的交互。掌握好这个技能,将为你的Python编程之路打开无数可能性。
1万+

被折叠的 条评论
为什么被折叠?



