You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust函数能否同时兼容异步与阻塞式Fetcher回调?

适配阻塞/异步Fetcher的Rust抽象方案

完全可以实现这种抽象,核心是通过Trait结合异步标记统一阻塞和异步的Fetcher接口,让check逻辑与底层IO模型解耦。以下是具体实现方案:

1. 定义通用Fetcher Trait

先分别抽象阻塞和异步的Fetcher行为,异步Trait依赖async-trait crate(Rust原生暂不支持异步Trait,该宏是社区成熟解决方案):

阻塞式Fetcher Trait

use std::error::Error;
use url::Url;

pub trait BlockingFetcher {
    // 阻塞式获取robots.txt内容
    fn fetch_robots(&mut self, url: &Url) -> Result<String, Box<dyn Error>>;
}

异步Fetcher Trait

use async_trait::async_trait;

#[async_trait]
pub trait AsyncFetcher {
    // 异步式获取robots.txt内容
    async fn fetch_robots(&mut self, url: &Url) -> Result<String, Box<dyn Error>>;
}

2. 实现适配双模式的Robots缓存结构体

假设你的缓存结构体名为RobotsCache,可以为它实现两个版本的check方法,或通过条件编译让接口更简洁:

方案A:显式分离阻塞/异步方法

use std::collections::HashMap;
use url::Url;
// 假设使用第三方robots解析库,也可以自己实现解析逻辑
use robots::RobotsTxt;

pub struct RobotsCache {
    domain_cache: HashMap<String, RobotsTxt>,
}

#[derive(Debug)]
pub struct CheckResult {
    pub allowed: bool,
}

impl RobotsCache {
    pub fn new() -> Self {
        Self { domain_cache: HashMap::new() }
    }

    // 阻塞版检查逻辑
    pub fn check_blocking<F: BlockingFetcher>(
        &mut self,
        target_url: &Url,
        fetcher: &mut F,
    ) -> Result<CheckResult, Box<dyn Error>> {
        let domain = target_url.domain().ok_or("Invalid URL: missing domain")?;
        
        // 缓存未命中时,阻塞获取并解析robots.txt
        if !self.domain_cache.contains_key(domain) {
            let robots_url = Url::parse(&format!("{}://{}/robots.txt", target_url.scheme(), domain))?;
            let robots_content = fetcher.fetch_robots(&robots_url)?;
            let robots = RobotsTxt::parse(&robots_content)?;
            self.domain_cache.insert(domain.to_string(), robots);
        }

        // 执行抓取权限检查
        let robots = self.domain_cache.get(domain).unwrap();
        Ok(CheckResult { allowed: robots.allowed(target_url, "your-crawler-name") })
    }

    // 异步版检查逻辑
    pub async fn check_async<F: AsyncFetcher>(
        &mut self,
        target_url: &Url,
        fetcher: &mut F,
    ) -> Result<CheckResult, Box<dyn Error>> {
        let domain = target_url.domain().ok_or("Invalid URL: missing domain")?;
        
        // 缓存未命中时,异步获取并解析robots.txt
        if !self.domain_cache.contains_key(domain) {
            let robots_url = Url::parse(&format!("{}://{}/robots.txt", target_url.scheme(), domain))?;
            let robots_content = fetcher.fetch_robots(&robots_url).await?;
            let robots = RobotsTxt::parse(&robots_content)?;
            self.domain_cache.insert(domain.to_string(), robots);
        }

        // 执行抓取权限检查(逻辑与阻塞版完全一致)
        let robots = self.domain_cache.get(domain).unwrap();
        Ok(CheckResult { allowed: robots.allowed(target_url, "your-crawler-name") })
    }
}

方案B:条件编译简化接口

如果你的项目通过特性(Feature)区分阻塞/异步模式,可以用条件编译让调用者只用check方法:

impl RobotsCache {
    #[cfg(not(feature = "async"))]
    pub fn check<F: BlockingFetcher>(&mut self, target_url: &Url, fetcher: &mut F) -> Result<CheckResult, Box<dyn Error>> {
        // 同阻塞版实现
    }

    #[cfg(feature = "async")]
    pub async fn check<F: AsyncFetcher>(&mut self, target_url: &Url, fetcher: &mut F) -> Result<CheckResult, Box<dyn Error>> {
        // 同异步版实现
    }
}

3. 实现具体的Fetcher实例

阻塞版(基于ureq)

pub struct UreqFetcher;

impl BlockingFetcher for UreqFetcher {
    fn fetch_robots(&mut self, url: &Url) -> Result<String, Box<dyn Error>> {
        let resp = ureq::get(url.as_str()).call()?;
        Ok(resp.into_string()?)
    }
}

异步版(基于reqwest)

pub struct ReqwestFetcher {
    client: reqwest::Client,
}

impl ReqwestFetcher {
    pub fn new() -> Self {
        Self { client: reqwest::Client::new() }
    }
}

#[async_trait]
impl AsyncFetcher for ReqwestFetcher {
    async fn fetch_robots(&mut self, url: &Url) -> Result<String, Box<dyn Error>> {
        let resp = self.client.get(url.as_str()).send().await?;
        Ok(resp.text().await?)
    }
}

关键注意事项

  • 缓存逻辑复用:阻塞和异步版本的核心缓存检查、权限判断逻辑完全一致,可抽成私有辅助函数减少重复代码。
  • 并发安全:如果爬虫是多线程(阻塞)或多任务(异步)场景,需要给RobotsCache添加线程安全包装,比如Arc<Mutex<RobotsCache>>(阻塞)或Arc<tokio::Mutex<RobotsCache>>(异步)。
  • Rust版本兼容:Rust 1.75+已支持异步Trait的稳定语法,但async-trait宏仍可兼容旧版本。

内容的提问来源于stack exchange,提问作者Thomas Koch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:47:35