Skip to content

2.1 使用 requests 发送 GET/POST 请求

网络爬虫的第一步就是学会如何向网站“打招呼”,而 Python 中最常用的“打招呼”工具就是 requests 库。它简单、直观,几乎成了爬虫界的“Hello World”。

GET 请求:最基础的访问方式

GET 请求就像你用浏览器打开一个网页,告诉服务器:“嘿,给我这个页面的内容!”

python
# 导入 requests 库
import requests

# 发送 GET 请求到指定 URL
response = requests.get('https://httpbin.org/get')

# 打印响应状态码(200 表示成功)
print("状态码:", response.status_code)

# 打印响应内容(通常是 HTML 或 JSON)
print("响应内容:", response.text)

POST 请求:提交数据给服务器

POST 请求则像是填写表单后点击“提交”,把数据发给服务器处理。

python
# 导入 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

完整示例:安全地获取和提交数据

下面是一个更完整的例子,包含了错误处理,这是任何健壮爬虫都必须考虑的。

python
# 导入 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 对象还有很多有用的信息:

python
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_codetextheaders 等属性,能让你准确判断请求是否成功,并正确地获取到所需的数据。

Response 对象常用属性速查表

功能名称实例调用方法具体功能与注意事项
获取状态码response.status_code整数类型,200 表示成功,4xx/5xx 表示错误
获取解码后的文本response.text字符串类型,内容已根据 encoding 解码
获取原始二进制数据response.contentbytes 类型,用于下载图片、文件等
获取响应头response.headers类似字典的对象,存储了服务器返回的所有头信息
获取实际请求的URLresponse.url字符串,如果发生了重定向,这里会是最终的URL

完整示例:全面解析响应

下面的代码展示了如何在一个函数中全面检查和利用 Response 对象。

python
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 库对内容进行分析后得出的编码,通常更可靠。
  • content vs text: 如果你要处理的是非文本数据(如图片、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 参数传入一个字典即可。

python
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 不是自家域名的请求。

python
# 同时设置 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-Agentheaders={'User-Agent': '...'}必需,用于伪装成浏览器,避免被识别为爬虫
设置 Refererheaders={'Referer': '来源页面URL'}可选,在遇到防盗链时必需,告诉服务器请求来源
设置 Accept-Languageheaders={'Accept-Language': 'zh-CN,zh;q=0.9'}可选,告诉服务器客户端希望接收的语言,有时能获取到中文内容

完整示例:构建一个更真实的请求

为了更逼真,我们可以一次性设置多个常见的请求头。

python
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

python
import requests

response = requests.get('http://example.com/chinese-page.html')

# 假设我们知道这个网站是 GBK 编码
response.encoding = 'gbk'  # 手动指定编码

# 现在再访问 .text,就会用 GBK 正确解码
print(response.text)  # 应该能看到正常的中文了

解决方案二:自动识别编码

如果不知道编码怎么办?我们可以借助 chardet 库(requests 内部也用它)来自动探测。

python
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必需,在进行编码探测或处理二进制数据时使用

完整示例:智能处理编码的函数

下面是一个集成了自动探测和手动指定的健壮函数。

python
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.encodingISO-8859-1 时,基本可以断定需要手动处理。
  • 性能考量chardet.detect() 需要分析整个 content,对于大文件会比较慢。如果能提前知道编码,手动指定是更高效的选择。