Aneiang.Pa 高阶用法:动态爬虫 SDK 详解与实战

Aneiang.Pa 高阶用法:动态爬虫 SDK 详解与实战

在之前介绍 Aneiang.Pa 的热门新闻爬虫库时,我们提到了它支持微博、知乎、B站等十多个平台的热榜数据抓取。但对于有更灵活需求的开发者来说,可能需要抓取特定网站的自定义数据结构。今天,我们将深入探讨 Aneiang.Pa 的高阶用法------动态爬虫 SDK。

什么是动态爬虫 SDK?

动态爬虫 SDK 是 Aneiang.Pa 提供的一个独立模块,它允许你通过简单的模型定义和特性标注,快速实现对任意网站数据结构的抓取。无需为每个网站编写特定的解析逻辑,只需定义你想要的数据模型,SDK 会自动处理 HTML 解析和数据提取。

GitHub地址:https://github.com/AneiangSoft/Aneiang.Pa

Gitee地址:https://gitee.com/aneiangsoft/Aneiang.Pa

快速入门

1. 安装 NuGet 包

首先,在你的项目中安装动态爬虫 SDK:

bash 复制代码
dotnet add package Aneiang.Pa.Dynamic

2. 配置服务

在 Startup.cs 或 Program.cs 中注册动态爬虫服务:

csharp 复制代码
services.AddDynamicScraper(context.Configuration);

3. 定义数据模型

这是最核心的一步。假设我们要从某个网站抓取工具卡片数据:

csharp 复制代码
[HtmlContainer("div", htmlClass: "tab-content", index: 1)]
[HtmlItem("a")]
public class ToolCard
{
    [HtmlValue("p/b")]
    public string Title { get; set; }

    [HtmlValue(".", "href")]
    public string Url { get; set; }

    [HtmlValue("img", "src")]
    public string Icon { get; set; }

    [HtmlValue("p", htmlClass: "card-desc")]
    public string Description { get; set; }

    [HtmlValue("span", htmlClass: "download-count")]
    public string DownloadCount { get; set; }
}

4. 使用爬虫

csharp 复制代码
public class ToolService
{
    private readonly IDynamicScraper _scraper;
    
    public ToolService(IDynamicScraper scraper)
    {
        _scraper = scraper;
    }
    
    public async Task<List<ToolCard>> GetPopularToolsAsync()
    {
        var tools = await _scraper.DatasetScraper<ToolCard>(
            "https://www.example-tools.com/popular"
        );
        
        return tools;
    }
}

特性详解

HtmlContainerAttribute - 容器定位

这个特性标识数据集的容器元素,也就是包含所有数据项的父级元素。

csharp 复制代码
// 基本用法:通过标签名定位
[HtmlContainer("div")]

// 通过 class 定位
[HtmlContainer("div", htmlClass: "item-list")]

// 通过 id 定位
[HtmlContainer("div", htmlId: "main-content")]

// 当有多个匹配元素时,使用 index 指定第几个
[HtmlContainer("div", htmlClass: "tab-content", index: 1)]

HtmlItemAttribute - 数据项定位

标识单个数据项对应的 HTML 元素,这些元素都位于容器内部。

csharp 复制代码
// 简单标签定位
[HtmlItem("a")]

// 带有 class 的项
[HtmlItem("div", htmlClass: "card-item")]

// 列表中的每一项
[HtmlItem("li", htmlClass: "list-item")]

HtmlValueAttribute - 数据提取

定义如何从每个数据项中提取具体的字段值。

csharp 复制代码
// 从子元素提取文本
[HtmlValue("h3")]
public string Title { get; set; }

// 从特定路径提取
[HtmlValue("div/span")]
public string Subtitle { get; set; }

// 从 HTML 属性中提取值
[HtmlValue("a", "href")]
public string Link { get; set; }

[HtmlValue("img", "src")]
public string ImageUrl { get; set; }

// 使用当前元素(使用 "." 选择器)
[HtmlValue(".", "data-id")]
public string ItemId { get; set; }

// 通过 class 定位
[HtmlValue("p", htmlClass: "price")]
public string Price { get; set; }

选择器语法详解

动态爬虫 SDK 使用类似 XPath 但更简洁的选择器语法:

