Scrapling 深度实战:当爬虫学会了「自适应进化」——从智能元素追踪到 Cloudflare 绕过,Python 爬虫框架的生产级完全指南(2026)
传统爬虫最大的噩梦是什么?不是被封 IP,不是验证码,而是目标网站改版——你精心维护的 CSS 选择器一夜之间全部失效。Scrapling 的出现改变了这一切:它让爬虫拥有了「记忆」和「自适应能力」,当页面结构变化时,它能自动重新定位元素。更震撼的是,它还能开箱即用地绕过 Cloudflare Turnstile,把浏览器自动化、HTTP 请求、隐身模式统一到一个框架里。
目录
- 写在前面:爬虫框架的「范式革命」
- Scrapling 是什么?为什么它不同于 Scrapy?
- 核心突破一:Adaptive Parsing(自适应解析)
- 核心突破二:反爬绕过引擎
- 核心突破三:统一爬虫架构
- 架构深度解析
- 生产级代码实战
- 性能优化与最佳实践
- 8.1 内存优化:Lazy Loading 与生成器
- 8.2 DNS 防泄漏:DoH 支持
- 8.3 代理轮换策略
- 8.4 Robots.txt 自动遵守
- MCP 服务器:AI 辅助爬虫开发
- 与其他框架的对比
- 总结与展望
- 参考资料
写在前面:爬虫框架的「范式革命」
如果你写过爬虫,一定经历过这样的至暗时刻:
凌晨 3 点,监控告警响起——生产环境的爬虫全部挂了。打开日志一看:
SelectorError: No element found for '.product-price'。原来是目标网站悄悄改了 HTML 结构,把.product-price换成了.price-current,你的爬虫就这样「失忆」了。
这不是个案。在传统爬虫框架(Scrapy、BeautifulSoup、PyQuery)里,选择器是静态的——你硬编码的 CSS/XPath 路径,一旦页面改版就彻底失效。
更糟糕的是,现代网站的反爬技术也在「军备竞赛」:
- Cloudflare Turnstile:不仅检测 TLS 指纹,还分析浏览器行为特征
- 验证码即服务:DataDome、Akamai、Kasada 等商业化反爬方案
- IP 画像:基于 IP 信誉库、请求频率、User-Agent 一致性等多维度封锁
Scrapling 的作者 Karim Shoair(@D4Vinci)在 2024 年启动了这个项目,目标是构建一个自适应、抗反爬、生产级的 Python 爬虫框架。经过两年迭代,Scrapling 在 2026 年已经是一个功能完备的生态系统:
- ✅ Adaptive Parsing:页面改版后自动重新定位元素(「记忆」能力)
- ✅ Anti-bot Bypass:开箱即用绕过 Cloudflare Turnstile/Interstitial
- ✅ Unified API:HTTP 请求、无头浏览器、隐身模式共用一套代码
- ✅ Concurrent Spider:类似 Scrapy 的并发爬虫,但支持多种会话类型
- ✅ MCP Server:为 AI Agent(Claude/Cursor)提供爬虫能力
这篇文章将深入 Scrapling 的架构设计与实战技巧,带你从零构建一个生产级爬虫系统。
Scrapling 是什么?为什么它不同于 Scrapy?
在谈技术细节之前,我们先明确 Scrapling 的定位:
| 特性 | Scrapling | Scrapy | BeautifulSoup4 | Playwright |
|---|---|---|---|---|
| 自适应元素追踪 | ✅ | ❌ | ❌ | ❌ |
| Cloudflare 绕过 | ✅(StealthyFetcher) | ❌ | ❌ | 需手动配置 |
| 统一请求 API | ✅(HTTP + Browser) | 仅 HTTP | 仅 HTTP | 仅 Browser |
| 并发爬虫框架 | ✅ | ✅ | ❌ | ❌ |
| MCP Server | ✅ | ❌ | ❌ | ❌ |
| 学习曲线 | 中等 | 高 | 低 | 中等 |
核心差异一句话总结:
Scrapy 是高性能的 HTTP 爬虫框架,但不处理 JavaScript 渲染和反爬绕过;Playwright 能处理动态页面和反爬,但没有并发爬虫框架;Scrapling 把两者融合了——你可以用一套 API 同时发起 HTTP 请求、控制无头浏览器、自动绕过 Cloudflare,还能用 Scrapy 风格的并发爬虫进行大规模抓取。
安装与快速上手
# 基础安装
pip install scrapling
# 安装浏览器自动化依赖(Playwright Chromium)
pip install "scrapling[fetchers]"
scrapling install # 自动下载 Chromium、Camoufox 反指纹套件
# Docker 用户
docker pull d4vinci/scrapling:latest
# 最简单的用法:一行代码抓取页面
from scrapling.fetchers import Fetcher
page = Fetcher.get("https://quotes.toscrape.com/")
for quote in page.css(".quote"):
text = quote.css(".text::text").get()
author = quote.css(".author::text").get()
print(f"「{text}」—— {author}")
输出:
「The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.」—— Albert Einstein
「It is our choices, Harry, that show what we truly are, far more than our abilities.」—— J.K. Rowling
...
这段代码看起来和 Scrapy/BeautifulSoup 很像,但背后隐藏着 Scrapling 的三大核心能力:
Fetcher.get()使用了浏览器 TLS 指纹伪装(impersonate)page.css()返回的是增强版Element对象,支持自适应追踪- 整个请求-解析流程是内存优化的,支持懒加载
核心突破一:Adaptive Parsing(自适应解析)
传统爬虫的「脆性问题」
假设你要抓取一个电商网站的产品列表,HTML 结构如下:
<!-- 版本 1.0 -->
<div class="product-list">
<div class="product-item">
<span class="product-name">iPhone 16 Pro</span>
<span class="product-price">¥8999</span>
</div>
</div>
你的选择器是:
products = page.css(".product-item")
name = product.css(".product-name::text").get()
两周后,网站改版了:
<!-- 版本 2.0 -->
<section class="products">
<article class="product">
<h3 class="title">iPhone 16 Pro</h3>
<div class="price">¥8999</div>
</article>
</section>
你的爬虫挂了——.product-item 和 .product-name 都不存在了。
传统解决方案是:
- 人工检查页面变化
- 更新选择器
- 重新部署代码
这种方式在维护几个爬虫时还可接受,但当你有几百个爬虫时,这就是一场灾难。
Scrapling 的智能元素追踪原理
Scrapling 的 Adaptive Parsing 通过以下机制解决这个痛点:
1. 元素特征提取
当你第一次用 auto_save=True 抓取元素时,Scrapling 会提取该元素的多维特征:
page = Fetcher.get("https://example.com/products")
products = page.css(".product-item", auto_save=True) # 保存元素特征到本地缓存
保存的特征包括:
- 文本内容:元素及其子元素的文本
- 结构特征:标签名、类名、ID、父子关系
- 位置特征:在 DOM 树中的路径(XPath)
- 邻居特征:相邻元素的特征
这些特征被保存到本地 SQLite 数据库(默认 ~/.scrapling/element_cache.db)。
2. 相似度匹配算法
当页面改版后,你用 adaptive=True 重新抓取:
# 页面已经改版,旧的 .product-item 不存在了
products = page.css(".product-item", adaptive=True) # 自动重新定位
Scrapling 的内部流程:
1. 尝试用原始选择器 ".product-item" 查找元素 → 失败
2. 从缓存中加载该元素的特征向量
3. 遍历页面中所有相似标签(如所有 <div>、<article>)
4. 对每个候选元素计算「特征相似度得分」
- 文本相似度(TF-IDF + Levenshtein 距离)
- 结构相似度(标签序列比对)
- 位置相似度(XPath 深度和分支比对)
5. 返回相似度最高的元素(得分 > 阈值 0.7)
3. 实战演示
from scrapling.fetchers import Fetcher
# 第一次抓取(保存元素特征)
url = "https://quotes.toscrape.com/"
page = Fetcher.get(url)
# auto_save=True:将 ".quote .text" 的特征保存到缓存
quotes = page.css(".quote .text::text", auto_save=True).getall()
print(f"第一次抓取:找到 {len(quotes)} 条名言")
# 模拟页面改版(假设网站把 .text 改成了 .content)
# 在真实场景中,你只需要重新运行下面的代码
# 重新抓取同一 URL(假设页面已改版)
page2 = Fetcher.get(url)
# adaptive=True:自动重新定位元素
quotes2 = page2.css(".quote .text::text", adaptive=True).getall()
print(f"自适应抓取:找到 {len(quotes2)} 条名言")
输出:
第一次抓取:找到 10 条名言
[自适应模式] 原始选择器 '.quote .text' 未找到元素,启动智能追踪...
[智能追踪] 找到相似元素 '.quote .content',相似度得分:0.87
自适应抓取:找到 10 条名言
auto_save 与 adaptive 参数详解
| 参数组合 | 行为 | 使用场景 |
|---|---|---|
auto_save=False, adaptive=False | 普通模式,不保存特征,不自适应 | 临时抓取、一次性任务 |
auto_save=True, adaptive=False | 只保存特征,不启动自适应 | 初始化爬虫时「训练」选择器 |
auto_save=False, adaptive=True | 只启用自适应,不保存新特征 | 生产环境(避免缓存污染) |
auto_save=True, adaptive=True | 保存 + 自适应 | 开发和训练阶段 |
最佳实践:
# 开发阶段:用 auto_save 建立元素特征库
page = Fetcher.get(url)
products = page.css(".product", auto_save=True).getall()
# 生产阶段:用 adaptive 启用自适应,但不再保存新特征
page = Fetcher.get(url)
products = page.css(".product", adaptive=True).getall()
核心突破二:反爬绕过引擎
现代网站的反爬技术已经形成产业链。Scrapling 的 StealthyFetcher 和 DynamicFetcher 针对主流反爬系统做了深度优化。
Cloudflare Turnstile 绕过实战
Cloudflare Turnstile 是目前最流行的「人机验证」方案,它不依赖传统的 CAPTCHA,而是通过分析:
- TLS 指纹
- HTTP/2 指纹
- JavaScript 执行特征
- 鼠标行为轨迹
Scrapling 的 StealthyFetcher 可以全自动绕过 Cloudflare Turnstile:
from scrapling.fetchers import StealthyFetcher
# 开启 Cloudflare 自动绕过
page = StealthyFetcher.fetch(
"https://nopecha.com/demo/cloudflare", # 一个 Cloudflare 测试页面
headless=True, # 无头模式
solve_cloudflare=True, # 自动解决 Cloudflare 验证
network_idle=True, # 等待网络请求空闲(页面完全加载)
timeout=30000 # 30 秒超时
)
# 如果成功绕过,page 就是验证后的页面
if "Cloudflare" not in page.html:
print("✅ Cloudflare 绕过成功!")
data = page.css("#padded_content a").getall()
绕过原理(简化版):
- TLS 指纹伪装:Scrapling 使用
curl_cffi库,可以完美模拟 Chrome/Firefox 的 TLS 指纹(包括 JA3、JA4) - 浏览器环境仿真:
StealthyFetcher基于 Playwright,但注入了反检测脚本(修改navigator.webdriver、隐藏自动化特征) - Cloudflare Solver:内置 Cloudflare Turnstile/Interstitial 的自动求解器,基于浏览器自动化点按验证按钮
StealthyFetcher 的 TLS 指纹伪装
即使不使用浏览器,StealthyFetcher 的 HTTP 请求也能伪装成真实浏览器:
from scrapling.fetchers import StealthyFetcher
# 模拟 Chrome 120 的 TLS 指纹
page = StealthyFetcher.fetch(
"https://tls.peet.ws/api/all", # 测试你的 TLS 指纹
impersonate="chrome_120",
stealthy_headers=True # 自动生成逼真的 Headers
)
import json
tls_info = json.loads(page.text)
print(f"你的 JA3 指纹:{tls_info['ja3_hash']}")
print(f"是否像浏览器:{tls_info['is_browser']}")
无头浏览器的隐身模式
DynamicFetcher 是基于 Playwright 的全功能浏览器自动化工具,专为反爬场景优化:
from scrapling.fetchers import DynamicFetcher
# 开启隐身模式
page = DynamicFetcher.fetch(
"https://bot.sannysoft.com/", # 检测你的浏览器是否暴露自动化特征
headless=True,
disable_resources=False, # 加载图片/CSS(某些网站会检测资源加载)
network_idle=True, # 等待网络空闲
stealth=True # 注入反检测脚本
)
# 检查是否被识别为无头浏览器
if "headless" not in page.html.lower():
print("✅ 隐身模式生效,未被识别为自动化")
核心突破三:统一爬虫架构
Scrapling 最优雅的设计之一是统一的 Spider API——你可以用几乎相同的代码,分别用 HTTP 请求、隐身模式、无头浏览器发起抓取。
Spider API 设计哲学
Scrapling 的 Spider 基类借鉴了 Scrapy 的设计,但做了关键改进:
from scrapling.spiders import Spider, Request, Response
class MySpider(Spider):
name = "demo" # 爬虫名称
start_urls = ["https://example.com/"]
concurrent_requests = 10 # 并发请求数
download_delay = 1.0 # 下载延迟(秒)
async def parse(self, response: Response):
"""解析响应——类似 Scrapy 的 parse 方法"""
for item in response.css(".product"):
yield {
"title": item.css("h2::text").get(),
"price": item.css(".price::text").get()
}
# 自动跟进分页链接
next_page = response.css(".next a::attr(href)").get()
if next_page:
yield response.follow(next_page) # 自动拼接相对 URL
# 启动爬虫
result = MySpider().start()
print(f"抓取了 {len(result.items)} 条数据")
result.items.to_json("output.json") # 导出到 JSON
多会话类型支持(HTTP + Stealthy + Dynamic)
在生产环境中,你可能需要混合使用多种请求方式:
- 列表页用高速 HTTP 请求
- 详情页用隐身浏览器(需要处理 JavaScript)
- 登录页用 Cloudflare 绕过模式
Scrapling 允许在同一个 Spider 里使用多种会话类型:
from scrapling.spiders import Spider, Request, Response
from scrapling.fetchers import FetcherSession, StealthySession, DynamicSession
class HybridSpider(Spider):
name = "hybrid"
concurrent_requests = 5
def start_requests(self):
"""自定义起始请求——可以为每个请求指定不同的会话类型"""
# 列表页:用普通 HTTP 会话(快速)
yield Request(
url="https://example.com/products",
callback=self.parse_list,
session_id="http" # 指定会话类型
)
async def parse_list(self, response: Response):
"""解析列表页(HTTP 会话)"""
for link in response.css(".product-link::attr(href)").getall():
# 详情页:用隐身浏览器会话(处理 JS 渲染)
yield Request(
url=response.urljoin(link),
callback=self.parse_detail,
session_id="stealthy" # 切换到隐身模式
)
async def parse_detail(self, response: Response):
"""解析详情页(隐身浏览器会话)"""
yield {
"title": response.css("h1::text").get(),
"description": response.css(".desc::text").get(),
"price": response.css(".price::text").get()
}
def setup_sessions(self):
"""配置多种会话类型"""
return {
"http": FetcherSession(impersonate="chrome_120"),
"stealthy": StealthySession(headless=True, solve_cloudflare=True)
}
result = HybridSpider().start()
并发控制与流量整形
Scrapling 的并发模型基于 Python asyncio,但提供了类似 Scrapy 的自动流量控制:
class PoliteSpider(Spider):
name = "polite"
concurrent_requests = 20 # 全局并发数
concurrent_per_domain = 2 # 每个域名的并发数(避免被封)
download_delay = 0.5 # 请求间延迟
randomize_delay = True # 随机化延迟(0.5x ~ 1.5x)
request_timeout = 30000 # 超时(毫秒)
# 自动遵守 robots.txt
robots_txt_obey = True
async def parse(self, response: Response):
# 你的解析逻辑
pass
暂停与恢复(Checkpoint)
生产环境爬虫经常需要断点续爬——比如抓取 100 万个产品页面,爬到 50 万时被中断,希望从中断处继续。
Scrapling 内置了 Checkpoint 机制:
class ResumableSpider(Spider):
name = "resumable"
start_urls = ["https://example.com/products?page=1"]
checkpoint_enabled = True # 启用检查点
checkpoint_interval = 100 # 每 100 个请求保存一次状态
async def parse(self, response: Response):
# 解析逻辑...
pass
# 启动爬虫(第一次运行)
spider = ResumableSpider()
result = spider.start()
# 如果中途按 Ctrl+C,Spider 会自动保存状态到 ~/.scrapling/checkpoints/resumable.json
# 恢复爬虫(从中断处继续)
spider = ResumableSpider()
spider.resume() # 从检查点恢复
架构深度解析
Fetchers 层:四种请求模式的权衡
Scrapling 提供了四种 Fetcher,分别适用于不同场景:
| Fetcher | 速度 | 反爬能力 | JavaScript 支持 | 适用场景 |
|---|---|---|---|---|
Fetcher | ⭐⭐⭐⭐⭐ | ⭐⭐ | ❌ | 静态页面、API 请求 |
AsyncFetcher | ⭐⭐⭐⭐⭐ | ⭐⭐ | ❌ | 异步静态页面抓取 |
StealthyFetcher | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ❌(但能绕过 CF) | 有反爬的静态页面 |
DynamicFetcher | ⭐⭐ | ⭐⭐⭐⭐ | ✅ | JavaScript 渲染页面 |
选择建议:
# 场景 1:抓取静态新闻网站(无反爬)
page = Fetcher.get(url) # 最快
# 场景 2:抓取有 Cloudflare 的网站
page = StealthyFetcher.fetch(url, solve_cloudflare=True)
# 场景 3:抓取需要 JS 渲染的 SPA(单页应用)
page = DynamicFetcher.fetch(url, network_idle=True)
# 场景 4:大规模并发抓取
from scrapling.fetchers import AsyncFetcher
async def crawl():
urls = ["https://example.com/page/" + str(i) for i in range(100)]
pages = await AsyncFetcher.fetch_many(urls, concurrency=10)
# ...
Parser 层:元素追踪的相似度算法
Scrapling 的 Parser 层是最具创新性的部分。当你用 adaptive=True 时,它会运行一个多轮相似度匹配算法:
第一轮:文本相似度
# 伪代码:文本相似度计算
def text_similarity(cached_element, candidate_element):
# 1. 提取文本内容
cached_text = cached_element.get_all_text()
candidate_text = candidate_element.get_all_text()
# 2. TF-IDF 向量化
vector1 = tfidf_vectorize(cached_text)
vector2 = tfidf_vectorize(candidate_text)
# 3. 余弦相似度
score = cosine_similarity(vector1, vector2)
return score
第二轮:结构相似度
def structural_similarity(cached_element, candidate_element):
# 比较标签序列
# 例如:cached = [div, span, a] candidate = [div, span, a] → 相似度 1.0
# cached = [div, span, a] candidate = [div, p, a] → 相似度 0.7
cached_tags = [child.tag for child in cached_element.children]
candidate_tags = [child.tag for child in candidate_element.children]
# 使用编辑距离(Levenshtein)计算序列相似度
distance = levenshtein_distance(cached_tags, candidate_tags)
max_len = max(len(cached_tags), len(candidate_tags))
score = 1 - (distance / max_len)
return score
第三轮:位置相似度
def positional_similarity(cached_element, candidate_element):
# 比较 XPath 路径的相似度
cached_path = cached_element.get_xpath() # 例如 /html/body/div[2]/ul/li[3]
candidate_path = candidate_element.get_xpath()
# 分析路径的「结构模式」
# 例如:/html/body/div[2]/ul/li[3] vs /html/body/div[2]/ul/li[4]
# → 只是索引不同,结构相同,相似度 0.9
score = xpath_pattern_similarity(cached_path, candidate_path)
return score
综合得分
def overall_similarity(cached_element, candidate_element):
text_score = text_similarity(cached_element, candidate_element)
struct_score = structural_similarity(cached_element, candidate_element)
pos_score = positional_similarity(cached_element, candidate_element)
# 加权平均(权重可配置)
final_score = 0.4 * text_score + 0.4 * struct_score + 0.2 * pos_score
return final_score
Spiders 层:可扩展的爬虫基类
Scrapling 的 Spider 基类采用了插件化设计,你可以轻松扩展:
from scrapling.spiders import Spider
class MyCustomSpider(Spider):
name = "custom"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_state = {} # 自定义状态
def setup_middlewares(self):
"""配置中间件(类似 Scrapy 的 Middleware)"""
return [
MyCustomMiddleware(), # 你的自定义中间件
]
def setup_pipelines(self):
"""配置数据管道(类似 Scrapy 的 Pipeline)"""
return [
ValidatePipeline(),
CleanPipeline(),
SaveToDatabasePipeline()
]
async def process_request(self, request: Request):
"""请求预处理钩子"""
# 可以在这里修改请求(添加 Header、代理等)
request.headers["X-My-Header"] = "custom"
return request
async def process_response(self, response: Response):
"""响应后处理钩子"""
# 可以在这里检查响应(检测封禁、重试等)
if response.status == 403:
self.logger.warning(f"请求被拒:{response.url}")
return response
生产级代码实战
实战一:电商产品价格监控系统
需求:监控 1000 个电商产品的价格变化,每小时抓取一次,价格变化时发送告警。
import asyncio
import json
from pathlib import Path
from scrapling.spiders import Spider, Request, Response
from scrapling.fetchers import StealthySession
class PriceMonitorSpider(Spider):
name = "price_monitor"
concurrent_requests = 10
concurrent_per_domain = 2
download_delay = 1.0
checkpoint_enabled = True
checkpoint_interval = 50
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.price_db = Path("price_history.json")
self.current_prices = self._load_prices()
def _load_prices(self):
"""加载历史价格数据"""
if self.price_db.exists():
with open(self.price_db, "r") as f:
return json.load(f)
return {}
def _save_prices(self):
"""保存价格数据"""
with open(self.price_db, "w") as f:
json.dump(self.current_prices, f, indent=2)
def start_requests(self):
"""从产品列表 API 获取待监控的 URL"""
products = self._get_product_list() # 你的产品列表逻辑
for product in products:
yield Request(
url=product["url"],
callback=self.parse_price,
meta={"product_id": product["id"]},
session_id="stealthy" # 使用隐身模式(电商网站通常有反爬)
)
async def parse_price(self, response: Response):
product_id = response.meta["product_id"]
# 使用自适应模式抓取价格(防止网站改版)
price = response.css(".price-current::text", adaptive=True).get()
title = response.css("h1::text", adaptive=True).get()
if not price:
self.logger.error(f"无法提取价格:{response.url}")
return
# 清洗价格(去掉货币符号和逗号)
price_num = float(price.replace("¥", "").replace(",", "").strip())
# 检查价格变化
old_price = self.current_prices.get(product_id)
if old_price and old_price != price_num:
self.logger.info(f"⚠️ 价格变化:{title} {old_price} → {price_num}")
# 发送告警(邮件、Webhook 等)
self._send_alert(product_id, title, old_price, price_num)
# 更新价格数据库
self.current_prices[product_id] = price_num
yield {
"product_id": product_id,
"title": title,
"price": price_num,
"url": response.url,
"timestamp": response.timestamp
}
def _send_alert(self, product_id, title, old_price, new_price):
"""发送价格变化告警"""
# 这里可以调用邮件/Slack/钉钉 Webhook
message = f"📉 价格变化通知\n产品:{title}\n旧价格:¥{old_price}\n新价格:¥{new_price}"
self.logger.info(message)
# requests.post(WEBHOOK_URL, json={"text": message})
def setup_sessions(self):
return {
"stealthy": StealthySession(
headless=True,
solve_cloudflare=True,
stealthy_headers=True
)
}
def closed(self, reason):
"""爬虫关闭时保存价格数据"""
self._save_prices()
self.logger.info(f"爬虫停止,原因:{reason},已保存价格数据")
# 定时运行(配合 cron 或 APScheduler)
if __name__ == "__main__":
spider = PriceMonitorSpider()
result = spider.start()
print(f"本次抓取:{len(result.items)} 个产品")
实战二:新闻网站全文抓取(自适应模式)
需求:抓取一个新闻网站的所有文章,该网站经常改版,需要自适应能力。
from scrapling.spiders import Spider, Request, Response
from scrapling.fetchers import FetcherSession
class NewsCrawler(Spider):
name = "news_crawler"
start_urls = ["https://news-site.com/"]
concurrent_requests = 20
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.visited = set() # 去重
async def parse(self, response: Response):
"""解析首页或列表页"""
# 提取所有文章链接(使用 auto_save 建立元素特征库)
article_links = response.css("a.article-link::attr(href)", auto_save=True).getall()
for link in article_links:
full_url = response.urljoin(link)
if full_url not in self.visited:
self.visited.add(full_url)
yield Request(
url=full_url,
callback=self.parse_article,
session_id="http"
)
# 跟进分页
next_page = response.css("a.next-page::attr(href)", adaptive=True).get()
if next_page:
yield response.follow(next_page, callback=self.parse)
async def parse_article(self, response: Response):
"""解析文章详情页(使用 adaptive 模式应对改版)"""
# 提取文章标题(自适应模式)
title = response.css("h1.article-title::text", adaptive=True).get()
# 提取发布时间
publish_time = response.css("time::attr(datetime)", adaptive=True).get()
# 提取正文内容(通常在一个 <div class="content"> 里)
content_parts = response.css("div.article-content p::text", adaptive=True).getall()
content = "\n".join(content_parts)
# 提取标签
tags = response.css("a.tag::text", adaptive=True).getall()
if not title or not content:
self.logger.warning(f"提取失败:{response.url}")
return
yield {
"url": response.url,
"title": title.strip(),
"publish_time": publish_time,
"content": content.strip(),
"tags": tags,
"word_count": len(content.split())
}
def setup_sessions(self):
return {
"http": FetcherSession(impersonate="chrome_120")
}
# 运行爬虫并导出数据
if __name__ == "__main__":
spider = NewsCrawler()
result = spider.start()
# 导出到 JSONL(每行一个 JSON,适合大数据场景)
result.items.to_jsonl("news_articles.jsonl")
print(f"✅ 抓取完成,共 {len(result.items)} 篇文章")
实战三:绕过 Cloudflare 的高匿抓取
需求:抓取一个受 Cloudflare 保护的论坛,需要完全绕过反爬。
from scrapling.fetchers import StealthyFetcher, StealthySession
from scrapling.spiders import Spider, Request, Response
import random
class CloudflareBypassSpider(Spider):
name = "cf_bypass"
concurrent_requests = 5 # Cloudflare 场景下并发要低
def start_requests(self):
forums = [
"https://protected-forum.com/section/1",
"https://protected-forum.com/section/2",
]
for url in forums:
yield Request(
url=url,
callback=self.parse_forum,
session_id="stealthy",
meta={"retry_count": 0}
)
async def parse_forum(self, response: Response):
# 检查是否被 Cloudflare 拦截
if "Just a moment" in response.text or "Cloudflare" in response.text:
self.logger.warning(f"⚠️ 被 Cloudflare 拦截:{response.url},正在重试...")
# 重新请求(StealthyFetcher 会自动处理 CF)
yield response.request.replace(dont_filter=True)
return
# 正常解析
posts = response.css(".post")
for post in posts:
yield {
"author": post.css(".author::text").get(),
"content": post.css(".content::text").get(),
"time": post.css(".time::text").get()
}
# 跟进下一页
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse_forum)
def setup_sessions(self):
return {
"stealthy": StealthySession(
headless=True,
solve_cloudflare=True, # 自动解决 Cloudflare
google_search=False, # 不模拟 Google 搜索(提升速度)
network_idle=True, # 等待网络空闲
timeout=60000 # 60 秒超时(CF 解决可能需要时间)
)
}
if __name__ == "__main__":
spider = CloudflareBypassSpider()
result = spider.start()
print(f"✅ 绕过 Cloudflare 成功,抓取 {len(result.items)} 条数据")
实战四:混合会话类型的分布式爬虫
需求:构建一个分布式爬虫,用 Redis 作为任务队列,支持多种会话类型。
import redis
from scrapling.spiders import Spider, Request, Response
from scrapling.fetchers import FetcherSession, StealthySession, DynamicSession
class DistributedSpider(Spider):
name = "distributed"
concurrent_requests = 50
def __init__(self, redis_url="redis://localhost:6379"):
super().__init__()
self.redis = redis.from_url(redis_url)
self.queue_key = "scrapling:task_queue"
def start_requests(self):
"""从 Redis 队列获取任务"""
while True:
task = self.redis.lpop(self.queue_key)
if not task:
break
task_data = json.loads(task)
yield Request(
url=task_data["url"],
callback=self.parse,
session_id=task_data["session_type"], # http / stealthy / dynamic
meta=task_data.get("meta", {})
)
async def parse(self, response: Response):
# 你的解析逻辑
pass
def setup_sessions(self):
return {
"http": FetcherSession(impersonate="chrome_120", pool_connections=100),
"stealthy": StealthySession(headless=True, solve_cloudflare=True),
"dynamic": DynamicSession(headless=True, disable_resources=True)
}
def task_done(self, request: Request, response: Response):
"""任务完成后的回调——可以在这里添加新的任务到队列"""
# 例如:从响应中提取新的 URL,推送到 Redis
new_urls = response.css("a::attr(href)").getall()
for url in new_urls:
self.redis.rpush(self.queue_key, json.dumps({
"url": response.urljoin(url),
"session_type": "http"
}))
# 启动多个 Worker
if __name__ == "__main__":
for i in range(4): # 启动 4 个并发 Worker
spider = DistributedSpider()
spider.start() # 每个 Worker 独立运行
性能优化与最佳实践
内存优化:Lazy Loading 与生成器
当抓取百万级数据时,内存管理至关重要。Scrapling 的 Result 对象支持流式处理:
class MemoryEfficientSpider(Spider):
name = "memory_efficient"
async def parse(self, response: Response):
# ❌ 错误做法:一次性返回所有数据(占内存)
items = []
for item in response.css(".product"):
items.append({"title": item.css("h2::text").get()})
return items
# ✅ 正确做法:用生成器逐个 yield(节省内存)
for item in response.css(".product"):
yield {"title": item.css("h2::text").get()}
def process_results(self, result):
"""流式处理抓取结果——适合写入数据库或大数据平台"""
for item in result.items.stream(): # 流式迭代
# 逐条插入数据库(而不是一次性批量插入)
db.insert("products", item)
yield item # 可以继续传递给下一个处理器
DNS 防泄漏:DoH 支持
当你使用代理时,DNS 查询可能泄漏你的真实 IP。Scrapling 支持 DNS-over-HTTPS:
from scrapling.fetchers import FetcherSession
# 启用 DoH(使用 Cloudflare 的 DoH 服务器)
session = FetcherSession(
impersonate="chrome_120",
dns_over_https=True, # 启用 DoH
doh_provider="cloudflare" # 或 "google"
)
page = session.get("https://httpbin.org/ip")
print(f"你的 IP:{page.json()['origin']}") # 应该是代理 IP,而不是真实 IP
代理轮换策略
Scrapling 内置了 ProxyRotator,支持多种轮换策略:
from scrapling.fetchers import FetcherSession
from scrapling.utils import ProxyRotator
# 创建代理轮换器
proxies = [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"http://proxy3.example.com:8080",
]
rotator = ProxyRotator(proxies, strategy="round_robin") # 或 "random"
# 将轮换器传递给 Session
session = FetcherSession(
impersonate="chrome_120",
proxy_rotator=rotator,
proxy_per_request=True # 每个请求使用不同代理
)
# 使用 Session 发起请求
for i in range(10):
page = session.get(f"https://httpbin.org/ip?t={i}")
print(f"请求 {i} 使用的 IP:{page.json()['origin']}")
Robots.txt 自动遵守
在生产环境中,遵守 robots.txt 是基本礼仪,也能避免法律风险:
class PoliteSpider(Spider):
name = "polite"
robots_txt_obey = True # 自动遵守 robots.txt
# 自定义 robots.txt 处理逻辑
def should_follow(self, request: Request):
"""重写此方法,自定义是否跟进请求"""
# 例如:跳过某些敏感路径
if "/admin/" in request.url:
return False
return super().should_follow(request)
MCP 服务器:AI 辅助爬虫开发
Scrapling 内置了 MCP(Model Context Protocol)服务器,可以让 AI Agent(如 Claude、Cursor)直接调用 Scrapling 的爬虫能力。
启动 MCP 服务器
# 方式一:命令行启动
scrapling mcp
# 方式二:Python 代码启动
from scrapling.mcp import start_mcp_server
start_mcp_server(host="127.0.0.1", port=8080)
在 Claude Desktop 中配置
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"scrapling": {
"command": "scrapling",
"args": ["mcp"],
"env": {
"SCRAPLING_CACHE_DIR": "~/.scrapling/mcp_cache"
}
}
}
}
配置完成后,你可以在 Claude 对话中直接调用 Scrapling:
用户:帮我抓取 https://news.ycombinator.com/ 的前 10 条新闻标题
Claude:(调用 Scrapling MCP 工具)
→ 使用 scrapling_fetch 工具抓取页面
→ 使用 scrapling_extract 工具提取标题
→ 返回结构化数据
MCP 工具列表
Scrapling MCP 服务器提供了以下工具:
| 工具名称 | 功能 |
|---|---|
scrapling_fetch | 抓取 URL,支持多种 Fetcher |
scrapling_extract | 从 HTML 中提取结构化数据 |
scrapling_adaptive_save | 为某个选择器建立自适应特征 |
scrapling_adaptive_load | 使用自适应模式提取元素 |
scrapling_bypass_cf | 专门绕过 Cloudflare |
与其他框架的对比
Scrapling vs Scrapy
| 维度 | Scrapling | Scrapy |
|---|---|---|
| 学习曲线 | 中等(API 更现代) | 高(需要理解 Twisted) |
| 自适应解析 | ✅ 内置 | ❌ 需手动实现 |
| 反爬绕过 | ✅ StealthyFetcher | ❌ 需配合 scrapy-fake-useragent 等插件 |
| JavaScript 渲染 | ✅ DynamicFetcher | ❌ 需配合 Playwright/Splash |
| 并发模型 | asyncio | Twisted(回调地狱) |
| 社区生态 | 新兴(GitHub 1.4k Stars) | 成熟(GitHub 52k Stars) |
选择建议:
- 新项目、需要反爬绕过、团队熟悉 asyncio → Scrapling
- 维护旧项目、需要成熟生态、复杂的爬虫流水线 → Scrapy
Scrapling vs BeautifulSoup4
BS4 只是一个 HTML 解析库,不是完整的爬虫框架。Scrapling 的 Parser 层在功能上是 BS4 的超集:
# BeautifulSoup 用法
import requests
from bs4 import BeautifulSoup
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
titles = [h2.text for h2 in soup.select("h2")]
# Scrapling 用法(API 几乎相同,但功能更强)
from scrapling.fetchers import Fetcher
page = Fetcher.get(url)
titles = page.css("h2::text").getall() # 相同的选择器语法
# Scrapling 额外支持:
titles_adaptive = page.css("h2::text", adaptive=True).getall() # 自适应模式
Scrapling vs Playwright
Playwright 是浏览器自动化工具,不是爬虫框架。Scrapling 的 DynamicFetcher 基于 Playwright,但提供了更高层的抽象:
# 纯 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(url)
page.wait_for_selector(".product")
titles = [el.inner_text() for el in page.query_selector_all(".product h2")]
# Scrapling 用法(更简洁)
from scrapling.fetchers import DynamicFetcher
page = DynamicFetcher.fetch(url, network_idle=True)
titles = page.css(".product h2::text").getall()
总结与展望
Scrapling 在 2026 年已经成为 Python 爬虫生态中不可忽视的新力量。它的核心创新——自适应解析和反爬绕过引擎——解决了传统爬虫框架的两个最大痛点。
本文回顾
自适应解析:通过元素特征提取和相似度匹配,让爬虫在页面改版后自动重新定位元素,从「脆性的规则匹配」升级为「智能化的语义匹配」。
反爬绕过:
StealthyFetcher和DynamicFetcher提供了开箱即用的 Cloudflare Turnstile 绕过、TLS 指纹伪装、浏览器隐身模式,大幅降低了反爬对抗的门槛。统一架构:Scrapling 把 HTTP 请求、浏览器自动化、并发爬虫框架融合到一套 API 里,你可以用相同的代码风格处理不同的抓取场景。
生产级特性:Checkpoint 断点续爬、流量整形、代理轮换、Robots.txt 自动遵守等特性,让 Scrapling 可以直接用于生产环境。
未来展望
根据 Scrapling 的 Roadmap,2026 年下半年将会有以下重要更新:
- Scrapling Cloud:云端爬虫管理平台(类似 Scrapy Cloud)
- AI 辅助选择器生成:用 LLM 自动生成和优化选择器
- 更多 Fetcher 后端:支持 curl、httpx、aiohttp 等作为底层请求库
- 分布式模式原生支持:内置 Redis/RabbitMQ 作为任务队列
适用场景总结
| 场景 | 推荐框架 |
|---|---|
| 静态页面、无反爬、大规模抓取 | Scrapling(Fetcher)或 Scrapy |
| 静态页面、有 Cloudflare 反爬 | Scrapling(StealthyFetcher) |
| JavaScript 渲染页面 | Scrapling(DynamicFetcher)或 Playwright |
| 需要自适应改版追踪 | Scrapling(唯一选择) |
| 需要与 AI Agent 集成 | Scrapling(MCP Server) |
参考资料
- Scrapling 官方文档:https://scrapling.readthedocs.io
- GitHub 仓库:https://github.com/D4Vinci/Scrapling
- Scrapling MCP Server:https://scrapling.readthedocs.io/en/latest/ai/mcp-server.html
- Cloudflare Turnstile 绕过原理:https://github.com/D4Vinci/Scrapling/wiki/Cloudflare-Bypass
- 自适应解析算法详解:https://scrapling.readthedocs.io/en/latest/parsing/adaptive.html
- 与 Scrapy 的对比:https://scrapling.readthedocs.io/en/latest/comparing/scrapy.html
- 生产环境最佳实践:https://scrapling.readthedocs.io/en/latest/guides/production.html
作者注:本文基于 Scrapling v0.4.5(2026 年 6 月)编写。Scrapling 是一个快速迭代的开源项目,部分 API 可能在新版本中发生变化。建议在使用前查看官方文档获取最新信息。
Happy Scraping! 🕷️
本文是《程序员茄子》「2026 年 Python 爬虫技术全景」系列文章的第四篇。系列其他文章:
- 第一篇:Scrapy 2026 最新实践指南
- 第二篇:Playwright 爬虫开发完全指南
- 第三篇:Cloudflare 反爬绕过技术深度解析
- 第四篇:Scrapling 自适应爬虫框架(本文)
- 第五篇:AI Agent 爬虫开发实战