rust_scrapling/lib.rs
1//! RUSTScrapling — a Rust port of the [Scrapling](https://github.com/D4Vinci/Scrapling)
2//! web-scraping framework.
3//!
4//! The crate is organized in three layers, each usable on its own:
5//!
6//! - **[`parser`]** — HTML parsing and data extraction. [`Selector`] wraps a
7//! parsed DOM node and offers CSS selection (including Parsel-style
8//! `::text` / `::attr(name)` pseudo-elements via [`Selector::css_get`] /
9//! [`Selector::css_getall`]), text extraction, DOM navigation, regex
10//! helpers, JSON parsing, and *adaptive element relocation* — a saved
11//! element's "unique properties" can be used to find it again by
12//! similarity after the page layout changes.
13//! - **[`fetchers`]** — async HTTP. [`Fetcher`] is a reqwest-backed client
14//! configured through [`FetcherConfig`] (timeouts, retries, redirects,
15//! single/per-protocol/rotating proxies, response-size caps) that sends
16//! browser-like *stealth headers* by default, and returns a [`Response`]
17//! with charset-aware body decoding and direct parser integration.
18//! - **[`spiders`]** — crawling. Implement the [`Spider`] trait (or use a
19//! ready-made template: [`CrawlSpider`], [`SitemapSpider`],
20//! [`ShopifySpider`]) and run it with [`CrawlerEngine`]. The engine
21//! handles concurrency limits (global and per-domain), request
22//! deduplication, robots.txt compliance (RFC 9309 matching plus
23//! `Crawl-delay`), AutoThrottle adaptive per-domain delays,
24//! blocked-response retries, checkpoints for pause/resume, and a
25//! development-mode response cache for offline iteration. Results come
26//! back as a [`CrawlResult`] with live stats and an [`ItemList`]
27//! exportable to CSV, JSON, JSON Lines, or XML.
28//!
29//! # Quickstart: parsing HTML
30//!
31//! ```
32//! use rust_scrapling::Selector;
33//!
34//! let html = r#"<html><body>
35//! <ul>
36//! <li class="item"><a href="/p/1">Widget</a> <span class="price">9.99</span></li>
37//! <li class="item"><a href="/p/2">Gadget</a> <span class="price">19.99</span></li>
38//! </ul>
39//! </body></html>"#;
40//!
41//! let page = Selector::from_html(html);
42//!
43//! // Parsel-style `::text` / `::attr()` pseudo-elements:
44//! let names: Vec<String> = page
45//! .css_getall("li.item a::text")
46//! .into_iter()
47//! .map(|t| t.into_string())
48//! .collect();
49//! assert_eq!(names, ["Widget", "Gadget"]);
50//!
51//! let first_href = page.css_get("li.item a::attr(href)").unwrap();
52//! assert_eq!(first_href.as_str(), "/p/1");
53//!
54//! // Plain CSS queries return `Selectors` for further navigation:
55//! let prices = page.css(".price");
56//! assert_eq!(prices.len(), 2);
57//! assert_eq!(prices[0].text().as_str(), "9.99");
58//! ```
59//!
60//! # Fetching a page
61//!
62//! ```no_run
63//! use rust_scrapling::{Fetcher, FetcherConfig};
64//!
65//! #[tokio::main]
66//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
67//! let config = FetcherConfig::builder()
68//! .timeout(20)
69//! .stealth(true) // browser-like headers (the default)
70//! .build();
71//! let fetcher = Fetcher::new(config)?;
72//!
73//! let response = fetcher.get("https://example.com").await?;
74//! println!("status: {}", response.status());
75//! if let Some(title) = response.selector().css_get("title::text") {
76//! println!("title: {}", title);
77//! }
78//! Ok(())
79//! }
80//! ```
81//!
82//! # A minimal spider
83//!
84//! ```no_run
85//! use async_trait::async_trait;
86//! use rust_scrapling::spiders::response::SpiderResponse;
87//! use rust_scrapling::spiders::session::SessionManager;
88//! use rust_scrapling::{CrawlerEngine, FetcherConfig, Spider, SpiderRequest};
89//! use std::sync::Arc;
90//!
91//! struct QuotesSpider;
92//!
93//! #[async_trait]
94//! impl Spider for QuotesSpider {
95//! fn name(&self) -> &str {
96//! "quotes"
97//! }
98//!
99//! fn start_urls(&self) -> Vec<String> {
100//! vec!["https://quotes.toscrape.com/".to_string()]
101//! }
102//!
103//! async fn parse(
104//! &self,
105//! response: SpiderResponse,
106//! ) -> (Vec<serde_json::Value>, Vec<SpiderRequest>) {
107//! let items = response
108//! .css_getall(".quote .text::text")
109//! .into_iter()
110//! .map(|text| serde_json::json!({ "quote": text.as_str() }))
111//! .collect();
112//! (items, Vec::new()) // no follow-up requests
113//! }
114//! }
115//!
116//! #[tokio::main]
117//! async fn main() {
118//! let sessions = SessionManager::new(FetcherConfig::default());
119//! let engine = CrawlerEngine::new(Arc::new(QuotesSpider), sessions, None)
120//! .expect("HTTP client should build");
121//! let result = engine.crawl().await;
122//! println!("scraped {} items", result.items.len());
123//! }
124//! ```
125//!
126//! # Feature highlights
127//!
128//! - CSS selectors with `::text` / `::attr(name)` extraction
129//! ([`Selector::css_get`] / [`Selector::css_getall`])
130//! - Adaptive element relocation that survives page-layout changes
131//! ([`parser::adaptive`])
132//! - Stealth (browser-like) request headers, on by default
133//! ([`FetcherConfig::build_headers`])
134//! - robots.txt compliance with RFC 9309 longest-match rules and
135//! `Crawl-delay` support ([`spiders::robots`])
136//! - AutoThrottle: adaptive per-domain crawl delays ([`spiders::throttle`])
137//! - Checkpoints with pause/resume ([`spiders::checkpoint`],
138//! [`CrawlerEngine::request_pause`])
139//! - Development-mode response cache for offline replays
140//! ([`spiders::cache`])
141//! - Item export to CSV / JSON / JSON Lines / XML ([`ItemList`])
142
143#![warn(missing_docs)]
144
145pub mod core;
146pub mod fetchers;
147pub mod parser;
148pub mod spiders;
149
150// Re-export primary types at crate root
151pub use fetchers::client::Fetcher;
152pub use fetchers::config::FetcherConfig;
153pub use fetchers::response::Response;
154pub use parser::{Selector, Selectors};
155pub use spiders::engine::CrawlerEngine;
156pub use spiders::links::LinkExtractor;
157pub use spiders::request::SpiderRequest;
158pub use spiders::result::{CrawlResult, CrawlStats, ItemList};
159pub use spiders::spider::Spider;
160pub use spiders::templates::{CrawlRule, CrawlSpider, ShopifySpider, SitemapSpider};