选择器 含义 示例匹配结构
p/b p 直接子元素中的 b <p><b>文本</b></p>
p//b p 的任何后代元素中的 b <p><div><span><b>文本</b></span></div></p>
div/p/span div > p > span <div><p><span>文本</span></p></div>
. 当前元素本身 用于提取当前元素的属性

实战案例

案例 1:电商商品列表抓取

csharp 复制代码
[HtmlContainer("ul", htmlClass: "product-list")]
[HtmlItem("li", htmlClass: "product-item")]
public class Product
{
    [HtmlValue("h2/product-name")]
    public string Name { get; set; }
    
    [HtmlValue(".", "data-product-id")]
    public string ProductId { get; set; }
    
    [HtmlValue("img", "src")]
    public string ImageUrl { get; set; }
    
    [HtmlValue("span/price")]
    public decimal Price { get; set; }
    
    [HtmlValue("div/rating")]
    public double Rating { get; set; }
    
    [HtmlValue("a", "href")]
    public string DetailUrl { get; set; }
}

案例 2:新闻文章列表抓取

csharp 复制代码
[HtmlContainer("div", htmlClass: "article-list")]
[HtmlItem("article")]
public class NewsArticle
{
    [HtmlValue("h1/title")]
    public string Title { get; set; }
    
    [HtmlValue("p/summary")]
    public string Summary { get; set; }
    
    [HtmlValue("span/author")]
    public string Author { get; set; }
    
    [HtmlValue("time", "datetime")]
    public DateTime PublishTime { get; set; }
    
    [HtmlValue("div//img", "src")]
    public string Thumbnail { get; set; }
    
    [HtmlValue("a", "href")]
    public string ArticleUrl { get; set; }
}

案例 3:复杂嵌套结构处理

对于复杂的页面结构,可以使用相对路径:

csharp 复制代码
[HtmlContainer("table", htmlClass: "data-table")]
[HtmlItem("tr")]
public class TableRow
{
    // 第一列:使用索引定位
    [HtmlValue("td[1]")]
    public string FirstColumn { get; set; }
    
    // 第二列中的链接
    [HtmlValue("td[2]/a")]
    public string SecondColumnLinkText { get; set; }
    
    [HtmlValue("td[2]/a", "href")]
    public string SecondColumnLink { get; set; }
    
    // 第三列中的多个元素
    [HtmlValue("td[3]/span", htmlClass: "tag")]
    public List<string> Tags { get; set; }
}

高级功能

1. 自定义解析器

如果默认的解析逻辑不能满足需求,可以实现自定义的值解析器:

csharp 复制代码
public class CustomDateParser : IHtmlValueParser
{
    public object Parse(HtmlNode node, HtmlValueAttribute attribute)
    {
        var text = node.InnerText;
        // 自定义日期解析逻辑
        return DateTime.ParseExact(text, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
    }
}

// 在模型中使用
public class Article
{
    [HtmlValue("time", ParserType = typeof(CustomDateParser))]
    public DateTime CustomDate { get; set; }
}

2. 异步数据加载处理

对于需要滚动加载或异步加载数据的页面:

csharp 复制代码
var scraper = scope.ServiceProvider.GetRequiredService<IDynamicScraper>();

// 配置浏览器模拟选项
var options = new ScraperOptions
{
    WaitForJavaScript = true,    // 等待 JavaScript 执行
    ScrollToBottom = true,       // 滚动到页面底部
    ScrollDelay = 1000,          // 滚动延迟
    Timeout = 30000              // 超时时间
};

var data = await scraper.DatasetScraper<Product>(
    "https://www.infinite-scroll-site.com/products",
    options
);

3. 分页处理

csharp 复制代码
public async Task<List<Product>> GetAllProductsAsync()
{
    var allProducts = new List<Product>();
    int page = 1;
    bool hasMore = true;
    
    while (hasMore)
    {
        var url = $"https://www.example.com/products?page={page}";
        var products = await _scraper.DatasetScraper<Product>(url);
        
        if (products.Any())
        {
            allProducts.AddRange(products);
            page++;
            
            // 避免请求过快
            await Task.Delay(1000);
        }
        else
        {
            hasMore = false;
        }
    }
    
    return allProducts;
}

最佳实践

1. 错误处理

csharp 复制代码
try
{
    var data = await _scraper.DatasetScraper<MyModel>(url);
    // 处理数据
}
catch (HtmlParseException ex)
{
    // HTML 解析错误
    _logger.LogError(ex, "HTML 解析失败");
}
catch (NetworkException ex)
{
    // 网络请求错误
    _logger.LogError(ex, "网络请求失败");
}
catch (Exception ex)
{
    // 其他错误
    _logger.LogError(ex, "抓取数据时发生错误");
}

2. 请求限制与礼貌爬取

csharp 复制代码
// 使用内置的延迟机制
var options = new ScraperOptions
{
    DelayBetweenRequests = 5000, // 5秒延迟
    MaxRetries = 3,              // 最大重试次数
    RetryDelay = 2000            // 重试延迟
};

// 或者手动控制
public async Task<List<T>> ScrapeWithDelay<T>(List<string> urls) 
    where T : class
{
    var results = new List<T>();
    
    foreach (var url in urls)
    {
        var data = await _scraper.DatasetScraper<T>(url);
        results.AddRange(data);
        
        // 礼貌爬取:每次请求后暂停
        await Task.Delay(Random.Shared.Next(3000, 8000));
    }
    
    return results;
}

3. 缓存策略

csharp 复制代码
public class CachedScraperService
{
    private readonly IDynamicScraper _scraper;
    private readonly IMemoryCache _cache;
    
