Python 爬虫实战:从 requests 到 Scrapy 的完整进阶之路
爬虫是 Python 最经典的实战领域之一。从单脚本抓取到分布式采集,从静态页面到 JS 渲染,本文梳理一条清晰的进阶路径,并重点讲解数据解析、反爬对抗与工程化落地。
一、地基:requests + 解析三板斧
1.1 基础请求
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://example.com/",
}
resp = requests.get("https://api.example.com/list", headers=headers, timeout=10, params={"page": 1})
resp.raise_for_status()
data = resp.json()
三条底线:必须带合理的 UA、必须设 timeout、必须 raise_for_status() 检查状态码。
1.2 解析三件套
| 工具 | 适用场景 | 特点 | |------|----------|------| | 正则 re | 提取 URL、ID 等结构化片段 | 快但脆弱 | | BeautifulSoup | 静态 HTML 的 CSS 选择器 | 简单直观 | | lxml + XPath | 复杂层级、属性提取 | 性能最好 |
from bs4 import BeautifulSoup
soup = BeautifulSoup(resp.text, "lxml")
for item in soup.select("div.article-list > .item"):
title = item.select_one("h2 a").get_text(strip=True)
link = item.select_one("h2 a")["href"]
date = item.select_one(".date").get_text(strip=True)
二、会话、Cookie 与限速
2.1 Session 复用
session = requests.Session()
session.get("https://example.com/login") # 建立 Cookie
session.post("https://example.com/api/login", json={"user": "u", "pass": "p"})
# 后续请求自动携带登录态
resp = session.get("https://example.com/private/data")
2.2 礼貌爬取:限速与重试
import time
from requests.adapters import HTTPAdapter
session.mount("https://", HTTPAdapter(max_retries=3))
for url in urls:
session.get(url)
time.sleep(1) # 至少 1 秒间隔,避免给目标服务器造成压力
工程意识:爬虫不是攻击,遵守 robots.txt、设置合理间隔、保留来源标识,是长期稳定采集的前提。
三、JS 渲染页面:两种破解思路
3.1 找接口(优先方案)
多数"动态页面"的数据来自 XHR 接口,用浏览器 DevTools 的 Network 面板定位数据接口,直接请求 JSON:
resp = requests.get("https://example.com/api/articles?page=2&size=20", headers=headers)
for item in resp.json()["data"]["items"]:
print(item["title"])
3.2 无头浏览器兜底
接口加密、签名复杂时,用 Playwright 渲染真实浏览器:
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/list", wait_until="networkidle")
# 等待数据渲染完成
page.wait_for_selector(".article-list .item")
items = page.query_selector_all(".article-list .item")
for it in items:
print(it.query_selector("h2 a").inner_text())
browser.close()
Playwright 的优势:真实渲染、自动等待、支持点击/滚动等交互触发加载。
四、数据清洗与落库
4.1 清洗常规动作
import re
def clean(text: str) -> str:
text = re.sub(r"<[^>]+>", "", text) # 去 HTML 标签
text = re.sub(r"\s+", " ", text) # 合并空白
return text.strip()
4.2 批量入库(参数化 + 事务)
import sqlite3
conn = sqlite3.connect("data.db")
with conn: # 事务自动提交/回滚
conn.executemany(
"INSERT OR IGNORE INTO articles(title, url, content, fetched_at) VALUES (?, ?, ?, ?)",
[(t, u, c, time.time()) for t, u, c in cleaned],
)
去重策略:URL 建唯一索引 + INSERT OR IGNORE,幂等采集。
五、反爬对抗的正确姿势
5.1 常见的反爬手段与对策
| 反爬手段 | 对策 | |----------|------| | UA 校验 | 随机 UA 池 | | IP 频率限制 | 代理池轮换 + 合理限速 | | 验证码 | 降低频率、优先找接口;必要时接入打码服务 | | 字体反爬 | 下载字体文件映射字形到字符 | | 参数签名 | 逆向 JS、模拟签名算法 | | 登录墙 | Cookie/Session 维持登录态 |
5.2 对抗的道德与技术边界
- 只在目标允许的范围内采集,尊重 robots.txt 与 ToS;
- 控制并发与频率,不造成目标服务不可用;
- 涉及个人信息时注意合规(匿名化、最小化收集)。
六、Scrapy:工程化框架的进阶
单个脚本到 10 个站点、百万级页面时,Scrapy 的异步调度优势凸显:
import scrapy
class ArticleSpider(scrapy.Spider):
name = "articles"
start_urls = ["https://example.com/list/1"]
def parse(self, response):
for item in response.css(".article-list .item"):
yield {
"title": item.css("h2 a::text").get().strip(),
"url": item.css("h2 a::attr(href)").get(),
}
next_page = response.css(".pagination .next::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)
运行与输出:
scrapy crawl articles -O articles.jsonl
Scrapy 的核心能力:异步并发抓取、自动去重、Pipeline 落库/导出、中间件支持代理与重试、Item 校验。
七、爬虫工程化的五条经验
- 先小后大:先抓 20 条验证解析逻辑,再放开全量;
- 可观测:每批任务记录成功/失败数与耗时,失败原因分类统计;
- 幂等可重跑:断点续爬(记录游标/最后 ID),失败任务可重试;
- 隔离环境:爬虫与业务服务分开部署,异常不互相拖累;
- 尊重边界:频率、范围、用途都保持克制,才能长久稳定。
结语
爬虫的进阶路径其实很清晰:requests 入门 → 解析与清洗 → 会话与限速 → 反爬对抗 → Scrapy 工程化。真正拉开差距的不是某个酷炫技巧,而是工程习惯——幂等、可观测、有边界。把采集当作"受控的数据管道"来设计,而不是"临时的取数脚本",你的爬虫体系才能支撑起长期稳定的数据业务。
评论(0)
登录 后参与评论。