2.1 使用 requests 发送 GET/POST 请求
网络爬虫的第一步就是学会如何向网站“打招呼”,而 Python 中最常用的“打招呼”工具就是 requests 库。它简单、直观,几乎成了爬虫界的“Hello World”。
GET 请求:最基础的访问方式
GET 请求就像你用浏览器打开一个网页,告诉服务器:“嘿,给我这个页面的内容!”
# 导入 requests 库
import requests
# 发送 GET 请求到指定 URL
response = requests.get('https://httpbin.org/get')
# 打印响应状态码(200 表示成功)
print("状态码:", response.status_code)
# 打印响应内容(通常是 HTML 或 JSON)
print("响应内容:", response.text)POST 请求:提交数据给服务器
POST 请求则像是填写表单后点击“提交”,把数据发给服务器处理。
# 导入 requests 库
import requests
# 要发送的数据(字典格式)
data = {
'username': 'testuser',
'password': '123456'
}
# 发送 POST 请求,并附带数据
response = requests.post('https://httpbin.org/post', data=data)
# 打印响应状态码
print("状态码:", response.status_code)
# 打印服务器返回的内容(通常包含你发送的数据)
print("响应内容:", response.text)小结:GET 用于获取数据,POST 用于提交数据。requests 库让这两种请求变得异常简单,是爬虫入门的必备技能。
requests 常用方法速查表
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 发送 GET 请求 | requests.get(url, params=None, **kwargs) | params 用于传递查询字符串参数(如 ?key=value),可选 |
| 发送 POST 请求 | requests.post(url, data=None, json=None, **kwargs) | data 用于表单数据,json 用于 JSON 数据,二者选一 |
| 发送 PUT/DELETE 等 | requests.put(url, ...) / requests.delete(url, ...) | 适用于 RESTful API,用法类似 GET/POST |
完整示例:安全地获取和提交数据
下面是一个更完整的例子,包含了错误处理,这是任何健壮爬虫都必须考虑的。
# 导入 requests 库
import requests
def safe_get(url):
"""
安全地发送 GET 请求
:param url: 目标 URL
:return: 响应文本或 None
"""
try:
# 设置超时时间为5秒,防止程序卡死
response = requests.get(url, timeout=5)
# 检查 HTTP 状态码是否为 2xx (成功)
response.raise_for_status() # 如果状态码不是2xx,会抛出异常
return response.text
except requests.exceptions.Timeout:
print(f"请求超时: {url}")
except requests.exceptions.HTTPError as e:
print(f"HTTP 错误: {e}")
except requests.exceptions.RequestException as e:
print(f"请求发生错误: {e}")
return None
def safe_post(url, data):
"""
安全地发送 POST 请求
:param url: 目标 URL
:param data: 要发送的数据 (dict)
:return: 响应文本或 None
"""
try:
response = requests.post(url, data=data, timeout=5)
response.raise_for_status()
return response.text
except requests.exceptions.Timeout:
print(f"POST 请求超时: {url}")
except requests.exceptions.HTTPError as e:
print(f"POST HTTP 错误: {e}")
except requests.exceptions.RequestException as e:
print(f"POST 请求发生错误: {e}")
return None
# 使用示例
if __name__ == "__main__":
# 尝试获取一个网页
html = safe_get('https://httpbin.org/get')
if html:
print("GET 成功!")
# 尝试提交一些数据
post_data = {'key': 'value'}
result = safe_post('https://httpbin.org/post', post_data)
if result:
print("POST 成功!")注意事项:
- 超时设置:永远不要忘记设置
timeout参数,否则你的程序可能会因为某个慢速网站而无限期挂起。 - 异常处理:网络是不可靠的,
requests库会抛出各种异常(如连接错误、超时、HTTP错误等),必须用try...except块来捕获并妥善处理。 raise_for_status():这是一个非常有用的方法,它会在 HTTP 状态码表示错误(如404, 500)时自动抛出HTTPError异常,让你能集中处理所有非成功响应。
2.2 响应对象解析:status_code、text、encoding
当你成功发送一个请求后,requests 会返回一个 Response 对象。这个对象就像一个百宝箱,里面装着服务器给你的所有信息。学会如何打开这个箱子并取出你需要的东西,是爬虫的核心技能。
核心属性:status_code, text, encoding
status_code: 这是 HTTP 状态码,告诉你请求的结果。200 是成功,404 是页面没找到,500 是服务器内部错误等等。text: 这是响应的主体内容,通常是 HTML、JSON 或纯文本。它是经过requests库根据encoding属性解码后的字符串。encoding: 这是requests库推测的响应内容的编码格式(如 'utf-8', 'gbk')。你可以读取它,也可以手动设置它来解决乱码问题。
查看响应头和其他信息
除了上面三个核心属性,Response 对象还有很多有用的信息:
import requests
# 发送一个请求
resp = requests.get('https://www.example.com')
# 查看状态码
print("状态码:", resp.status_code) # 例如: 200
# 查看响应头 (一个字典)
print("Content-Type:", resp.headers['Content-Type']) # 例如: 'text/html; charset=UTF-8'
# 查看原始的二进制内容 (bytes)
print("原始内容前100字节:", resp.content[:100])
# 查看 URL (可能和请求的 URL 不同,比如发生了重定向)
print("最终 URL:", resp.url)小结:Response 对象是连接你和目标网站的桥梁。熟练掌握 status_code、text 和 headers 等属性,能让你准确判断请求是否成功,并正确地获取到所需的数据。
Response 对象常用属性速查表
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 获取状态码 | response.status_code | 整数类型,200 表示成功,4xx/5xx 表示错误 |
| 获取解码后的文本 | response.text | 字符串类型,内容已根据 encoding 解码 |
| 获取原始二进制数据 | response.content | bytes 类型,用于下载图片、文件等 |
| 获取响应头 | response.headers | 类似字典的对象,存储了服务器返回的所有头信息 |
| 获取实际请求的URL | response.url | 字符串,如果发生了重定向,这里会是最终的URL |
完整示例:全面解析响应
下面的代码展示了如何在一个函数中全面检查和利用 Response 对象。
import requests
def analyze_response(url):
"""
全面分析一个 HTTP 响应
:param url: 目标 URL
"""
try:
response = requests.get(url, timeout=5)
# 1. 检查状态码
print(f"[状态码] {response.status_code}")
if response.status_code != 200:
print("⚠️ 请求未成功!")
return
# 2. 检查 Content-Type
content_type = response.headers.get('content-type', '未知')
print(f"[Content-Type] {content_type}")
# 3. 检查编码
print(f"[推测编码] {response.encoding}")
print(f"[真实编码] {response.apparent_encoding}") # apparent_encoding 是基于内容推测的,有时更准
# 4. 打印部分内容
print("[响应内容预览]")
print(response.text[:500] + "...") # 只打印前500个字符
except requests.exceptions.RequestException as e:
print(f"❌ 请求失败: {e}")
# 使用示例
analyze_response('https://httpbin.org/html')注意事项:
apparent_encoding: 当response.encoding推测不准确导致乱码时,可以尝试使用response.apparent_encoding,它是基于chardet库对内容进行分析后得出的编码,通常更可靠。contentvstext: 如果你要处理的是非文本数据(如图片、PDF),一定要使用response.content来获取原始的二进制数据,而不是response.text。- 响应头大小写不敏感:
response.headers的键是不区分大小写的,所以response.headers['Content-Type']和response.headers['content-type']效果一样。
2.3 设置请求头(User-Agent、Referer)模拟浏览器
很多网站都不太喜欢被爬虫访问,它们会通过检查请求头(Headers)来识别你是不是一个“正常”的浏览器。如果你的请求头看起来像个机器人,很可能就会被拒之门外。因此,学会伪造请求头,让自己看起来像个普通用户,是爬虫进阶的必修课。
什么是 User-Agent?
User-Agent`(简称 UA)是浏览器在每次请求时都会发送的一个字符串,用来告诉服务器“我是谁”。比如,Chrome 浏览器的 UA 可能长这样:
`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36如果你不设置 UA,requests 库默认会发送 python-requests/2.x.x,这简直就是对着服务器大喊:“我是爬虫!快来封我!”
如何设置请求头?
在 requests 中,通过 headers 参数传入一个字典即可。
import requests
# 定义一个看起来像 Chrome 浏览器的请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# 发送带有自定义请求头的 GET 请求
response = requests.get('https://httpbin.org/headers', headers=headers)
# 打印服务器收到的请求头,看看我们的伪装是否成功
print(response.json()) # httpbin.org/headers 会以 JSON 格式返回你发送的请求头Referer 的作用
Referer(注意,这个单词拼错了,但约定俗成了)头字段告诉服务器,你是从哪个页面跳转过来的。有些网站会检查这个字段,以防止别人直接链接到它的资源(防盗链)。比如,一个图片服务器可能会拒绝没有 Referer 或者 Referer 不是自家域名的请求。
# 同时设置 User-Agent 和 Referer
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://www.google.com/' # 假装是从 Google 搜索结果点进来的
}
response = requests.get('https://some-website.com/page', headers=headers)小结:通过精心构造 headers 字典,我们可以有效地模拟真实浏览器的行为,绕过一些简单的反爬虫机制。User-Agent 是最基本的伪装,而 Referer 则在处理特定资源时非常有用。
常用请求头设置速查表
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 设置 User-Agent | headers={'User-Agent': '...'} | 必需,用于伪装成浏览器,避免被识别为爬虫 |
| 设置 Referer | headers={'Referer': '来源页面URL'} | 可选,在遇到防盗链时必需,告诉服务器请求来源 |
| 设置 Accept-Language | headers={'Accept-Language': 'zh-CN,zh;q=0.9'} | 可选,告诉服务器客户端希望接收的语言,有时能获取到中文内容 |
完整示例:构建一个更真实的请求
为了更逼真,我们可以一次性设置多个常见的请求头。
import requests
def create_browser_like_session():
"""
创建一个模拟真实浏览器的 requests Session 对象
"""
session = requests.Session()
# 设置一个综合的、看起来很真实的请求头
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
})
return session
# 使用示例
if __name__ == "__main__":
# 创建一个模拟浏览器的会话
browser = create_browser_like_session()
try:
# 用这个会话去请求页面
response = browser.get('https://httpbin.org/headers', timeout=5)
response.raise_for_status()
# 打印服务器看到的我们的请求头
print("服务器接收到的请求头:")
for key, value in response.json()['headers'].items():
print(f" {key}: {value}")
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")注意事项:
- 使用
Session对象:上面的例子使用了requests.Session()。Session对象可以在多次请求之间保持 cookie 和 header,效率更高,也更像一个真实的浏览器会话。 - 不要过度伪装:虽然可以设置很多头,但也要注意合理性。比如,不要在一个 Windows 的 UA 下设置 macOS 的
Accept-Language。 - 动态变化:高级的反爬系统可能会检测 UA 是否长时间不变。在大规模爬取时,可以从一个 UA 列表中随机选择,增加真实性。
2.4 处理中文乱码与编码自动识别
辛辛苦苦爬下来的数据,打开一看全是“锟斤拷”、“烫烫烫”?别慌,这是经典的中文乱码问题。根本原因在于:服务器返回的数据是某种编码(如 GBK),但你的程序却用另一种编码(如 UTF-8)去解读它,自然就“鸡同鸭讲”了。
乱码的根源:编码不匹配
requests 库在获取到响应后,会根据响应头中的 Content-Type 字段(如 text/html; charset=gbk)来推测编码,并用这个编码去解码 content 得到 text。但很多时候,服务器要么不提供 charset,要么提供的 charset 是错的,这就导致了乱码。
解决方案一:手动指定编码
如果你知道目标网站使用的是什么编码(比如通过查看网页源代码的 <meta> 标签),可以直接告诉 requests。
import requests
response = requests.get('http://example.com/chinese-page.html')
# 假设我们知道这个网站是 GBK 编码
response.encoding = 'gbk' # 手动指定编码
# 现在再访问 .text,就会用 GBK 正确解码
print(response.text) # 应该能看到正常的中文了解决方案二:自动识别编码
如果不知道编码怎么办?我们可以借助 chardet 库(requests 内部也用它)来自动探测。
import requests
import chardet
response = requests.get('http://example.com/unknown-encoding.html')
# 使用 chardet 探测编码
detected_encoding = chardet.detect(response.content)['encoding']
print(f"探测到的编码: {detected_encoding}")
# 将探测到的编码赋值给 response.encoding
response.encoding = detected_encoding
print(response.text) # 应该能正确显示了小结:中文乱码是爬虫新手最常见的坑之一。核心思路就是确保 response.text 使用的解码方式和服务器发送数据时的编码方式一致。要么手动指定,要么用 chardet 自动识别,总有一款适合你。
编码处理方法速查表
| 功能名称 | 实例调用方法 | 具体功能与注意事项 |
|---|---|---|
| 手动指定编码 | response.encoding = '编码名' | 必需,当你确切知道网页编码时使用,如 'utf-8', 'gbk', 'gb2312' |
| 自动探测编码 | chardet.detect(response.content)['encoding'] | 可选,当编码未知时使用,需要先安装 chardet 库 (pip install chardet) |
| 获取原始字节 | response.content | 必需,在进行编码探测或处理二进制数据时使用 |
完整示例:智能处理编码的函数
下面是一个集成了自动探测和手动指定的健壮函数。
import requests
import chardet
def get_text_with_correct_encoding(url, specified_encoding=None):
"""
获取网页文本,并自动或手动处理编码问题
:param url: 目标 URL
:param specified_encoding: 可选,手动指定的编码
:return: 正确解码的文本字符串
"""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
# 如果用户指定了编码,优先使用
if specified_encoding:
response.encoding = specified_encoding
print(f"使用指定编码: {specified_encoding}")
else:
# 否则,尝试自动探测
detected = chardet.detect(response.content)
confidence = detected['confidence']
encoding = detected['encoding']
# 如果探测置信度高,就使用它
if confidence > 0.8 and encoding is not None:
response.encoding = encoding
print(f"自动探测编码: {encoding} (置信度: {confidence:.2f})")
else:
# 置信度低,就用 requests 默认的或 fallback 到 utf-8
print("探测置信度低,使用默认编码")
if response.encoding == 'ISO-8859-1':
# requests 在无法确定时会默认用 ISO-8859-1,这通常是错的
response.encoding = 'utf-8'
return response.text
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
except Exception as e:
print(f"处理编码时出错: {e}")
return None
# 使用示例
if __name__ == "__main__":
# 尝试抓取一个已知是 gbk 编码的网站(仅为演示)
# text = get_text_with_correct_encoding('http://some-gbk-site.com', specified_encoding='gbk')
# 尝试抓取一个编码未知的网站
text = get_text_with_correct_encoding('https://httpbin.org/html')
if text:
print("成功获取文本!")
# print(text) # 取消注释以查看内容注意事项:
chardet的局限性:chardet并非万能,对于内容很少或者编码混杂的页面,探测结果可能不准确。置信度(confidence)是一个很好的参考指标。ISO-8859-1陷阱:当requests无法从任何地方确定编码时,它会默认使用ISO-8859-1。这个编码几乎肯定不是中文网站的编码,所以看到response.encoding是ISO-8859-1时,基本可以断定需要手动处理。- 性能考量:
chardet.detect()需要分析整个content,对于大文件会比较慢。如果能提前知道编码,手动指定是更高效的选择。