Python Scrapling:小白也能轻松掌握的现代网页抓取工具

什么是 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浏览器。

在网页抓取中的关键作用:

  1. 身份伪装 :默认的Python爬虫(如使用requests库)会暴露自己是自动化程序。设置一个常见的浏览器User Agent(如上方的Chrome示例)能让你的爬虫看起来更像普通用户,是绕过基础反爬虫检测的第一步。
  2. 获取正确内容:有些网站会为不同设备返回不同HTML结构。通过设置合适的User Agent,可以确保获取到你想要的页面版本(如桌面版通常包含更完整的数据)。
  3. 遵守网站规范:部分网站要求合法的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()

选择器优先级

  1. 优先使用ID和class:#id.class
  2. 使用属性选择器:[data-id="123"]
  3. 使用文本内容:contains(text(), "特价")
  4. 组合使用:.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示例,你可以看到:

  1. 如何定位和提取数据:使用CSS选择器、XPath等多种方式
  2. 如何处理动态内容:通过动态渲染执行JavaScript
  3. 如何维护会话:保持登录状态,访问受保护页面
  4. 如何绕过反爬虫:使用高级伪装技术
  5. 如何构建完整爬虫:从简单脚本到完整项目

记住,良好的爬虫实践包括:

  • 尊重网站的robots.txt
  • 添加适当延迟,避免对服务器造成压力
  • 处理异常和错误
  • 保存进度,支持断点续传
  • 遵守法律法规和网站条款

现在你已经掌握了Scrapling的核心功能,可以开始构建自己的数据抓取项目了!从简单的单页抓取开始,逐步扩展到复杂的分布式爬虫系统。

相关推荐
老张的码2 小时前
飞书 × OpenClaw 接入指南
人工智能·后端
Lee川2 小时前
🚀《JavaScript 灵魂深处:从 V8 引擎的“双轨并行”看执行上下文的演进之路》
javascript·面试
zone77392 小时前
004:RAG 入门-LangChain读取PDF
后端·python·面试
漫霂2 小时前
基于redis实现登录校验
redis·后端
zone77392 小时前
005:RAG 入门-LangChain读取表格数据
后端·python·agent
青青家的小灰灰2 小时前
Vue 3 新标准:<script setup> 核心特性、宏命令与避坑指南
前端·vue.js·面试
用户7344028193422 小时前
mysql如何存储boolean类型
后端
golang学习记2 小时前
Fiber v3 适配器模式:17 种写法随便用,老代码"即插即用"🔌
后端·go
用户020742201752 小时前
从零到一:用 Rust 实现一个简单的区块链
后端