Rust 語言在爬蟲領域的應用相對較少,盡管 Rust 的 async/await
已穩定,但其與線程安全、Pin
等概念的結合仍較復雜,而爬蟲高度依賴并發處理,進一步提高了開發成本。這就導致了使用Rust語言爬蟲用的人很少。
下面是一個使用 Rust 編寫的異步爬蟲示例,支持并發請求、深度控制和去重功能。該爬蟲使用 Tokio 作為異步運行時,Reqwest 處理 HTTP 請求,Select 解析 HTML。
use std::{collections::HashSet, sync::Arc, time::Duration};use select::{document::Document,predicate::{Name, Attr},
};
use tokio::{sync::{Mutex, Semaphore},time,
};
use url::Url;// 爬蟲配置
const MAX_DEPTH: usize = 3; // 最大爬取深度
const MAX_PAGES: usize = 50; // 最大爬取頁面數
const MAX_CONCURRENT_REQUESTS: usize = 10; // 最大并發請求數
const USER_AGENT: &str = "Mozilla/5.0 (compatible; AsyncCrawler/1.0)";#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {let start_url = "https://www.rust-lang.org/";println!("Starting crawl from: {}", start_url);// 共享狀態let visited = Arc::new(Mutex::new(HashSet::new()));let page_count = Arc::new(Mutex::new(0));let semaphore = Arc::new(Semaphore::new(MAX_CONCURRENT_REQUESTS));// 初始 URLcrawl_page(start_url.to_string(),0,visited.clone(),page_count.clone(),semaphore.clone(),).await?;println!("Crawling completed!");Ok(())
}/// 爬取單個頁面
async fn crawl_page(url: String,depth: usize,visited: Arc<Mutex<HashSet<String>>>,page_count: Arc<Mutex<usize>>,semaphore: Arc<Semaphore>,
) -> Result<(), Box<dyn std::error::Error>> {// 檢查深度限制if depth > MAX_DEPTH {return Ok(());}// 檢查是否已訪問{let mut visited_set = visited.lock().await;if visited_set.contains(&url) {return Ok(());}visited_set.insert(url.clone());}// 獲取信號量許可 (控制并發)let _permit = semaphore.acquire().await?;// 創建 HTTP 客戶端let client = reqwest::Client::builder().user_agent(USER_AGENT).timeout(Duration::from_secs(5)).build()?;// 發送請求let response = match client.get(&url).send().await {Ok(res) => res,Err(e) => {eprintln!("Request failed: {} - {}", url, e);return Ok(());}};// 檢查狀態碼if !response.status().is_success() {eprintln!("HTTP error: {} - {}", url, response.status());return Ok(());}// 獲取頁面內容let html = match response.text().await {Ok(html) => html,Err(e) => {eprintln!("Failed to get text: {} - {}", url, e);return Ok(());}};// 更新頁面計數器let mut count = page_count.lock().await;*count += 1;println!("[{}/{}] Depth {}: {}", *count, MAX_PAGES, depth, url);// 檢查頁面限制if *count >= MAX_PAGES {return Ok(());}// 解析頁面并提取鏈接let base_url = Url::parse(&url)?;let document = Document::from(html.as_str());let links: Vec<String> = document.find(Name("a")).filter_map(|a| a.attr("href")).filter_map(|href| base_url.join(href).ok()).map(|url| url.to_string()).collect();// 限制請求速率time::sleep(Duration::from_millis(100)).await;// 創建新爬取任務let mut tasks = vec![];for link in links {let visited = visited.clone();let page_count = page_count.clone();let semaphore = semaphore.clone();tasks.push(tokio::spawn(async move {crawl_page(link, depth + 1, visited, page_count, semaphore).await}));}// 等待所有任務完成for task in tasks {let _ = task.await;}Ok(())
}
功能說明
1、異步并發:
- 使用 Tokio 的異步任務 (
tokio::spawn
) - 通過信號量 (
Semaphore
) 限制最大并發請求數
2、爬取控制:
MAX_DEPTH
:限制爬取深度MAX_PAGES
:限制最大頁面數- 請求超時設置 (5 秒)
- 請求間延遲 (100ms)
3、智能解析:
- 使用
url
庫處理相對/絕對路徑 - 通過
select
庫解析 HTML 并提取鏈接 - 只處理
<a>
標簽的href
屬性
4、狀態管理:
- 使用
Mutex
保護共享狀態 - 使用
HashSet
記錄已訪問 URL - 原子計數器跟蹤已爬取頁面數
使用說明
1、添加依賴到 Cargo.toml
:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
reqwest = "0.11"
select = "0.6"
url = "2.4"
2、可配置參數:
// 在代碼頂部修改這些常量:
const MAX_DEPTH: usize = 3; // 最大爬取深度
const MAX_PAGES: usize = 50; // 最大爬取頁面數
const MAX_CONCURRENT_REQUESTS: usize = 10; // 并發請求數
const USER_AGENT: &str = "..."; // 自定義 User-Agent
3、運行:
cargo run
這個爬蟲框架提供了基礎功能,我們可以根據具體需求擴展其功能。建議在實際使用時添加適當的日志記錄、錯誤處理和遵守目標網站的爬取政策。