    public async Task<List<Product>> GetProductsAsync(bool forceRefresh = false)
    {
        var cacheKey = "products_data";
        
        if (!forceRefresh && _cache.TryGetValue(cacheKey, out List<Product> cachedData))
        {
            return cachedData;
        }
        
        var data = await _scraper.DatasetScraper<Product>(
            "https://www.example.com/products"
        );
        
        // 缓存5分钟
        _cache.Set(cacheKey, data, TimeSpan.FromMinutes(5));
        
        return data;
    }
}

性能优化建议

  1. 并行处理:对于多个独立页面的抓取,可以使用并行处理
  2. 连接复用:确保 HttpClient 正确复用
  3. 选择性抓取:只抓取需要的字段,减少内存占用
  4. 流式处理:对于大量数据,考虑使用流式处理方式
csharp 复制代码
// 并行抓取示例
public async Task<List<Product>> ScrapeMultiplePagesAsync(List<string> urls)
{
    var tasks = urls.Select(url => 
        _scraper.DatasetScraper<Product>(url)
    );
    
    var results = await Task.WhenAll(tasks);
    
    return results.SelectMany(r => r).ToList();
}

总结

Aneiang.Pa 的动态爬虫 SDK 提供了一种声明式、类型安全的方式来抓取网页数据。通过简单的模型定义和特性标注,你可以快速实现各种复杂网站的抓取逻辑,而无需深入了解 HTML 解析的细节。

这种方式的优势在于:

  • 代码简洁:模型即文档,清晰易懂
  • 维护方便:网站结构变化时,只需调整模型特性
  • 类型安全:编译时检查,减少运行时错误
  • 灵活扩展:支持自定义解析器和高级选项

无论是快速原型开发,还是生产环境的数据抓取任务,Aneiang.Pa 的动态爬虫都是一个强大而实用的工具。


提示:请始终遵守目标网站的 robots.txt 规则,尊重版权和隐私,将抓取间隔控制在合理范围,避免对目标网站造成过大压力。数据抓取仅应用于合法合规的目的。

相关推荐
有味道的男人2 小时前
淘宝图片搜索(拍立淘)+ 店铺全商品爬虫 深度实战指南(Python)
开发语言·爬虫·python
一招定胜负17 小时前
网络爬虫(第三部)
前端·javascript·爬虫
interception1 天前
爬虫逆向:瑞数5(华能电子)
爬虫
光算科技1 天前
商品颜色/尺码选项太多|谷歌爬虫不收录怎么办
java·javascript·爬虫
是Yu欸1 天前
扫描网站结构的SEO元数据抓取方案
爬虫·seo·亮数据·brightdata
Data_Journal1 天前
Puppeteer vs. Playwright —— 哪个更好?
运维·人工智能·爬虫·媒体·静态代理
啊巴矲1 天前
小白从零开始勇闯人工智能:爬虫初级篇(Selenium库)
爬虫·selenium·测试工具
serve the people1 天前
AI 模型识别 Nginx 流量中爬虫机器人的防御机制
人工智能·爬虫·nginx
薛不痒1 天前
网络爬虫下(一些对网页的操作)
爬虫