从零到接单 08:HTTP 协议与 requests 库——让程序上网

pythontutorial爬虫自动化接单requestsHTTP

系列目录:本文是「从零到接单:Python 自动化与爬虫实战」系列的第 8 篇。基础语法已经学完,从这篇开始,我们正式进入爬虫世界。你将学会用 Python 发网络请求、获取网页内容——这是所有爬虫的第一步。


前面的程序都在本地"自娱自乐"。现在让 Python 上网——从互联网抓取数据。这才是接单赚钱的核心能力。


一、理解 HTTP:浏览器和服务器怎么"说话"

打开一个网页,浏览器和服务器之间经历了什么?

你(浏览器)                    服务器
    |                              |
    |——— GET /index.html ————→    |
    |                              |(查找文件)
    |←—— 200 OK + HTML 内容 ———   |
    |                              |
    |——— GET /style.css ——————→   |
    |←—— 200 OK + CSS 内容 ———    |
    |                              |

一个 HTTP 请求包含:

  • URL:要访问的地址
  • 方法:GET(拿数据)、POST(提交数据)、PUT(更新)、DELETE(删除)
  • 请求头(Headers):User-Agent(我是谁)、Cookie、Content-Type 等
  • 请求体(Body):POST 时携带的数据

一个 HTTP 响应包含:

  • 状态码:200(成功)、301(重定向)、404(找不到)、500(服务器错误)
  • 响应头:Content-Type、Set-Cookie 等
  • 响应体:HTML、JSON、图片等实际内容

二、安装 requests

pip install requests

requests 是 Python 最流行的 HTTP 库,被称为"HTTP for Humans"——把复杂的 HTTP 操作变得简单优雅。


三、第一个爬虫:GET 请求

import requests

# 发送 GET 请求
response = requests.get("https://httpbin.org/get")

# 状态码
print(f"状态码:{response.status_code}")        # 200

# 响应头
print(f"Content-Type:{response.headers['Content-Type']}")

# 响应内容(文本)
print(f"内容:{response.text[:200]}...")        # 前 200 个字符

# 如果是 JSON 响应
data = response.json()
print(f"JSON 数据:{data}")

常用响应属性

resp = requests.get("https://example.com")

resp.status_code        # 状态码:200, 404, 500...
resp.text               # 响应文本(字符串)
resp.content            # 响应内容(bytes,处理图片/文件时用)
resp.json()             # 解析 JSON 响应
resp.headers            # 响应头字典
resp.url                # 最终 URL(可能被重定向过)
resp.encoding           # 编码
resp.cookies            # Cookie
resp.elapsed            # 请求耗时

四、带参数的请求

URL 参数(查询字符串)

# 方式一:手动拼在 URL 里
resp = requests.get("https://httpbin.org/get?name=张三&age=25")

# 方式二:用 params 参数(推荐,自动处理特殊字符)
params = {
    "name": "张三",
    "age": 25,
    "city": "北京"
}
resp = requests.get("https://httpbin.org/get", params=params)
print(resp.url)  # https://httpbin.org/get?name=%E5%BC%A0%E4%B8%89&age=25&city=%E5%8C%97%E4%BA%AC

POST 请求:提交数据

# 提交表单数据
data = {
    "username": "admin",
    "password": "123456"
}
resp = requests.post("https://httpbin.org/post", data=data)
print(resp.json()["form"])

# 提交 JSON 数据
import json
resp = requests.post(
    "https://httpbin.org/post",
    json={"name": "小明", "score": 95}
)
print(resp.json()["json"])

五、请求头:伪装成浏览器

服务器会检查请求头,如果发现是 Python 脚本,可能拒绝访问。

# 默认的 requests User-Agent 长这样:
# python-requests/2.28.0

# ❌ 很多网站会封掉这种请求!

设置请求头

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Referer": "https://www.google.com/",
}

resp = requests.get("https://httpbin.org/headers", headers=headers)
print(resp.json()["headers"]["User-Agent"])
# Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...

💡 一个好习惯:把常用 headers 封装到函数或配置文件里。


六、Cookie 与 Session:保持登录状态

# 方式一:直接传 Cookie
cookies = {"session_id": "abc123", "token": "xyz789"}
resp = requests.get("https://httpbin.org/cookies", cookies=cookies)
print(resp.json())

# 方式二:用 Session 自动管理 Cookie(推荐!)
session = requests.Session()

