Python 爬虫的高效性,依赖于 "请求 - 解析 - 工程化" 的工具链协同。Requests、Beautiful Soup、Scrapy 三者分别对应爬虫流程的核心环节,不仅要会用,更要理解 "为何用""何时用"。以下从核心原理、进阶功能、实战案例、避坑技巧四维度,带你彻底掌握这三大库。
一、Requests:爬虫的 "HTTP 请求瑞士军刀"
Requests 基于 urllib3 封装,核心是 "简化 HTTP 协议交互"------ 无需手动拼接请求头、处理编码异常,让开发者专注于 "要爬什么",而非 "怎么发请求"。
1. 核心原理:模拟浏览器与服务器通信
当你用 requests.get(url) 时,底层发生了 3 件事:
- 构建符合 HTTP 协议的请求头(如
User-Agent、Cookie); - 向目标服务器发送 TCP 连接,传递请求信息;
- 接收服务器响应,自动解析响应状态码(如 200 = 成功、403 = 禁止访问)和响应体(HTML/JSON)。
2. 进阶功能:应对 90% 基础反爬
(1)请求头伪装:突破 "浏览器检测"
服务器常通过 User-Agent 判断请求是否来自浏览器,直接发送请求会被识别为 "爬虫",返回 403。解决方案 :添加真实浏览器的User-Agent,甚至补充Referer(表明请求来源页面)。
python
运行
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Referer": "https://www.example.com/" # 模拟从example.com跳转过来的请求
}
response = requests.get("https://www.example.com/target", headers=headers)
(2)Session 维持:模拟 "登录状态"
爬取需要登录的页面(如个人账号数据)时,单独的get请求会丢失登录 Cookie。requests.Session() 可维持会话,自动保存 Cookie。
python
运行
# 1. 创建Session对象(自动管理Cookie)
s = requests.Session()
# 2. 先发送登录请求(传递账号密码,获取登录Cookie)
login_data = {"username": "your_id", "password": "your_pwd"}
s.post("https://www.example.com/login", data=login_data, headers=headers)
# 3. 再访问需要登录的页面(已携带登录Cookie,可正常获取数据)
user_data = s.get("https://www.example.com/my-data", headers=headers)
print(user_data.text)
(3)代理 IP 配置:突破 "IP 封禁"
频繁请求同一 IP 会被服务器封禁,需用代理 IP 切换请求来源。
python
运行
# 代理IP格式:{协议: 代理地址}
proxies = {
"http": "http://123.45.67.89:8080",
"https": "https://123.45.67.89:8443"
}
# 发送请求时指定代理
response = requests.get("https://www.example.com", headers=headers, proxies=proxies)
3. 避坑技巧
- 编码乱码 :服务器返回的 HTML 可能是
gbk编码(如部分中文网站),用response.encoding = "gbk"手动指定,而非默认的utf-8; - 超时设置 :避免请求卡在无响应的服务器,添加
timeout=5(5 秒内无响应则报错); - SSL 证书错误 :爬取
https网站时若提示证书错误,添加verify=False(仅用于测试,生产环境需配置证书)。
二、Beautiful Soup:爬虫的 "HTML 解析手术刀"
Beautiful Soup 核心是 "将非结构化的 HTML 字符串,转为结构化的 DOM 树对象",支持通过 "标签、属性、文本" 精准定位数据,无需写复杂正则。
1. 核心原理:DOM 树遍历与查询
当你用 BeautifulSoup(html, "lxml") 时,工具会:
- 解析 HTML 字符串,生成类似浏览器的 DOM 树(每个标签都是一个 "节点");
- 提供
find()/find_all()等方法,按 "标签名 + 属性" 查询节点; - 从节点中提取文本(
get_text())或属性值(['href'])。
2. 进阶功能:精准提取复杂数据
(1)多条件查询:定位唯一节点
当页面有多个相同标签时,通过 "标签名 + class+id" 组合查询,避免提取错误数据。
python
运行
from bs4 import BeautifulSoup
import requests
html = requests.get("https://www.example.com/product", headers=headers).text
soup = BeautifulSoup(html, "lxml") # 推荐用lxml解析器(速度比默认html.parser快3倍)
# 提取:class为"product-price"且id为"current-price"的span标签(商品当前价格)
price = soup.find("span", class_="product-price", id="current-price") # class_加下划线避免与关键字冲突
print(price.get_text()) # 输出价格文本,如"¥999"
# 提取:所有class为"product-item"的div标签下的a标签(商品链接)
product_links = soup.find_all("div", class_="product-item")
for item in product_links:
link = item.find("a")["href"] # 获取a标签的href属性(商品链接)
print(link)
(2)CSS 选择器:更灵活的定位
若习惯前端 CSS 选择器(如.class、#id),可用soup.select(),语法更简洁。
python
运行
# 1. 用CSS选择器提取:id为"current-price"的标签(#表示id)
price = soup.select("#current-price")[0].get_text() # select返回列表,取第1个元素
# 2. 用CSS选择器提取:class为"product-item"下的a标签(.表示class)
product_links = soup.select(".product-item a")
for link in product_links:
print(link["href"])
(3)处理嵌套标签:提取深层数据
面对多层嵌套的 HTML(如 "商品列表→商品项→商品名称"),通过 "父节点→子节点" 逐层定位,避免干扰。
html
预览
<!-- 页面HTML结构 -->
<div class="product-list">
<div class="product-item">
<h3 class="product-name">手机A</h3>
</div>
<div class="product-item">
<h3 class="product-name">手机B</h3>
</div>
</div>
python
运行
# 1. 先定位父节点(商品列表)
product_list = soup.find("div", class_="product-list")
# 2. 在父节点内提取所有子节点(商品名称)
product_names = product_list.find_all("h3", class_="product-name")
for name in product_names:
print(name.get_text()) # 输出"手机A"、"手机B"
3. 避坑技巧
- 解析器选择 :优先用
lxml(需提前安装:pip install lxml),速度快且支持容错(能解析不规范的 HTML); - 空值处理 :若
find()未找到节点,会返回None,直接调用get_text()会报错,需先判断:if price: print(price.get_text()); - 动态内容 :Beautiful Soup 只能解析 "静态 HTML",若数据是 JavaScript 动态加载(如滚动加载的列表),需搭配
Selenium或抓包工具获取 API 接口。
三、Scrapy:爬虫的 "工程化作战系统"
Scrapy 不是单一库,而是 "请求调度 + 数据解析 + 存储 + 反爬" 的完整框架,核心是 "用组件化思想,实现高并发、可维护的爬虫项目"。
1. 核心原理:组件化工作流
Scrapy 的工作流由 5 个核心组件协同完成:
- 爬虫(Spider):定义爬取 URL、解析数据的逻辑(开发者主要编写这部分);
- 调度器(Scheduler):管理待爬 URL 队列,避免重复爬取,支持优先级排序;
- 下载器(Downloader) :发送 HTTP 请求,获取响应(类似
Requests,但支持并发); - 管道(Item Pipeline):处理爬取到的数据(如清洗、去重、存入数据库);
- 中间件(Middleware):拦截请求 / 响应,添加代理、修改请求头(应对反爬)。
2. 进阶功能:从 "能爬" 到 "爬得好"
(1)XPath 解析:比 CSS 选择器更强大
Scrapy 内置Selector,支持 XPath(XML 路径语言),能更灵活地定位深层数据,尤其适合复杂 HTML。
python
运行
# 爬虫文件(example_spider.py)
import scrapy
from scrapy.http import Request
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["example.com"] # 限制爬取域名,避免爬到其他网站
start_urls = ["https://www.example.com/product-list"] # 起始URL
def parse(self, response):
# 1. 用XPath提取所有商品链接(//表示全局查找,@href获取属性)
product_urls = response.xpath('//div[@class="product-item"]/a/@href').getall()
for url in product_urls:
# 2. 发送请求爬取商品详情页(callback指定解析详情页的函数)
yield Request(url=response.urljoin(url), callback=self.parse_detail)
# 3. 爬取下一页(提取下一页链接,继续发送请求)
next_page = response.xpath('//a[@class="next-page"]/@href').get()
if next_page:
yield Request(url=response.urljoin(next_page), callback=self.parse)
# 解析商品详情页的函数
def parse_detail(self, response):
# 用XPath提取商品名称、价格、描述
item = {} # 存储数据的字典(或定义Item类)
item["name"] = response.xpath('//h1[@class="product-name"]/text()').get().strip()
item["price"] = response.xpath('//span[@id="current-price"]/text()').get()
item["desc"] = response.xpath('//div[@class="product-desc"]/p/text()').getall() # getall()获取所有文本
yield item # 将数据传递给Item Pipeline
(2)Item Pipeline:数据清洗与存储
爬取到的数据常需清洗(如去除空格、去重),再存入数据库。在pipelines.py中定义处理逻辑:
python
运行
# pipelines.py
import pymongo # 需安装:pip install pymongo
class ExamplePipeline:
# 爬虫启动时执行(初始化数据库连接)
def open_spider(self, spider):
self.client = pymongo.MongoClient("mongodb://localhost:27017/")
self.db = self.client["spider_db"] # 数据库名
self.col = self.db["product_data"] # 集合名(类似表)
# 处理每一条爬取到的数据
def process_item(self, item, spider):
# 1. 数据清洗:去除商品名称的空格
item["name"] = item["name"].strip()
# 2. 数据去重:根据商品名称判断是否已存在,不存在则插入
if not self.col.find_one({"name": item["name"]}):
self.col.insert_one(dict(item))
return item
# 爬虫关闭时执行(关闭数据库连接)
def close_spider(self, spider):
self.client.close()
之后在settings.py中启用 Pipeline:ITEM_PIPELINES = {"example.pipelines.ExamplePipeline": 300}(300 是优先级,数字越小越先执行)。
(3)中间件:应对中高级反爬
在middlewares.py中添加 "请求头随机化""代理 IP 切换" 逻辑,突破 IP 封禁和请求头检测:
python
运行
# middlewares.py
import random
class RandomUserAgentMiddleware:
# 随机User-Agent列表
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/125.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Firefox/126.0"
]
# 拦截请求,添加随机User-Agent
def process_request(self, request, spider):
request.headers["User-Agent"] = random.choice(self.USER_AGENTS)
class RandomProxyMiddleware:
# 代理IP列表
PROXIES = [
"http://123.45.67.89:8080",
"https://98.76.54.32:8443"
]
# 拦截请求,添加随机代理
def process_request(self, request, spider):
proxy = random.choice(self.PROXIES)
request.meta["proxy"] = proxy
在settings.py中启用中间件:
python
运行
DOWNLOADER_MIDDLEWARES = {
"example.middlewares.RandomUserAgentMiddleware": 543,
"example.middlewares.RandomProxyMiddleware": 544,
}
3. 避坑技巧
- 并发控制 :默认并发数较高,易被服务器检测,在
settings.py中设置CONCURRENT_REQUESTS = 5(5 个并发请求); - 下载延迟 :添加
DOWNLOAD_DELAY = 2(每个请求间隔 2 秒),模拟人类浏览速度; - 日志查看 :启用日志(
LOG_LEVEL = "INFO"),通过日志排查 "请求失败""数据提取为空" 的原因。
三大库对比与选择指南
| 维度 | Requests | Beautiful Soup | Scrapy |
|---|---|---|---|
| 核心定位 | 发送 HTTP 请求 | 解析 HTML 数据 | 工程化爬虫框架 |
| 学习成本 | 低(1 小时上手) | 低(2 小时上手) | 中(1-2 天掌握核心) |
| 并发能力 | 弱(需手动实现多线程) | 无(仅解析) | 强(内置高并发) |
| 适用场景 | 单页面 / 接口爬取 | 静态 HTML 解析 | 多页面 / 整站 / 分布式爬取 |
| 典型搭配 | 单独使用或 + Beautiful Soup | 必须搭配 Requests/Scrapy | 单独使用(内置解析) |
选择逻辑:
- 快速验证需求 (如爬取 1 个接口数据):直接用
Requests; - 静态页面数据提取 (如爬取新闻标题 + 内容):
Requests + Beautiful Soup; - 长期维护 / 大量数据 (如爬取整站商品、定时爬取):
Scrapy