什么是 Scrapling?
Scrapling 是一个功能强大的自适应网页抓取框架,专为现代网络环境设计。它能帮你从任何网站轻松提取数据,无论是简单的产品价格,还是复杂的动态内容。从单次请求到大规模并发爬取,Scrapling 都能优雅处理,让你的数据获取工作变得前所未有的简单。
实际应用场景:
- 电商价格监控:自动追踪竞争对手的价格变化
- 新闻聚合:从多个新闻网站收集最新报道
- 数据分析:为机器学习项目收集训练数据
- 市场研究:分析行业趋势和用户评论
- 内容备份:保存重要的网页信息
为什么选择 Scrapling?三大核心优势
1. 智能自适应 - 告别网站改版的烦恼
网站经常改版,传统爬虫的CSS选择器很容易失效。Scrapling的解析器能"学习"你的目标数据特征,即使网站结构大变,也能自动重新定位到正确元素。
实际案例 :假设你正在监控某电商网站的价格,原来使用 .price选择器。某天网站改版,价格元素变成了 .product-price。传统爬虫会立即失效,而Scrapling能自动找到相似元素,让你的爬虫继续工作。
2. 反爬虫克星 - 绕过主流防护系统
许多网站使用Cloudflare、Akamai等防护系统,普通爬虫很容易被识别和封禁。Scrapling内置先进的指纹伪装和浏览器仿真技术,能让你像真实用户一样访问网站。
对比传统方法:
传统爬虫:容易被识别 → 频繁被封IP → 需要复杂代理池
Scrapling:模仿真实浏览器 → 绕过检测 → 稳定获取数据
3. 无缝扩展 - 从简单到复杂平滑过渡
开始可能只需要抓取几个页面,但随着需求增长,你需要处理更多页面、更复杂逻辑。Scrapling的设计让你无需重写代码,就能平滑过渡到高级功能。
成长路径:
第1周:单页面抓取(几行代码)
第1个月:多页面并发抓取
第3个月:分布式爬虫 + 自动代理切换
详细实例教程
实例1:快速上手 - 抓取电商网站商品信息
让我们从一个实际的电商网站HTML开始。假设我们有如下网页结构:
html
<!-- 电商网站商品列表页示例 -->
<html>
<head>
<title>电子产品商店</title>
</head>
<body>
<div class="product-list">
<div class="product-item" data-id="101">
<a href="/products/wireless-headphones-pro">
<img src="headphones.jpg" alt="无线耳机">
<h3 class="product-name">无线耳机专业版</h3>
</a>
<div class="price-section">
<span class="current-price">¥129.99</span>
<span class="original-price" style="text-decoration: line-through;">¥199.99</span>
<span class="discount">-35%</span>
</div>
<div class="rating" data-score="4.5">
<span class="stars">★★★★☆</span>
<span class="review-count">(128 评价)</span>
</div>
<div class="stock-status">
<span class="in-stock">有货</span>
<span class="delivery">明天送达</span>
</div>
</div>
<div class="product-item" data-id="102">
<a href="/products/smart-watch-2024">
<img src="watch.jpg" alt="智能手表">
<h3 class="product-name">智能手表 2024 款</h3>
</a>
<div class="price-section">
<span class="current-price">¥299.99</span>
</div>
<div class="rating" data-score="4.2">
<span class="stars">★★★★☆</span>
<span class="review-count">(89 评价)</span>
</div>
<div class="stock-status">
<span class="out-of-stock">暂时缺货</span>
</div>
</div>
</div>
</body>
</html>
使用Scrapling抓取这个页面的商品信息:
python
from scrapling.fetchers import StealthyFetcher
from scrapling.parser import Parser
import json
# 1. 获取页面(自动绕过反爬虫)
# 假设目标网站是:https://www.example-shop.com/products
page = StealthyFetcher.fetch(
'https://www.example-shop.com/products',
headless=True, # 使用无头浏览器
network_idle=True # 等待页面完全加载
)
# 2. 解析商品列表
products = page.css('.product-item', auto_save=True)
# 3. 提取详细信息
all_products = []
for product in products:
# 清理和提取价格
price_element = product.css('.current-price::text').get()
price = price_element.strip() if price_element else "价格未找到"
# 如果有原价,计算折扣
original_element = product.css('.original-price::text').get()
original_price = original_element.strip() if original_element else None
data = {
'product_id': product.attr('data-id'), # 获取data-id属性
'name': product.css('.product-name::text').get().strip(),
'price': price,
'original_price': original_price,
'discount': product.css('.discount::text').get(),
'rating': product.css('.rating').attr('data-score'), # 获取data-score属性
'review_count': product.css('.review-count::text').get(),
'url': product.css('a').attr('href'), # 获取链接
'in_stock': '缺货' not in product.css('.stock-status').text() # 检查库存状态
}
all_products.append(data)
print(f"已获取: {data['name']} - {data['price']}")
# 4. 保存到JSON文件
with open('products.json', 'w', encoding='utf-8') as f:
json.dump(all_products, f, indent=2, ensure_ascii=False)
print(f"总共获取了 {len(all_products)} 个商品")
无头浏览器(Headless Browser)是一种没有图形用户界面(GUI)的网页浏览器。它具备完整浏览器的核心功能(如解析HTML、执行JavaScript、渲染页面、处理网络请求等),但无需显示窗口,所有操作均在后台通过程序控制。在Web抓取和自动化测试中,无头浏览器常用于以下场景:
- 抓取动态加载的网页:许多现代网站依赖JavaScript异步加载内容,无头浏览器能像普通浏览器一样执行脚本并渲染出完整页面,使抓取工具可提取动态生成的数据。
- 绕过反爬机制:无头浏览器可模拟真实用户行为(如鼠标移动、点击、滚动),并通过伪装浏览器指纹(如User-Agent、屏幕分辨率)来降低被识别为机器人的风险。
- 自动化测试:开发者可用其测试网页功能、性能或截图,无需人工干预界面。
在Scrapling框架中,StealthyFetcher默认启用无头模式(headless=True),配合network_idle=True(等待页面网络活动空闲)可确保动态内容完全加载后再进行解析。这尤其适用于需要处理JavaScript渲染或对抗反爬措施的网站。
输出结果示例:
json
[
{
"product_id": "101",
"name": "无线耳机专业版",
"price": "¥129.99",
"original_price": "¥199.99",
"discount": "-35%",
"rating": "4.5",
"review_count": "(128 评价)",
"url": "/products/wireless-headphones-pro",
"in_stock": true
},
{
"product_id": "102",
"name": "智能手表 2024 款",
"price": "¥299.99",
"original_price": null,
"discount": null,
"rating": "4.2",
"review_count": "(89 评价)",
"url": "/products/smart-watch-2024",
"in_stock": false
}
]
实例2:自适应抓取 - 应对网站改版
假设几周后,网站改版了HTML结构:
html
<!-- 改版后的网站HTML -->
<html>
<head>
<title>电子产品商店 - 新版</title>
</head>
<body>
<section id="product-grid">
<article class="product-card" data-product-id="101">
<a href="/p/wireless-headphones-pro">
<figure>
<img src="headphones-v2.jpg" alt="专业无线耳机">
<figcaption>
<h2 class="item-title">无线耳机专业版 2024</h2>
</figcaption>
</figure>
</a>
<div class="pricing">
<div class="current">¥119.99</div>
<div class="old">¥199.99</div>
<span class="save-badge">节省 40%</span>
</div>
<div class="reviews" rating-value="4.5">
评分: ★★★★☆
<small>128 条评价</small>
</div>
<div class="availability">
<span class="status available">库存充足</span>
<span class="shipping">免费次日达</span>
</div>
</article>
<article class="product-card" data-product-id="102">
<a href="/p/smart-watch-2024">
<figure>
<img src="watch-v2.jpg" alt="新款智能手表">
<figcaption>
<h2 class="item-title">智能手表 2024 旗舰款</h2>
</figcaption>
</figure>
</a>
<div class="pricing">
<div class="current">¥279.99</div>
</div>
<div class="reviews" rating-value="4.2">
评分: ★★★★☆
<small>89 条评价</small>
</div>
<div class="availability">
<span class="status out-of-stock">补货中</span>
</div>
</article>
</section>
</body>
</html>
注意:CSS类名已经完全不同了!传统爬虫会完全失效。但Scrapling能自适应:
python
from scrapling.fetchers import StealthyFetcher
import json
import time
# 第一次运行:学习并保存元素特征
page = StealthyFetcher.fetch('https://news.example.com/latest')
articles = page.css('.news-article', auto_save=True) # auto_save是关键!
data = []
for article in articles[:5]: # 先抓取5条测试
item = {
'title': article.css('h2::text').get(),
'summary': article.css('.summary::text').get(),
'timestamp': article.css('time::text').get()
}
data.append(item)
time.sleep(0.5) # 礼貌延迟
# 保存数据
with open('news_data.json', 'w') as f:
json.dump(data, f, indent=2)
# 几周后网站改版了,HTML结构变了
# 传统爬虫会失败,但Scrapling能自适应
# 新版网站结构完全不同
page_new = StealthyFetcher.fetch('https://news.example.com/latest-v2')
# 使用adaptive=True来自动寻找相似元素
articles_adaptive = page_new.css('.news-article', adaptive=True)
if len(articles_adaptive) > 0:
print(f"✅ 自适应成功!仍然找到了 {len(articles_adaptive)} 篇文章!")
# 尝试用原来的选择器获取标题
for article in articles_adaptive[:3]:
title = article.css('h2::text').get()
if title:
print(f" 📰 标题: {title}")
else:
# 自动寻找类似的元素
possible_title = article.find_similar('h2')
if possible_title:
print(f" 🔍 找到类似标题: {possible_title.text()[:50]}...")
else:
print("⚠️ 没有找到文章,可能需要重新训练模型或使用新的选择器")
# 也可以手动寻找类似元素
print("\n🔍 手动寻找相似元素示例:")
page_new.css('.news-article', auto_save=True) # 重新学习新结构
实例3:完整爬虫项目 - 抓取多页面数据
让我们爬取一个博客网站的多个分类页面:
html
<!-- 博客列表页 example-blog.com/tech -->
<!DOCTYPE html>
<html>
<body>
<div class="container">
<h1>技术文章</h1>
<div class="article-list">
<article class="post" data-post-id="1">
<h2 class="post-title">
<a href="/tech/python-web-scraping-2024">2024年Python网页抓取完整指南</a>
</h2>
<div class="post-meta">
<span class="author">作者: 张三</span>
<span class="date">发布日期: 2024-01-15</span>
<span class="category">分类: <a href="/category/python">Python</a></span>
</div>
<div class="excerpt">
学习使用Python进行现代网页抓取的最佳实践...
</div>
<div class="tags">
<a href="/tag/web-scraping" class="tag">网页抓取</a>
<a href="/tag/python" class="tag">Python</a>
<a href="/tag/tutorial" class="tag">教程</a>
</div>
<div class="stats">
<span class="views">阅读: 1,234</span>
<span class="likes">👍 45</span>
<span class="comments">💬 12</span>
</div>
</article>
<article class="post" data-post-id="2">
<h2 class="post-title">
<a href="/tech/ai-trends-2024">2024年AI发展趋势预测</a>
</h2>
<div class="post-meta">
<span class="author">作者: 李四</span>
<span class="date">发布日期: 2024-01-10</span>
<span class="category">分类: <a href="/category/ai">人工智能</a></span>
</div>
<div class="excerpt">
2024年人工智能领域最值得关注的趋势...
</div>
<div class="tags">
<a href="/tag/ai" class="tag">AI</a>
<a href="/tag/machine-learning" class="tag">机器学习</a>
</div>
<div class="stats">
<span class="views">阅读: 2,345</span>
<span class="likes">👍 78</span>
<span class="comments">💬 23</span>
</div>
</article>
</div>
<!-- 分页 -->
<div class="pagination">
<a href="/tech/page/1" class="active">1</a>
<a href="/tech/page/2">2</a>
<a href="/tech/page/3">3</a>
<a href="/tech/page/2" class="next">下一页</a>
</div>
</div>
</body>
</html>
完整爬虫代码:
python
from scrapling.spiders import Spider, Response
from urllib.parse import urljoin, urlparse
import json
import csv
from datetime import datetime
import re
class BlogSpider(Spider):
name = "blog_spider"
# 起始URL列表 - 不同分类
start_urls = [
"https://example-blog.com/tech",
"https://example-blog.com/programming",
"https://example-blog.com/ai-ml"
]
def __init__(self, max_pages=3):
super().__init__()
self.max_pages = max_pages
self.articles = []
self.visited_urls = set()
# 配置爬虫
self.concurrent_requests = 2 # 并发请求数
self.download_delay = 2.0 # 礼貌延迟2秒
self.domain = "example-blog.com"
async def parse(self, response: Response):
"""解析文章列表页"""
current_url = str(response.url)
# 避免重复爬取
if current_url in self.visited_urls:
return
self.visited_urls.add(current_url)
print(f"🔍 正在抓取页面: {current_url}")
# 提取当前分类
category_match = re.search(r'/(tech|programming|ai-ml)', current_url)
category = category_match.group(1) if category_match else "unknown"
# 提取文章列表
articles = response.css('.post')
print(f" 找到 {len(articles)} 篇文章")
for article in articles:
article_data = {
'category': category,
'post_id': article.attr('data-post-id'),
'title': article.css('.post-title a::text').get(),
'url': urljoin(current_url, article.css('.post-title a').attr('href')),
'author': self.extract_author(article.css('.post-meta').text()),
'publish_date': self.extract_date(article.css('.post-meta').text()),
'excerpt': article.css('.excerpt::text').get(),
'tags': [tag.text() for tag in article.css('.tag')],
'views': self.extract_number(article.css('.views::text').get()),
'likes': self.extract_number(article.css('.likes::text').get()),
'comments': self.extract_number(article.css('.comments::text').get()),
'source_url': current_url,
'crawl_time': datetime.now().isoformat()
}
# 清理数据
article_data = self.clean_data(article_data)
self.articles.append(article_data)
# 如果需要获取文章详情,可以发起新请求
yield {
'type': 'request',
'url': article_data['url'],
'callback': self.parse_article_detail,
'meta': {'base_info': article_data}
}
# 处理分页(最多爬取max_pages页)
if len(self.visited_urls) < self.max_pages:
next_page = response.css('.pagination a.next').attr('href')
if next_page:
next_url = urljoin(current_url, next_page)
yield {
'type': 'request',
'url': next_url,
'callback': self.parse
}
async def parse_article_detail(self, response: Response):
"""解析文章详情页"""
base_info = response.meta['base_info']
# 提取文章正文
content_elements = response.css('.article-content p::text').getall()
full_content = ' '.join([p.strip() for p in content_elements])
# 提取阅读时间
read_time_match = re.search(r'(\d+)\s*分钟阅读', response.text)
read_time = read_time_match.group(1) if read_time_match else None
# 更新文章信息
base_info.update({
'full_content': full_content[:1000] + '...' if len(full_content) > 1000 else full_content,
'read_time_minutes': read_time,
'word_count': len(full_content.split()),
'has_images': len(response.css('.article-content img')) > 0,
'has_code_blocks': len(response.css('pre code')) > 0,
'detail_crawled': True
})
print(f" ✅ 已获取详情: {base_info['title'][:30]}...")
return base_info
def extract_author(self, text):
"""从meta文本中提取作者"""
match = re.search(r'作者[::]\s*([^\s]+)', text)
return match.group(1) if match else None
def extract_date(self, text):
"""从meta文本中提取日期"""
match = re.search(r'发布日期[::]\s*([\d-]+)', text)
return match.group(1) if match else None
def extract_number(self, text):
"""从文本中提取数字"""
if not text:
return 0
match = re.search(r'(\d+,?\d*)', text)
if match:
return int(match.group(1).replace(',', ''))
return 0
def clean_data(self, data):
"""清理数据"""
for key, value in data.items():
if isinstance(value, str):
data[key] = value.strip()
return data
def save_results(self, format='json'):
"""保存结果"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
if format == 'json':
filename = f'blog_articles_{timestamp}.json'
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.articles, f, indent=2, ensure_ascii=False)
print(f"✅ 数据已保存到 {filename},共 {len(self.articles)} 篇文章")
elif format == 'csv':
filename = f'blog_articles_{timestamp}.csv'
if self.articles:
# 获取所有字段
fieldnames = set()
for article in self.articles:
fieldnames.update(article.keys())
fieldnames = sorted(fieldnames)
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(self.articles)
print(f"✅ CSV 数据已保存到 {filename}")
return filename
def print_summary(self):
"""打印统计摘要"""
if not self.articles:
print("没有找到文章")
return
print(f"\n📊 爬取统计:")
print(f" 总文章数: {len(self.articles)}")
# 按分类统计
categories = {}
for article in self.articles:
cat = article.get('category', 'unknown')
categories[cat] = categories.get(cat, 0) + 1
print(f" 分类分布:")
for cat, count in categories.items():
print(f" {cat}: {count} 篇")
# 阅读量统计
if any(a.get('views') for a in self.articles):
total_views = sum(a.get('views', 0) for a in self.articles)
avg_views = total_views / len(self.articles)
print(f" 总阅读量: {total_views:,}")
print(f" 平均阅读量: {avg_views:,.0f}")
# 最新文章
dated_articles = [a for a in self.articles if a.get('publish_date')]
if dated_articles:
dated_articles.sort(key=lambda x: x.get('publish_date', ''), reverse=True)
print(f" 最新文章: {dated_articles[0]['title'][:30]}... ({dated_articles[0]['publish_date']})")
# 运行爬虫
if __name__ == "__main__":
spider = BlogSpider(max_pages=2) # 每个分类最多爬2页
print("🚀 开始爬取博客网站...")
spider.start()
print("\n📁 正在保存数据...")
json_file = spider.save_results('json')
csv_file = spider.save_results('csv')
spider.print_summary()
# 也可以流式处理
# async for item in spider.stream():
# print(f"实时获取: {item['title'][:30]}...")
核心功能详解与实例
智能元素选择技巧
假设有以下HTML页面:
html
<!DOCTYPE html>
<html>
<body>
<!-- 多种选择器示例 -->
<div id="content">
<h1 class="main-title" data-testid="page-title">欢迎来到示例页面</h1>
<!-- 简单选择器 -->
<div class="product" id="prod-123">
<h2>产品名称 A</h2>
<p class="price">¥99.99</p>
<p class="description">这是一个很好的产品</p>
<div class="tags">
<span class="tag popular">热门</span>
<span class="tag new">新品</span>
</div>
</div>
<!-- 嵌套结构 -->
<div class="product" id="prod-124">
<h2>产品名称 B</h2>
<p class="price">
<span class="current">¥149.99</span>
<del class="original">¥199.99</del>
</p>
<p class="description">另一个产品描述</p>
</div>
<!-- 列表 -->
<ul class="features">
<li>功能 1</li>
<li>功能 2</li>
<li>功能 3</li>
</ul>
<!-- 表格 -->
<table class="pricing-table">
<thead>
<tr>
<th>套餐</th>
<th>价格</th>
<th>周期</th>
</tr>
</thead>
<tbody>
<tr>
<td>基础版</td>
<td>¥29/月</td>
<td>按月</td>
</tr>
<tr>
<td>专业版</td>
<td>¥99/月</td>
<td>按月</td>
</tr>
</tbody>
</table>
<!-- 隐藏数据 -->
<div data-product='{"id": 125, "name": "隐藏产品", "stock": 50}'></div>
</div>
</body>
</html>
使用多种选择器提取数据:
py
from scrapling.fetchers import Fetcher
import re
# 获取页面
page = Fetcher.fetch('https://example.com/products')
# 1. 基本的CSS选择器
main_title = page.css('h1.main-title::text').get()
print(f"主标题: {main_title}")
# 2. 获取属性
test_id = page.css('h1').attr('data-testid')
print(f"测试ID: {test_id}")
# 3. 获取所有匹配项
all_products = page.css('.product')
print(f"找到 {len(all_products)} 个产品")
# 4. 遍历和提取详细数据
for i, product in enumerate(all_products, 1):
name = product.css('h2::text').get()
price = product.css('.price::text').get()
# 5. 复杂的CSS选择器
current_price = product.css('.price .current::text').get()
original_price = product.css('.price .original::text').get()
# 6. 获取标签列表
tags = [tag.text() for tag in product.css('.tag')]
print(f"\n产品 {i}: {name}")
print(f" 价格: {price or current_price}")
if original_price:
print(f" 原价: {original_price}")
if tags:
print(f" 标签: {', '.join(tags)}")
# 7. XPath选择器(更强大)
# 找到第二个产品
second_product = page.xpath('(//div[@class="product"])[2]').get()
print(f"\n第二个产品: {second_product[:100]}...")
# 8. 正则表达式匹配
# 找到所有价格数字
price_matches = page.find_all(re.compile(r'¥[\d,]+(?:.\d{2})?'))
print(f"\n所有价格: {[m.text() for m in price_matches]}")
# 9. 文本内容搜索
popular_items = page.find_all(text="热门")
print(f"热门产品: {len(popular_items)} 个")
# 10. 从data属性中提取JSON
hidden_product_div = page.css('div[data-product]').first()
if hidden_product_div:
product_data = hidden_product_div.attr('data-product')
print(f"隐藏产品数据: {product_data}")
# 11. 表格数据提取
table_rows = page.css('.pricing-table tbody tr')
for row in table_rows:
cols = row.css('td')
if len(cols) >= 3:
plan = cols[0].text()
price = cols[1].text()
period = cols[2].text()
print(f"套餐: {plan}, 价格: {price}, 周期: {period}")
# 12. 高级CSS伪类
# 获取第一个产品的描述
first_desc = page.css('.product:first-child .description::text').get()
print(f"\n第一个产品描述: {first_desc}")
# 13. 兄弟选择器
# 获取所有功能列表项
features = page.css('.features li::text').getall()
print(f"所有功能: {features}")
# 14. 组合选择器
# 获取有original价格的商品
discounted_products = page.css('.product:has(.original)')
print(f"打折商品数量: {len(discounted_products)}")
# 15. 获取HTML源码
product_html = page.css('.product').first().html()
print(f"\n第一个产品的HTML结构:\n{product_html[:200]}...")
会话管理与状态保持
让我们模拟一个需要登录的网站:
html
<!-- 登录页面 login.html -->
<!DOCTYPE html>
<html>
<body>
<form id="login-form" action="/login" method="POST">
<input type="email" name="email" placeholder="邮箱" required>
<input type="password" name="password" placeholder="密码" required>
<input type="hidden" name="csrf_token" value="abc123xyz">
<button type="submit">登录</button>
</form>
</body>
</html>
<!-- 登录后的仪表板 dashboard.html -->
<!DOCTYPE html>
<html>
<body>
<div class="user-info">
<h1>欢迎回来,张三!</h1>
<p>您的会员等级: <span class="level">黄金会员</span></p>
</div>
<div class="dashboard">
<div class="order-history">
<h2>最近订单</h2>
<ul>
<li>订单 #001 - ¥199.99 - 2024-01-15</li>
<li>订单 #002 - ¥299.99 - 2024-01-10</li>
</ul>
</div>
</div>
</body>
</html>
使用会话保持登录状态:
py
from scrapling.fetchers import StealthySession
import time
# 创建会话(保持cookies和状态)
session = StealthySession(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
headers={
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
}
)
# 1. 访问登录页面获取CSRF令牌
login_page = session.get('https://example.com/login')
csrf_token = login_page.css('input[name="csrf_token"]').attr('value')
print(f"获取到CSRF令牌: {csrf_token}")
# 2. 准备登录数据
login_data = {
'email': 'your_email@example.com',
'password': 'your_password',
'csrf_token': csrf_token
}
# 3. 提交登录表单
print("正在登录...")
response = session.post(
'https://example.com/login',
data=login_data,
headers={
'Referer': 'https://example.com/login',
'Content-Type': 'application/x-www-form-urlencoded',
}
)
# 4. 检查登录是否成功
if "欢迎回来" in response.text or "dashboard" in response.url:
print("✅ 登录成功!")
# 5. 访问需要登录的页面
dashboard = session.get('https://example.com/dashboard')
# 提取用户信息
username = dashboard.css('.user-info h1::text').get()
membership_level = dashboard.css('.level::text').get()
print(f"用户: {username}")
print(f"会员等级: {membership_level}")
# 6. 访问订单历史
orders_page = session.get('https://example.com/orders')
# 提取订单信息
orders = []
order_items = orders_page.css('.order-history li')
for order in order_items:
order_text = order.text()
# 解析订单文本
import re
match = re.search(r'订单\s*#(\d+)\s*-\s*¥([\d.]+)\s*-\s*(\d{4}-\d{2}-\d{2})', order_text)
if match:
orders.append({
'order_id': match.group(1),
'amount': float(match.group(2)),
'date': match.group(3)
})
print(f"找到 {len(orders)} 个订单:")
for order in orders:
print(f" 订单 #{order['order_id']}: ¥{order['amount']} - {order['date']}")
# 7. 访问其他需要登录的页面
profile_page = session.get('https://example.com/profile')
settings_page = session.get('https://example.com/settings')
print(f"已访问个人资料页面: {len(profile_page.text)} 字符")
print(f"已访问设置页面: {len(settings_page.text)} 字符")
# 8. 保存会话状态(可选)
session.save_cookies('session_cookies.json')
print("会话状态已保存到 session_cookies.json")
else:
print("❌ 登录失败")
print(f"响应状态: {response.status_code}")
print(f"响应内容: {response.text[:500]}...")
# 9. 稍后恢复会话
print("\n重新加载会话...")
new_session = StealthySession()
new_session.load_cookies('session_cookies.json')
# 会话状态仍然有效
profile_again = new_session.get('https://example.com/profile')
if "欢迎回来" in profile_again.text:
print("✅ 会话恢复成功!")
else:
print("❌ 会话已过期")
# 10. 会话统计
print(f"\n会话统计:")
print(f" 总请求数: {session.request_count}")
print(f" 成功请求: {session.successful_requests}")
print(f" 失败请求: {session.failed_requests}")
print(f" 平均响应时间: {session.average_response_time:.2f}秒")
# 11. 清除会话
session.clear()
print("\n会话已清除")
实用技巧和高级功能
处理JavaScript动态加载内容
有些网站使用JavaScript动态加载内容:
html
<!-- 初始HTML(内容为空) -->
<div id="product-list">
<!-- 内容由JavaScript动态加载 -->
</div>
<script>
// JavaScript动态加载数据
fetch('/api/products')
.then(response => response.json())
.then(data => {
const container = document.getElementById('product-list');
data.products.forEach(product => {
container.innerHTML += `
<div class="product" data-id="${product.id}">
<h3>${product.name}</h3>
<p class="price">$${product.price}</p>
</div>
`;
});
});
</script>
使用动态渲染:
py
from scrapling.fetchers import DynamicFetcher
import json
# 使用动态渲染获取JavaScript生成的内容
fetcher = DynamicFetcher(
headless=True, # 使用无头浏览器
wait_until='networkidle', # 等待网络空闲
timeout=30000 # 30秒超时
)
# 获取页面(会执行JavaScript)
print("正在加载动态页面...")
page = fetcher.fetch('https://dynamic-site.example.com/products')
# 等待特定元素出现
page.wait_for_selector('.product', timeout=10000)
# 现在可以获取动态生成的内容
products = page.css('.product')
print(f"找到 {len(products)} 个动态加载的产品")
for product in products[:5]:
name = product.css('h3::text').get()
price = product.css('.price::text').get()
product_id = product.attr('data-id')
print(f"产品 {product_id}: {name} - {price}")
# 执行自定义JavaScript
print("\n执行自定义JavaScript获取数据...")
result = page.execute_script("""
// 在页面上下文中执行JavaScript
const products = [];
document.querySelectorAll('.product').forEach(el => {
products.push({
id: el.getAttribute('data-id'),
name: el.querySelector('h3').innerText,
price: el.querySelector('.price').innerText
});
});
return products;
""")
print(f"通过JavaScript获取了 {len(result)} 个产品")
# 模拟用户交互
# 点击"加载更多"按钮
more_button = page.css('button.load-more').first()
if more_button:
print("正在点击'加载更多'按钮...")
more_button.click()
# 等待新内容加载
page.wait_for_selector('.product:nth-child(10)', timeout=5000)
products_after = page.css('.product')
print(f"点击后产品数量: {len(products_after)}")
# 截屏(用于调试)
page.save_screenshot('dynamic_page.png')
print("页面截图已保存到 dynamic_page.png")
伪装和反反爬虫
python
from scrapling.fetchers import StealthyFetcher
import random
import time
# 高级伪装配置
fetcher = StealthyFetcher(
# 浏览器指纹伪装
browser_fingerprint={
'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',
'viewport': {'width': 1920, 'height': 1080},
'language': 'zh-CN,zh;q=0.9,en;q=0.8',
'timezone': 'Asia/Shanghai',
'platform': 'Win32',
},
# TLS指纹伪装
tls_fingerprint='chrome_120',
# HTTP/2 支持
http2=True,
# 自动管理cookies
cookie_jar=True,
# 请求头伪装
headers={
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Upgrade-Insecure-Requests': '1',
}
)
# 访问被保护的网站
print("正在访问受保护的网站...")
try:
# 第一次访问,可能会遇到验证码
response = fetcher.fetch(
'https://protected-site.example.com',
headless=True,
# 启用浏览器自动化绕过验证码
bypass_security=True,
# 添加人类化延迟
humanize_delays=True
)
if response.status_code == 200:
print("✅ 成功绕过防护!")
print(f"页面标题: {response.css('title::text').get()}")
# 检查是否有验证码
if "captcha" in response.text.lower() or "验证码" in response.text:
print("⚠️ 检测到验证码,尝试自动处理...")
# 自动处理验证码的逻辑
# ...
else:
print(f"❌ 请求失败: {response.status_code}")
except Exception as e:
print(f"❌ 发生错误: {e}")
# 尝试备用方案
print("尝试备用方案...")
fetcher.stealth_level = 'maximum'
response = fetcher.fetch(
'https://protected-site.example.com',
headless=False, # 显示浏览器窗口(用于调试)
bypass_security=True
)
# 模拟人类行为
def human_like_delay(min_seconds=1, max_seconds=3):
"""人类化延迟"""
delay = random.uniform(min_seconds, max_seconds)
time.sleep(delay)
return delay
# 爬取多个页面,模拟人类浏览
urls_to_crawl = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
]
for i, url in enumerate(urls_to_crawl, 1):
print(f"\n[{i}/{len(urls_to_crawl)}] 访问: {url}")
# 随机延迟
delay = human_like_delay(1, 4)
print(f" 等待 {delay:.2f} 秒...")
# 随机滚动(模拟人类浏览)
scroll_position = random.randint(100, 1000)
# 获取页面
response = fetcher.fetch(
url,
# 模拟滚动
scroll_to=scroll_position,
# 随机鼠标移动
mouse_movements=True,
# 随机延迟
random_delays=True
)
if response.ok:
print(f" 成功获取 {len(response.text)} 字符")
# 随机点击(如果需要)
if random.random() > 0.7: # 30%的概率点击链接
links = response.css('a')
if links:
link_to_click = random.choice(links)
print(f" 随机点击链接: {link_to_click.text()[:30]}...")
# 实际点击逻辑...
else:
print(f" 失败: HTTP {response.status_code}")
# 页面间更长延迟
if i < len(urls_to_crawl):
page_delay = random.uniform(3, 8)
print(f" 页面间等待 {page_delay:.2f} 秒...")
time.sleep(page_delay)
print("\n✅ 爬取完成!")
User Agent(用户代理)是浏览器或爬虫程序在访问网站时,主动发送给服务器的"身份证"字符串,用于告知服务器"我是什么客户端"。
它的核心作用是让网站服务器识别访问者的设备类型、浏览器版本和操作系统,从而返回合适的网页内容(比如为手机返回移动版页面,为电脑返回桌面版)。
一个典型的User Agent字符串示例:
scss
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
这个字符串告诉服务器:访问者使用的是Windows 10系统、64位的Chrome 120浏览器。
在网页抓取中的关键作用:
- 身份伪装 :默认的Python爬虫(如使用
requests库)会暴露自己是自动化程序。设置一个常见的浏览器User Agent(如上方的Chrome示例)能让你的爬虫看起来更像普通用户,是绕过基础反爬虫检测的第一步。 - 获取正确内容:有些网站会为不同设备返回不同HTML结构。通过设置合适的User Agent,可以确保获取到你想要的页面版本(如桌面版通常包含更完整的数据)。
- 遵守网站规范:部分网站要求合法的User Agent,否则会拒绝请求。
安装和配置
基础安装
bash
# 1. 安装Python(需要3.10+)
# 访问 python.org 下载安装
# 2. 创建虚拟环境(推荐)
python -m venv scrapling_env
# Windows激活
scrapling_env\Scripts\activate
# Mac/Linux激活
source scrapling_env/bin/activate
# 3. 安装Scrapling
pip install scrapling
# 4. 验证安装
python -c "import scrapling; print(f'Scrapling版本: {scrapling.__version__}')"
完整安装
bash
# 安装所有功能
pip install "scrapling[all]"
# 安装浏览器驱动
scrapling install
# 或强制重新安装
scrapling install --force
Docker快速开始
bash
# Dockerfile
FROM pyd4vinci/scrapling:latest
# 设置工作目录
WORKDIR /app
# 复制代码
COPY . .
# 运行爬虫
CMD ["python", "my_spider.py"]
bash
# 运行容器
docker run -v $(pwd)/data:/app/data scrapling-app
常见问题解答
❓ 如何选择合适的选择器?
CSS选择器 vs XPath:
-
CSS选择器:简单、易读,适合大多数情况
py# CSS - 简单直观 titles = page.css('.product-title::text').getall() -
XPath:更强大,适合复杂选择
py# XPath - 更灵活 titles = page.xpath('//div[contains(@class, "product")]//h2/text()').getall()
选择器优先级:
- 优先使用ID和class:
#id、.class - 使用属性选择器:
[data-id="123"] - 使用文本内容:
contains(text(), "特价") - 组合使用:
.product:not(.out-of-stock)
调试技巧
py
# 1. 查看页面结构
page.save_html('debug.html') # 保存完整HTML
page.save_screenshot('debug.png') # 保存截图
# 2. 交互式调试
from scrapling.shell import start_shell
start_shell(page) # 进入交互式shell
# 3. 查看网络请求
for req in page.network_requests[:5]:
print(f"{req.method} {req.url} -> {req.status}")
# 4. 测试选择器
test_result = page.css('.product')
print(f"找到 {len(test_result)} 个匹配项")
if test_result:
print("第一个匹配的内容:")
print(test_result[0].text()[:100])
性能优化
py
# 1. 并发控制
spider = MySpider(concurrent_requests=3) # 限制并发数
# 2. 缓存结果
from scrapling.cache import FileCache
cache = FileCache(ttl=3600) # 缓存1小时
fetcher = Fetcher(cache=cache)
# 3. 批量处理
results = []
for item in spider.stream():
results.append(item)
if len(results) >= 100: # 每100条保存一次
save_to_database(results)
results = []
# 4. 内存优化
# 使用生成器避免内存爆炸
def process_items(spider):
for item in spider.stream():
yield transform_item(item) # 边获取边处理
总结
Scrapling 是一个强大而灵活的网页抓取框架,特别适合处理现代网站的复杂性。通过实际的HTML示例,你可以看到:
- 如何定位和提取数据:使用CSS选择器、XPath等多种方式
- 如何处理动态内容:通过动态渲染执行JavaScript
- 如何维护会话:保持登录状态,访问受保护页面
- 如何绕过反爬虫:使用高级伪装技术
- 如何构建完整爬虫:从简单脚本到完整项目
记住,良好的爬虫实践包括:
- 尊重网站的robots.txt
- 添加适当延迟,避免对服务器造成压力
- 处理异常和错误
- 保存进度,支持断点续传
- 遵守法律法规和网站条款
现在你已经掌握了Scrapling的核心功能,可以开始构建自己的数据抓取项目了!从简单的单页抓取开始,逐步扩展到复杂的分布式爬虫系统。