# 先登录
login_data = {"username": "test", "password": "123456"}
session.post("https://httpbin.org/post", data=login_data)

# 之后所有请求都自动携带 Cookie
resp = session.get("https://httpbin.org/cookies")
print(resp.json())

七、超时与重试:健壮的爬虫

# 设置超时(秒)
try:
    resp = requests.get("https://httpbin.org/delay/5", timeout=3)
except requests.Timeout:
    print("请求超时!")

# timeout 可以设为元组:(连接超时, 读取超时)
resp = requests.get("https://httpbin.org/get", timeout=(3, 10))

封装一个带重试的请求函数

import time

def safe_request(url, max_retries=3, timeout=10, **kwargs):
    """带重试的请求函数"""
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, timeout=timeout, **kwargs)
            resp.raise_for_status()  # 非 200 状态码抛出异常
            return resp
        except requests.RequestException as e:
            if attempt == max_retries - 1:
                raise e
            wait = 2 ** attempt  # 指数退避:1s, 2s, 4s
            print(f"第 {attempt + 1} 次失败,{wait} 秒后重试...")
            time.sleep(wait)

# 使用
resp = safe_request("https://httpbin.org/get")
print(resp.status_code)

八、代理设置

爬虫必备技能——用代理避免 IP 被封。

# HTTP 代理
proxies = {
    "http": "http://127.0.0.1:7890",
    "https": "http://127.0.0.1:7890",
}
resp = requests.get("https://httpbin.org/ip", proxies=proxies)
print(resp.json())

# 带认证的代理
proxies = {
    "http": "http://user:password@proxy.example.com:8080",
}

九、实战:写一个简单的网页抓取器

import requests
import re
import time
from pathlib import Path

class SimpleCrawler:
    """简易网页抓取器"""
    
    def __init__(self, delay=1):
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
        })
        self.delay = delay
        self.output_dir = Path("crawled_pages")
        self.output_dir.mkdir(exist_ok=True)
    
    def fetch(self, url, max_retries=3):
        """抓取单个页面"""
        for attempt in range(max_retries):
            try:
                print(f"正在抓取:{url}")
                resp = self.session.get(url, timeout=10)
                resp.raise_for_status()
                
                # 自动检测编码
                resp.encoding = resp.apparent_encoding
                
                print(f"✅ 成功!状态码:{resp.status_code},大小:{len(resp.text)} 字符")
                time.sleep(self.delay)
                return resp.text
                
            except requests.RequestException as e:
                print(f"❌ 第 {attempt + 1} 次失败:{e}")
                if attempt == max_retries - 1:
                    return None
                time.sleep(2 ** attempt)
    
    def save_page(self, url, html):
        """保存页面到本地"""
        safe_name = re.sub(r"[^\w\-_.]", "_", url)[:100]
        filepath = self.output_dir / f"{safe_name}.html"
        with open(filepath, "w", encoding="utf-8") as f:
            f.write(html)
        print(f"已保存到:{filepath}")
        return filepath
    
    def extract_links(self, html):
        """从 HTML 中提取所有链接"""
        return re.findall(r'href="(https?://[^"]+)"', html)

# 使用
if __name__ == "__main__":
    crawler = SimpleCrawler(delay=1)
    
    # 抓取单个页面
    html = crawler.fetch("https://httpbin.org/html")
    if html:
        crawler.save_page("httpbin-test", html)
        links = crawler.extract_links(html)
        print(f"提取到 {len(links)} 个链接")

总结

| 操作 | 代码 | |------|------| | GET 请求 | requests.get(url) | | POST 请求 | requests.post(url, data={}) | | 带参数 | requests.get(url, params={...}) | | 请求头 | requests.get(url, headers={...}) | | 保持登录 | session = requests.Session() | | 超时 | requests.get(url, timeout=10) | | 代理 | requests.get(url, proxies={...}) | | 检查状态 | resp.raise_for_status() | | 解析 JSON | resp.json() | | 获取文本 | resp.text |

你已经能让 Python 上网了!下一篇,我们将学习 BeautifulSoup——从抓回来的 HTML 中精确提取想要的信息。


练习:写一个程序,获取 httpbin.org/headers 的内容,打印出服务器看到的你的 User-Agent。然后修改请求头,伪装成 iPhone 浏览器再试一次。

iPhone Safari UA: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1

Comments

Sign in to leave a comment.