管道与中间件
Aneiang.Pa 内置 8 个中间件,组成生产级执行管道。全部默认开启,零配置。
管道执行顺序
请求 → Logging → Metrics → Tracing → Cache → RateLimit → CircuitBreaker → Retry → Timeout → DouYinCookie → Fetch → Parse → 响应
中间件一览
| 中间件 | 默认行为 | 关闭/调整 |
|---|---|---|
| Logging | ILogger<> 注入 | 不注入即空日志 |
| Metrics | Meter Aneiang.Pa | Pa.Configure(c => c.Options.EnableMetrics = false) |
| Tracing | ActivitySource Aneiang.Pa | 同上 EnableTracing |
| Cache | Memory 5min | .NoCache() 单次跳过 |
| Retry | 3 次指数退避 | .NoRetry() 单次跳过 |
| Timeout | 30s | .WithTimeout(TimeSpan) |
| RateLimit | 不启用 | 显式开启 + 配置 |
| CircuitBreaker | 5 次失败熔断 60s | 自动 |
| DouYinCookie | 抖音 Cookie 自动获取 | 仅对 DouYin 生效 |
缓存
默认 5 分钟内存缓存。相同 Source 的重复请求直接返回缓存数据。
// 第一次:实际请求
var data1 = await Pa.Source("WeiBo").GetAsync();
// data1.FromCache == false
// 第二次(5 分钟内):命中缓存
var data2 = await Pa.Source("WeiBo").GetAsync();
// data2.FromCache == true
// 跳过缓存,强制请求
var fresh = await Pa.Source("WeiBo").NoCache().GetAsync();
调整全局缓存时间:
Pa.Configure(c => c.Options.DefaultCacheDuration = TimeSpan.FromMinutes(10));
重试
默认 3 次指数退避:500ms → 2s → 8s,最长约 14 秒。
// 跳过重试
var data = await Pa.Source("WeiBo").NoRetry().GetAsync();
超时
默认 30 秒硬超时。
// 自定义超时
var data = await Pa.Source("WeiBo")
.WithTimeout(TimeSpan.FromSeconds(10))
.GetAsync();
熔断器
连续 5 次失败后自动熔断 60 秒,期间所有请求直接返回失败,不再实际发起 HTTP 请求。
熔断时间过后自动进入半开状态,尝试恢复。
限流
默认不启用。需要时显式开启:
Pa.Configure(c => c.Options.EnableRateLimit = true);
Pa.Configure(c => c.Options.RateLimit = new RateLimitOptions
{
PermitLimit = 10,
Window = TimeSpan.FromSeconds(1)
});
代理池
支持轮询/随机代理池,带健康跟踪和失败自动禁用:
Pa.Configure(c => c
.UseProxyPool(new[]
{
"http://127.0.0.1:7890",
"http://127.0.0.1:7891",
"http://127.0.0.1:7892",
})
.ProxyStrategy(ProxyStrategy.RoundRobin));
// 单次代理
var data = await Pa.Source("WeiBo")
.WithProxy("http://127.0.0.1:7890")
.GetAsync();
自定义中间件
需要特殊处理(签名、Token、登录态等)时,写一个中间件:
using Aneiang.Pa.Abstractions;
public class MyAuthMiddleware : IScrapeMiddleware
{
public int Order => 5;
public string Name => "MyAuth";
public async Task<ScrapeResult> InvokeAsync(ScrapeContext ctx, ScrapeDelegate next)
{
if (ctx.Recipe.Name == "MySite")
ctx.Variables["token"] = await GetTokenAsync();
return await next(ctx);
}
private async Task<string> GetTokenAsync()
{
// 获取 token 的逻辑
return "my-token";
}
}
注册中间件:
Pa.Configure(c => c.UseMiddleware(new MyAuthMiddleware()));
Recipe 中用 {token} 占位:
headers:
Authorization: "Bearer {token}"
IScrapeMiddleware 接口
| 属性/方法 | 说明 |
|---|---|
Order | 执行顺序(数字越小越先执行) |
Name | 中间件名称(用于日志) |
InvokeAsync(ctx, next) | 处理逻辑,调用 next(ctx) 继续管道 |
ctx.Variables | 共享变量字典 |
ctx.Recipe | 当前 Recipe 信息 |
ctx.CancellationToken | 取消令牌 |