Python requests 爬虫入门:从发送请求到解析 HTML

站长 2026-09-29 0 约 2 分钟 560 字
#Python#爬虫#requests#BeautifulSoup#数据采集

先立规矩:合法合规爬取

写代码之前必须说清楚:

  • 只爬公开数据,不碰需要登录才能看的内容
  • 遵守网站的 robots.txt 协议(在域名后加 /robots.txt 可查看)
  • 控制频率,别把人家服务器压垮(就是讲礼貌)
  • 数据仅供个人学习,不用于商业用途,不爬个人隐私信息

技术无罪,用法有责。下面的练习我们以公开的静态页面为例。

爬虫的本质:三步走

1. 发送请求(requests)→ 服务器返回 HTML 文本
2. 解析内容(BeautifulSoup)→ 从 HTML 里提取要的数据
3. 保存数据(csv/json/数据库)→ 落盘

安装依赖:

pip install requests beautifulsoup4 lxml

第一步:requests 发送请求

最基本的 GET 请求:

import requests

resp = requests.get("https://example.com")
print(resp.status_code)  # 200 表示成功
print(resp.text[:200])   # 网页 HTML 前 200 字符

带参数的 GET:

# 等价于访问 https://example.com/search?q=python&page=2
params = {"q": "python", "page": 2}
resp = requests.get("https://example.com/search", params=params)
print(resp.url)  # 自动拼好的完整 URL

POST 请求(模拟表单提交):

data = {"username": "test", "password": "123456"}
resp = requests.post("https://example.com/login", data=data)

响应对象常用属性:

resp.status_code   # 状态码:200 成功,404 不存在,403 被拒绝
resp.text          # 文本内容(自动按推测编码解码)
resp.content       # 二进制内容(下载图片/文件用这个)
resp.json()        # 接口返回 JSON 时直接转成字典
resp.encoding      # 乱码时手动指定:resp.encoding = "utf-8"

第二步:请求头伪装(过不了反爬就看这里)

很多网站会检查请求头,发现是程序访问就拒绝。加上请求头伪装成浏览器:

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/120.0.0.0 Safari/537.36",
    "Referer": "https://example.com/",  # 有些站检查来源页
}
resp = requests.get(url, headers=headers, timeout=10)

User-Agent 可以直接从浏览器复制:F12 打开开发者工具 → Network → 随便点一个请求 → Headers → Request Headers。

超时设置是必须的:不加 timeout,目标服务器不响应时程序会永远卡住。

第三步:BeautifulSoup 解析 HTML

拿到 HTML 后,用 CSS 选择器提取数据:

from bs4 import BeautifulSoup

html = """
<html><body>
  <div class="article">
    <h2>标题一</h2>
    <p class="intro">简介一</p>
  </div>
  <div class="article">
    <h2>标题二</h2>
    <p class="intro">简介二</p>
  </div>
</body></html>
"""

soup = BeautifulSoup(html, "lxml")

# 最常用的 4 个方法
soup.select("h2")            # CSS 选择器,返回所有匹配的列表
soup.select_one("h2")        # 只返回第一个
soup.find("div", class_="article")   # 按标签+属性查找
soup.find_all("h2")          # 返回所有匹配

# 提取内容
for article in soup.select("div.article"):
    title = article.select_one("h2").text.strip()
    intro = article.select_one("p.intro").text.strip()
    print(title, "|", intro)

提取属性(如链接地址、图片地址):

link = soup.select_one("a")
print(link["href"])     # 取 href 属性
print(link.get_text())  # 取链接文字

完整实战:抓取本站文章列表

下面是一个可以直接改造的实战模板:

import csv
import time

import requests
from bs4 import BeautifulSoup

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
}

def fetch_page(url: str) -> str:
    """下载页面,带重试"""
    for attempt in range(3):
        try:
            resp = requests.get(url, headers=HEADERS, timeout=10)
            resp.raise_for_status()  # 4xx/5xx 直接抛异常
            resp.encoding = "utf-8"
            return resp.text
        except requests.RequestException as e:
            print(f"第 {attempt + 1} 次请求失败: {e}")
            time.sleep(2)
    return ""

def parse_page(html: str) -> list[dict]:
    """解析页面,提取文章列表(选择器按目标网站实际结构调整)"""
    soup = BeautifulSoup(html, "lxml")
    articles = []
    for item in soup.select("div.post-item"):
        title_tag = item.select_one("h2 a")
        if not title_tag:
            continue
        articles.append({
            "title": title_tag.get_text(strip=True),
            "url": title_tag.get("href", ""),
            "date": item.select_one(".date").get_text(strip=True)
                    if item.select_one(".date") else "",
        })
    return articles

def save_csv(data: list[dict], filename: str = "articles.csv"):
    """保存到 CSV,Excel 可直接打开"""
    with open(filename, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=["title", "url", "date"])
        writer.writeheader()
        writer.writerows(data)
    print(f"已保存 {len(data)} 条到 {filename}")

if __name__ == "__main__":
    all_data = []
    for page in range(1, 4):  # 抓前 3 页
        url = f"https://example.com/posts?page={page}"
        html = fetch_page(url)
        if html:
            all_data.extend(parse_page(html))
        time.sleep(1.5)  # 礼貌间隔,别给人家服务器压力
    save_csv(all_data)

动态渲染页面怎么办

上面的方法只适合服务端渲染的页面(HTML 里直接有数据)。如果数据是 JS 动态加载的(resp.text 里找不到要的内容),两条路:

路线一:找接口(优先)

F12 → Network → Fetch/XHR,刷新页面,找返回 JSON 的接口。很多网站前端就是调这些接口拿数据的,直接请求接口比解析 HTML 省事十倍:

resp = requests.get("https://example.com/api/posts?page=1", headers=HEADERS)
data = resp.json()  # 直接拿到结构化数据,连解析都不用

路线二:上浏览器自动化

接口藏得太深时,用 Playwright 驱动真实浏览器:

pip install playwright && playwright install chromium
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://example.com")
    page.wait_for_selector("div.post-item")  # 等内容渲染出来
    html = page.content()
    browser.close()
# 之后照常 BeautifulSoup 解析

常见反爬与应对

反爬手段 表现 应对
UA 检测 403 拒绝 加真实 User-Agent
频率限制 429 或封 IP time.sleep 降速、换 IP
登录墙 跳转登录页 requests.Session() 保持 Cookie
验证码 弹验证 降低频率避开触发点,不建议硬刚
动态渲染 HTML 里没数据 找接口或用 Playwright

写在最后

爬虫 80% 的工作量是「分析目标网站」而不是写代码:数据在 HTML 里还是接口里?分页参数长什么样?有没有反爬?先花 20 分钟用 F12 把网站看透,代码往往 20 行就够了。

评论 (0)

我的头像

还没有评论,快来抢沙发吧~