【Python】Requests:请求发送
requests
是一个简洁易用的 Python 库,用于发送 HTTP 请求。它支持多种 HTTP 方法,并且在处理响应、会话保持、超时和重试等方面提供了强大的功能。本文将带你逐步了解如何使用requests
库,并通过实例掌握其基本用法。
发送 HTTP 请求
常见的 HTTP 请求方法
requests
库支持六种主要的 HTTP 请求方法,以下是每种请求方法的基本用法示例:
import requests
# GET 请求: 从服务器获取数据
response = requests.get('https://example.com')
# POST 请求: 向服务器提交数据
response = requests.post('https://example.com', data={'key': 'value'})
# PUT 请求: 更新服务器上的资源
response = requests.put('https://example.com/resource/1', data={'key': 'new_value'})
# DELETE 请求: 删除服务器上的资源
response = requests.delete('https://example.com/resource/1')
# HEAD 请求: 获取响应的头部信息,不返回响应体
response = requests.head('https://example.com')
# OPTIONS 请求: 获取目标资源的通信选项
response = requests.options('https://example.com')
在每个请求方法中,唯一的必选参数是 url
,即请求的目标地址。你还可以通过 params
、data
、json
等参数向服务器发送数据:
params
: 将参数添加到 URL 查询字符串中,适用于 GET 请求。data
: 用于发送表单数据,适用于 POST、PUT 请求。json
: 用于发送 JSON 格式的数据。
通用的 HTTP 请求方法
requests.request
是一个通用接口,用于发送任意类型的 HTTP 请求。你可以通过它灵活地控制请求的各个方面。
requests.request(method, url, **kwargs)
method
: 请求方法,必须是字符串,如'GET'
、'POST'
等。url
: 请求的目标 URL。params
: 用于添加到 URL 的查询参数(字典或字节流)。data
: 要发送的数据(字典、字节流或文件对象)。json
: JSON 格式的数据,会自动序列化为 JSON。headers
: 自定义请求头(字典形式)。cookies
: 要发送的 cookies(字典形式)。files
: 文件上传(字典形式),例如files={'file': open('test.txt', 'rb')}
。auth
: 处理认证(元组形式),例如auth=('user', 'pass')
。timeout
: 设置超时时间(秒)。allow_redirects
: 是否允许重定向,默认True
。stream
: 是否立即下载响应内容,默认False
。
示例:
import requests
response = requests.request(
method='GET',
url='https://api.github.com/repos/psf/requests',
headers={'Accept': 'application/vnd.github.v3+json'},
params={'state': 'open'}
)
print(response.json())
查看实际请求的 URL
在 requests
库中,response.request.url
是 requests
响应对象的一个属性,返回了实际请求的 URL。这在调试和日志记录中非常有用,尤其是在处理重定向或多次请求时。
以下是一个示例,展示如何使用 response.request.url
来获取请求的 URL:
import requests
response = requests.get('https://example.com')
print(f"请求的 URL: {response.request.url}")
redirect_response = requests.get('https://httpbin.org/redirect/1')
# 打印实际请求的 URL
print(f"原始请求的 URL: {redirect_response.request.url}")
print(f"响应的最终 URL: {redirect_response.url}")
-
response.request.url
:- 作用: 提供发出请求时使用的 URL。
- 用途: 有助于调试和验证实际请求的 URL,尤其在处理重定向和日志记录时非常有用。
-
response.url
:- 作用: 提供响应的最终 URL。这在处理重定向请求时尤为重要,因为它显示了最终被访问的 URL。
处理服务器响应
每次发送请求后,都会收到一个 Response
对象。这个对象包含了服务器返回的各种信息:
import requests
response = requests.get('https://api.github.com')
# 获取响应状态码
print(response.status_code)
# 获取响应内容(字符串形式)
print(response.text)
# 获取响应内容(JSON 形式)
print(response.json())
# 获取响应头
print(response.headers)
# 获取响应体的二进制内容
print(response.content)
响应内容解析
- 状态码:
response.status_code
表示请求的结果,如 200 成功,404 未找到资源。 - 响应体:
response.text
返回响应的文本内容,response.json()
将 JSON 内容解析为字典。 - 响应头:
response.headers
包含服务器返回的头部信息。
请求错误处理
在使用 requests
库处理 HTTP 请求时,进行返回异常检查是确保程序稳健性的关键步骤。requests
库提供了一些内置的异常处理机制,你可以通过捕获这些异常来处理各种错误场景,例如网络问题、无效的 HTTP 响应等。
requests.exceptions.RequestException
: 所有异常的基类。你可以捕获这个异常来处理所有与请求相关的错误。requests.exceptions.HTTPError
: 捕获 HTTP 错误,通常与响应的状态码相关(4xx 或 5xx)。requests.exceptions.ConnectionError
: 捕获网络连接相关的错误,例如 DNS 失败、拒绝连接等。requests.exceptions.Timeout
: 捕获请求超时错误,当请求超过设定的超时时间还没有响应时抛出。requests.exceptions.TooManyRedirects
: 捕获重定向次数过多的错误。
以下是一个基本的示例,演示如何在请求过程中捕获和处理异常:
import requests
url = 'https://example.com'
try:
response = requests.get(url, timeout=5)
# 如果服务器返回的状态码不是 200 (OK),手动引发 HTTPError 异常
response.raise_for_status()
# 处理响应内容
print(response.text)
except requests.exceptions.HTTPError as http_err:
print(f'HTTP 错误发生: {http_err}')
except requests.exceptions.ConnectionError as conn_err:
print(f'连接错误发生: {conn_err}')
except requests.exceptions.Timeout as timeout_err:
print(f'请求超时: {timeout_err}')
except requests.exceptions.RequestException as req_err:
print(f'发生错误: {req_err}')
-
response.raise_for_status()
: 这个方法在requests
库中非常有用。如果响应的状态码是 4xx 或 5xx,raise_for_status()
会引发requests.exceptions.HTTPError
异常。因此,你可以通过这个方法自动捕获不成功的请求,并根据需要进行处理。 -
捕获特定异常: 通过分别捕获
HTTPError
、ConnectionError
、Timeout
等特定异常,你可以根据错误类型执行不同的逻辑处理。例如,遇到连接错误时可能需要记录日志,而遇到超时错误时可以尝试重试请求。 -
RequestException
: 作为所有requests
异常的基类,RequestException
可以用来捕获任何与请求相关的错误。这在你不需要区分错误类型时非常有用。
响应的编码问题
当我们获取到 HTTP 响应时,服务器返回的文本内容可能使用了不同的编码格式。如果没有正确处理编码,可能会导致乱码或解析错误。
import requests
response = requests.get('https://example.com')
print(response.text) # 可能会出现乱码
requests
库会尝试根据响应头中的 Content-Type
自动检测编码,但有时服务器没有正确设置编码,导致检测失败。在这种情况下,可以手动设置编码。
import requests
response = requests.get('https://example.com')
# 手动设置编码为 'utf-8'
response.encoding = 'utf-8'
# 现在输出的文本应该没有乱码
print(response.text)
apparent_encoding
是 requests.Response
对象的一个属性,使用 chardet
库来检测响应内容的编码。它与 encoding
属性不同,encoding
是基于 HTTP 响应头的 Content-Type
自动设置的编码,而 apparent_encoding
是通过实际的响应内容来推测的编码。
import requests
url = 'https://example.com'
response = requests.get(url)
# 打印自动推测的编码和服务器提供的编码
print(f"服务器提供的编码: {response.encoding}")
print(f"推测的编码: {response.apparent_encoding}")
# 使用推测的编码
response.encoding = response.apparent_encoding
# 处理响应内容
print(response.text)
二进制资源的爬取和存储
发送 HTTP GET 请求
使用 requests
库发送 GET 请求以获取图片的内容。通过设置 stream=True
,可以分块下载内容,这样适合处理大文件。
import requests
# 图片的 URL
image_url = 'https://example.com/image.jpg'
# 发送 GET 请求获取图片内容
response = requests.get(image_url, stream=True)
requests.get(image_url, stream=True)
: 发送 HTTP GET 请求获取图片,stream=True
表示以流式方式下载响应内容。
检查请求是否成功
使用 raise_for_status()
方法检查请求是否成功,如果请求失败会抛出异常。
# 检查请求是否成功
response.raise_for_status()
response.raise_for_status()
: 如果响应状态码表示请求失败(例如 404 或 500),抛出异常,以便你可以处理错误情况。
保存资源到本地文件
将下载的图片内容写入到本地文件中,使用二进制模式打开文件,并逐块写入数据。
# 本地文件路径
file_path = 'local_image.jpg'
# 以二进制模式打开文件并写入图片内容
with open(file_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
print(f"图片成功下载并保存到 {file_path}")
with open(file_path, 'wb') as file
: 以二进制写入模式('wb'
)打开文件,确保在操作完成后文件被正确关闭。response.iter_content(chunk_size=8192)
: 分块读取响应内容,每块大小为 8192 字节,以避免一次性加载整个文件。file.write(chunk)
: 将每个数据块写入到本地文件中。
保持会话状态
通过 requests.Session
,可以在多个请求之间共享参数、cookies 和 HTTP 连接,适用于需要保持登录状态的操作:
import requests
# 创建会话对象
session = requests.Session()
# 设置公共的请求头
session.headers.update({'User-Agent': 'my-app/0.0.1'})
# 登录后发送请求
session.post('https://example.com/login', data={'username': 'user', 'password': 'pass'})
response = session.get('https://example.com/profile')
print(response.text)
处理超时和重试
在网络请求中,设置超时和实现自动重试机制可以提高程序的稳定性:
设置超时时间
通过 timeout
参数控制请求的最大等待时间:
import requests
try:
response = requests.get('https://httpbin.org/delay/5', timeout=3)
except requests.exceptions.Timeout:
print('请求超时')
实现重试机制
可以使用 HTTPAdapter
和 Retry
类为请求配置自动重试策略:
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
session = requests.Session()
# 定义重试策略
retry = Retry(
total=3, # 总重试次数
backoff_factor=0.3, # 重试间隔时间
status_forcelist=[500, 502, 503, 504], # 需要重试的 HTTP 状态码
)
# 安装适配器
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
try:
response = session.get('https://httpbin.org/status/500')
except requests.exceptions.RetryError:
print('多次重试失败')
伪装浏览器访问
为了使服务器更难察觉你在使用爬虫抓取内容,你可以通过修改和配置以下参数和方法,使得你的爬虫行为更加类似于普通用户的浏览器访问。需要注意的是,尽管这些方法可以帮助你降低被识别的风险,但你仍然应该遵守网站的 robots.txt
规则和服务条款,以避免法律和道德问题。
修改 User-Agent
User-Agent
是 HTTP 请求头的一部分,用于标识发出请求的客户端(如浏览器)。大多数网站会根据 User-Agent
来识别请求是否来自爬虫。因此,设置一个常见的浏览器 User-Agent
可以降低被识别的风险。
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'
}
response = requests.get('https://www.amazon.cn/gp/product/B01M8L5Z3Y', headers=headers)
设置请求间隔和随机延迟
爬虫通常会快速发送多个请求,而普通用户的浏览行为相对较慢。你可以通过在请求之间设置延迟,并随机化这个延迟时间,来模仿人类用户的行为。
import time
import random
delay = random.uniform(1, 5) # 随机延迟1到5秒之间
time.sleep(delay)
使用 Session
保持会话
使用 requests.Session
可以在多个请求之间保持会话,这包括了自动处理 cookies。这使得你的请求行为更接近于一个持续浏览的用户,而不是单个独立的请求。
session = requests.Session()
session.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'})
response = session.get('https://example.com')
添加 Referer 和 Accept-Language 等请求头
除了 User-Agent
外,添加其他常见的 HTTP 头也可以帮助模拟真实的浏览器请求。这些头部字段包括 Referer
(指示从哪里来),Accept-Language
(指示用户语言偏好),Accept-Encoding
(指示可接受的压缩格式)等。
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer': 'https://google.com',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br'
}
response = requests.get('https://example.com', headers=headers)
使用代理服务器
通过代理服务器,你可以改变请求的来源 IP 地址,从而避免被服务器检测到大量请求来自同一 IP 地址。许多网站会限制来自单一 IP 的请求数量,通过使用代理,你可以在不同 IP 之间轮换请求。
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
response = requests.get('https://example.com', proxies=proxies)
处理 Cookies
浏览器通常会在会话期间使用 cookies 来维持状态,你可以在请求中处理和发送 cookies,使你的爬虫更像是一个普通用户。
session = requests.Session()
session.get('https://example.com') # 获取初始 cookies
response = session.get('https://example.com/another-page')
规避反爬虫技术
一些网站使用 JavaScript 生成内容或检测浏览行为,以防止爬虫抓取数据。你可以使用浏览器自动化工具如 Selenium 或 Playwright 结合 requests,以规避这些检测。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
# 获取渲染后的页面内容
html = driver.page_source
控制并发请求数量
大多数爬虫会发出大量并发请求以加快抓取速度,但这会引起服务器的注意。限制并发请求数量或设置请求频率可以帮助模仿正常用户行为。
import requests
from concurrent.futures import ThreadPoolExecutor
urls = ['https://example.com/page1', 'https://example.com/page2']
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
def fetch(url):
response = requests.get(url, headers=headers)
return response.text
with ThreadPoolExecutor(max_workers=2) as executor:
results = executor.map(fetch, urls)
*遵守robots.txt
协议
协议格式
robots.txt
是一个用于向网络爬虫(如搜索引擎爬虫)指示哪些页面或文件可以被抓取的文本文件。它通常放置在网站的根目录下,通过简单的指令告诉爬虫哪些部分的网站是允许或禁止访问的。
一个典型的 robots.txt
文件可能如下所示:
User-agent: *
Disallow: /private/
Allow: /public/
User-agent
: 指定适用的爬虫类型。*
表示适用于所有爬虫。Disallow
: 指定不允许访问的路径。/private/
表示禁止访问该目录及其子目录下的所有内容。Allow
: 指定允许访问的路径。/public/
表示允许访问该目录下的内容。
获取文件
首先,你需要从网站上获取 robots.txt
文件。这通常可以通过访问 https://example.com/robots.txt
来实现。
import requests
robots_url = 'https://example.com/robots.txt'
response = requests.get(robots_url)
response.raise_for_status()
# 获取 robots.txt 内容
robots_txt = response.text
解析文件
requests
库本身并没有类似于 urllib.robotparser
的功能来解析 robots.txt
文件。但是你可以结合 requests
和 urllib.robotparser
来实现对 robots.txt
的解析和遵守。urllib.robotparser
是 Python 标准库中用于解析 robots.txt
文件的工具,可以用来检查某个 URL 是否被允许访问。
from urllib.robotparser import RobotFileParser
from io import StringIO
# 创建 RobotFileParser 实例
rp = RobotFileParser()
# 使用 StringIO 来读取 robots.txt 内容
rp.parse(StringIO(robots_txt))
# 检查某个 URL 是否被允许访问
user_agent = '*'
url_to_check = 'https://example.com/somepage'
if rp.can_fetch(user_agent, url_to_check):
print(f"可以访问 {url_to_check}")
else:
print(f"不能访问 {url_to_check}")
发起请求
如果 URL 被允许访问,则可以使用 requests
库发起请求。
if rp.can_fetch(user_agent, url_to_check):
response = requests.get(url_to_check)
response.raise_for_status()
print(f"请求成功: {response.status_code}")
else:
print(f"访问被拒绝: {url_to_check}")