रेस्ट वेब स्क्रैपिंग विद reqwest और स्क्रैपर: एक व्यावहारिक मार्गदर्शिका
Expert in Web Scraping Technologies
TL;DR:
- 2026 में Rust वेब स्क्रैपिंग दो क्रेट्स पर चलता है:
reqwestHTTP अनुरोध के लिए औरscraperCSS-सेलेक्टर पार्सिंग के लिए, जिसमेंtokioअसिंक्रोनस रनटाइम को संचालित करता है। - कंपाइलर आपको हर विफलता पथ को संभालने के लिए मजबूर करता है, इसलिए एक Rust स्क्रैपर जो बनता है, आमतौर पर एक ऐसा स्क्रैपर होता है जो गलत तरीके से निर्मित मार्कअप और गैर-200 प्रतिक्रियाओं को सहन करता है।
tokio::spawnएक पृष्ठ-दर-पृष्ठ क्रॉलर को लगभग पांच पंक्तियों में समवर्ती बनाता है, और यह गाइड इसे 50 रिकॉर्ड के लिए पांच पृष्ठों पर चलाता है।- न तो
reqwestऔर न हीscraperजावास्क्रिप्ट निष्पादित करते हैं, इसलिए एक क्लाइंट-Render की गई पृष्ठ वह मार्कअप लौटाती है जो साफ-सुथरा पार्स होता है और शून्य रिकॉर्ड देता है। - Scrapeless यूनिवर्सल स्क्रैपिंग API पहले पृष्ठ को रेंडर करता है और वही अपरिवर्तित
scraperकोड 10 रिकॉर्ड के लिए HTML लौटाता है। - Scrapeless मुफ्त योजना पर शुरू करें और अपने लक्ष्य के खिलाफ पिवट उदाहरण चलाएं।
Rust स्क्रैपिंग काम में एक विशेष कारण से आता है: क्रॉलर आमतौर पर डेटा पाइपलाइन का वो हिस्सा होता है जो घंटों तक अनियंत्रित रूप से चलता है, मार्कअप के खिलाफ जिसे कोई नियंत्रित नहीं करता। एक बोर चेक किया गया बाइनरी, जिसमें कोई गारबेज-कलेक्टर नहीं होता, हर सीमा पर स्पष्ट Result संभालने के साथ इस काम के लिए एक अच्छा विकल्प है।
यह गाइड वर्तमान क्रेट रिलीज़ के साथ एक कार्यशील स्क्रैपर बनाता है, इसे चलाता है, और फिर यह दिखाता है कि शुद्ध-Rust स्टैक ठीक कहाँ रुकता है - मापे गए नंबरों के साथ न कि चेतावनी के साथ।
आपको क्या चाहिए
Rust वेब स्क्रैपिंग के लिए तीन क्रेट और एक स्थिर टूलचेन की आवश्यकता होती है। नीचे दी गई संस्करण लेखन के समय Rust समुदाय क्रेट रजिस्ट्रि पर मौजूद वर्तमान प्रकाशित रिलीज़ हैं, और यहाँ हर उदाहरण बिल्कुल इन्हीं के खिलाफ संकलित और चलाए गए थे:
| क्रेट | संस्करण | कार्य |
|---|---|---|
reqwest |
0.13.4 | HTTP क्लाइंट |
scraper |
0.27.0 | HTML पार्सिंग और CSS सेलेक्टर |
tokio |
1.53.0 | Async रनटाइम |
serde_json |
1 | API प्रतिक्रियाओं के लिए JSON प्रबंधन |
scraper क्रेट html5ever को लपेटता है, यही पार्सिंग इंजन है जिसका उपयोग सर्फ़ो में किया गया है, इसलिए यह HTML पार्सिंग विशिष्टता का पालन करता है न कि मार्कअप को टेक्स्ट के रूप में regex पर मानता है। यह असली पृष्ठों पर महत्वपूर्ण है, जहाँ बंद न होने वाले टैग सामान्य होते हैं।
इंस्टॉल
प्रोजेक्ट बनाएं और निर्भरताएँ जोड़ें:
bash
cargo new rust-scraper
cd rust-scraper
फिर Cargo.toml में निर्भरता ब्लॉक घोषित करें:
toml
[package]
name = "rust-scraper"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.13.4", features = ["json"] }
scraper = "0.27.0"
tokio = { version = "1.53.0", features = ["macros", "rt-multi-thread"] }
serde_json = "1"
reqwest पर json सुविधा अनुरोध और प्रतिक्रिया सीरियलाइजेशन को खींचती है। tokio पर macros और rt-multi-thread सुविधाएँ ही #[tokio::main] विशेषता को काम बनाती हैं।
एक पृष्ठ लाना और पार्स करना
एक पूर्ण Rust स्क्रैपर इसकी प्रतिष्ठा से छोटा है। यह एक सर्वर-रेंडर की गई पृष्ठ को लाता है, प्रत्येक उद्धरण कंटेनर को चुनता है, और प्रत्येक से दो फ़ील्ड खींचता है:
rust
use scraper::{Html, Selector};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let body = reqwest::get("https://quotes.toscrape.com/")
.await?
.error_for_status()?
.text()
.await?;
let document = Html::parse_document(&body);
let quote_sel = Selector::parse("div.quote").unwrap();
let text_sel = Selector::parse("span.text").unwrap();
let author_sel = Selector::parse("small.author").unwrap();
let mut count = 0;
for quote in document.select("e_sel) {
let text = quote.select(&text_sel).next().map(|e| e.inner_html());
let author = quote.select(&author_sel).next().map(|e| e.inner_html());
if let (Some(t), Some(a)) = (text, author) {
if count == 0 {
println!("पहला उद्धरण: {} — {}", t, a);
}
count += 1;
}
}
println!("पार्स किए गए उद्धरण: {}", count);
Ok(())
}
इसे चलाने पर यह निकालता है:
text
पहला उद्धरण: “हमने जिस विश्व को बनाया है, वह हमारे सोचने की एक प्रक्रिया है। इसे बिना अपने सोचने को बदले नहीं बदला जा सकता।” — अल्बर्ट आइंस्टाइन
पार्स किए गए उद्धरण: 10
उस कोड में तीन विवरण अधिकांश भार उठाते हैं।
error_for_status() एक 4xx या 5xx प्रतिक्रिया को Err में परिवर्तित कर देता है, इसे डेटा के रूप में पार्स करने के बजाय। इसके बिना, एक 403 शरीर शून्य सेलेक्टर मेल खाता है और एक खाली परिणाम सेट के समान दिखाई देता है। उन स्थिति श्रेणियों के बीच का अंतर HTTP अर्थशास्त्र विशिष्टता में परिभाषित है।
Selector::parse दोषपूर्ण है क्योंकि एक चयनकर्ता स्ट्रिंग को संकलित किया जाता है, न कि मिलान के समय व्याख्या की जाती है। लूप के बाहर एक बार चयनकर्ताओं का संकलन करना — न कि प्रति तत्व — यह है कि कोई भी चयन कॉल सस्ती रहती हैं। वाक्यविन्यास W3C चयनकर्ता स्तर 4 विशिष्टता का पालन करता है।
main से बाहर निकलने वाला ? ऑपरेटर Box<dyn std::error::Error> को एक व्यावहारिक वापसी प्रकार में बदलता है। हर दोषपूर्ण कॉल एक ही स्थान पर अनरविंड होती है, और प्रक्रिया विफलता पर गैर-जीरो पर समाप्त होती है — यह उपयोगी है जब स्क्रैपर शेड्यूलर से चलता है।
पृष्ठों को समवर्ती रूप से स्क्रैप करें
रस्ट की समवर्तीता की कहानी यहाँ उपयोग करने के मुख्य तर्क है, और tokio::spawn वही है जहाँ यह प्रकट होता है। प्रत्येक पृष्ठ एक स्वतंत्र कार्य बन जाता है, इनमें से सभी एक कनेक्शन-पूल क्लाइंट साझा करते हैं, और परिणाम क्रम में एकत्रित किए जाते हैं:
rust
use scraper::{Html, Selector};
async fn page_quote_count(client: &reqwest::Client, page: u32) -> Result<usize, reqwest::Error> {
let url = format!("https://quotes.toscrape.com/page/{page}/");
let body = client.get(&url).send().await?.error_for_status()?.text().await?;
let quote_sel = Selector::parse("div.quote").unwrap();
Ok(Html::parse_document(&body).select("e_sel).count())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let tasks: Vec<_> = (1..=5)
.map(|page| {
let client = client.clone();
tokio::spawn(async move { (page, page_quote_count(&client, page).await) })
})
.collect();
let mut total = 0;
for task in tasks {
let (page, result) = task.await?;
let count = result?;
println!("पृष्ठ {page}: {count} उद्धरण");
total += count;
}
println!("5 पृष्ठों में कुल उद्धरण: {total}");
Ok(())
}
चलाना:
text
पृष्ठ 1: 10 उद्धरण
पृष्ठ 2: 10 उद्धरण
पृष्ठ 3: 10 उद्धरण
पृष्ठ 4: 10 उद्धरण
पृष्ठ 5: 10 उद्धरण
5 पृष्ठों में कुल उद्धरण: 50
reqwest::Client को क्लोन करना सस्ता है क्योंकि क्लोन अंतर्निहित कनेक्शन पूल को साझा करता है। प्रत्येक कार्य के लिए एक नया क्लाइंट बनाना हर बार एक नया पूल खोलेगा और लाभ को छोड़ देगा। पृष्ठ सीमा को इस तरह सीमित और आकार में रखना चाहिए कि लक्ष्य आराम से सेवा कर सके — समवर्तीता एक ज्ञात कार्यभार को पूरा करने का उपकरण है, न कि रनटाइम द्वारा अनुमति दिए गए जितने यथार्थ समय में अनुरोध करने के लिए।
क्या आप इसे एक ऐसा लक्ष्य दिखाने के लिए तैयार हैं जो सर्वर-साइड में रेंडर करता है? एक निःशुल्क Scrapeless खाता बनाएँ और आपके पास पहले से मौजूद विश्लेषण कोड बनाए रखें।
जहाँ reqwest और scraper रुकते हैं
न तो क्रेट जावास्क्रिप्ट निष्पादित करता है। reqwest सर्वर द्वारा भेजे गए बाइट लौटाता है, और scraper ठीक वही बाइट्स को पार्स करता है — इसलिए एक पृष्ठ पर जो अपने सामग्री को ब्राउज़र में बनाता है, चयनकर्ता कुछ भी मेल नहीं खाते।
यह मापने योग्य है न कि सैद्धांतिक। ऊपर उपयोग की गई साइट एक समान डेटा का क्लाइंट-रेनडर किया गया जुड़वा /js/ पर प्रकाशित करती है। उसी विश्लेषण तर्क को इसे इंगित करना:
rust
use scraper::{Html, Selector};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let body = reqwest::get("https://quotes.toscrape.com/js/")
.await?
.error_for_status()?
.text()
.await?;
let document = Html::parse_document(&body);
let quote_sel = Selector::parse("div.quote").unwrap();
let count = document.select("e_sel).count();
println!("html बाइट: {}", body.len());
println!("उद्धरण पार्स किए गए: {}", count);
Ok(())
}
text
html बाइट: 5808
उद्धरण पार्स किए गए: 0
अनुरोध सफल हुआ। स्थिति 200 थी। पार्सर 5,808 बाइट्स के मान्य HTML पर सही ढंग से काम कर रहा था जिसमें कोई उद्धरण तत्व नहीं था — ये एक स्क्रिप्ट चलने के बाद DOM में लिखे जाते हैं। एक स्क्रैपर जो केवल HTTP विफलता की जांच करता है, इसे एक खाली पृष्ठ के रूप में मानता है न कि एक कमी के रूप में, यही कारण है कि पहले error_for_status() कॉल की आवश्यकता है लेकिन यह पर्याप्त नहीं है।
Universal Scraping API के साथ पृष्ठ रेंडर करें
Scrapeless Universal Scraping API उस अंतर को बंद करता है जो पृष्ठ को एक क्लाउड ब्राउज़र में रेंडर करता है और परिणामी HTML को लौटाता है, जिसका मतलब है कि रस्ट साइड एक सामान्य HTTP कॉल रहती है। js_render को true पर सेट करें और प्रतिक्रिया शरीर में पोस्ट-स्क्रिप्ट DOM शामिल है।
अपने कुंजी के साथ पर्यावरण से प्रमाणित करें:
bash
export SCRAPELESS_API_KEY="आपकी_API_की_यहाँ"
फिर केवल फ़ेच परत को बदलें — नीचे के चयनकर्ता कोड पहले उदाहरण के समान है:
rust
use scraper::{Html, Selector};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("SCRAPELESS_API_KEY")?;
let payload = json!({
"अभिनेता": "अनलॉकर.वेबअनलॉकर",
"इनपुट": {
"यूआरएल": "https://quotes.toscrape.com/js/",
"जेस_render": true,
"हेडलेस": true
}
});
let लिफाफा: serde_json::Value = reqwest::Client::new()
.post("https://api.scrapeless.com/api/v2/unlocker/request")
.header("x-api-token", api_key)
.json(&payload)
.send()
.await?
.error_for_status()?
.json()
.await?;
let शरीर = लिफाफा["data"].as_str().unwrap_or_default();
let दस्तावेज़ = Html::parse_document(body);
let उद्धरण_sel = Selector::parse("div.quote").unwrap();
let पाठ_sel = Selector::parse("span.text").unwrap();
let लेखक_sel = Selector::parse("small.author").unwrap();
let mut गिनती = 0;
for उद्धरण in दस्तावेज़.select("e_sel) {
let पाठ = उद्धरण.select(&text_sel).next().map(|e| e.inner_html());
let लेखक = उद्धरण.select(&author_sel).next().map(|e| e.inner_html());
if let (Some(t), Some(a)) = (text, author) {
if count == 0 {
println!("पहला उद्धरण: {} — {}", t, a);
}
count += 1;
}
}
println!("एचटीएमएल बाइट: {}", body.len());
println!("उद्धरण पार्स किए: {}", count);
Ok(())
}
text
पहला उद्धरण: “जिस दुनिया को हमने बनाया है, वह हमारे सोचने की एक प्रक्रिया है। इसे अपने विचारों को बदले बिना नहीं बदला जा सकता।” — अल्बर्ट आइंस्टीन
एचटीएमएल बाइट: 8985
उद्धरण पार्स किए: 10
वही पृष्ठ, वही चयनकर्ता, वही क्रेट संस्करण। केवल परिवर्तन यह है कि HTML को लाने वाला कौन सा परत है, और रिकॉर्ड की गिनती 0 से 10 बढ़ जाती है जबकि पेलोड 5,808 से 8,985 बाइट्स तक बढ़ गया है - अंतर यह है कि उद्धरण मार्कअप जो स्क्रिप्ट ने DOM में लिखा।
प्रतिक्रिया एक JSON लिफाफे के रूप में आती है जिसमें data में render किया गया दस्तावेज़ एक स्ट्रिंग के रूप में होता है, इसलिए उदाहरण पहले serde_json::Value पर पार्स करता है और data को Html::parse_document को सौंपता है। रेंडर विकल्पों के विवरण Scrapeless दस्तावेज़ में विद्यमान हैं, और वही js_render व्यवहार अधिक गहराई में JS रेंडरिंग गाइड में कवर किया गया है।
समस्या निवारण
चयनकर्ता वेब पृष्ठ पर कुछ भी मेल नहीं खाते जिसे आप ब्राउज़र में देख सकते हैं। पहले प्रतिक्रिया की लंबाई प्रिंट करें, जैसा कि ऊपर जेस उदाहरण करता है। कुछ हजार बाइट बिना मेल से आमतौर पर दर्शाता है कि सामग्री क्लाइंट द्वारा रेंडर की गई है, न कि चयनकर्ता गलत है। पृष्ठ का स्रोत देखें न कि निरीक्षक — निरीक्षक DOM को दिखाता है जिसके बाद स्क्रिप्ट चलती हैं, जो कि reqwest को प्राप्त नहीं हुआ।
Selector::parse स्टार्टअप पर पैनिक करता है। चयनकर्ता स्ट्रिंग अमान्य CSS है। प्सेडो-एलिमेंट्स और कुछ jQuery शैली के एक्सटेंशन विनिर्देशन का हिस्सा नहीं हैं और संकलित नहीं होंगे।
TLS बैकएंड पर निर्माण विफल होता है। reqwest एक TLS स्टैक संकलित करता है; एक न्यूनतम कंटेनर पर, सिस्टम को एक C टूलचेन और CMake की आवश्यकता होती है, या आप फ़ीचर फ्लैग के माध्यम से शुद्ध-रस्ट rustls बैकएंड पर स्विच कर सकते हैं।
inner_html() मार्कअप की बजाय पाठ लौटाता है। वह विधि तत्व के अंदर सब कुछ लौटाती है, टैग शामिल हैं। जब एक क्षेत्र में नेस्टेड तत्व हो सकते हैं, तो text() का उपयोग करें और टुकड़ों को इकट्ठा करें।
इनमें से किसी भी बात को किसी लक्षित स्थिर पर इंगित करने से पहले, साइट की शर्तों और उसके /robots.txt निदेशों की जांच करें, जो रोबोट्स प्रतिबंध प्रोटोकॉल मानक का पालन करते हैं। संग्रह को सार्वजनिक डेटा और उस मात्रा में सीमित रखें जिसे लक्ष्य आराम से सेव कर सकता है।
निष्कर्ष
रस्ट आपको एक स्क्रैपर देता है जो विफलता को स्पष्ट रूप से संभालता है और बिना ज्यादा औपचारिकता के समांतर करता है - परिवहन के लिए reqwest, चयनकर्ताओं के लिए scraper, प्रतिस्पर्धा के लिए tokio, और एक संकलक जो आपको Result को अनदेखा करने की अनुमति नहीं देगा। वह स्टैक सर्वर-रेंडर किए गए पृष्ठों को पूरी तरह से कवर करता है।
जो वह नहीं करता वह है जावास्क्रिप्ट चलाना, और उस गैप की लागत एक मौन शून्य है बजाय एक त्रुटि के। इसे मापना उपयोगी आदत है: सर्वर-रेंडर किए गए पृष्ठ पर 10 रिकॉर्ड, क्लाइंट-रेंडर किए गए जुड़वां पर 0, फिर 10 फिर से जब कुछ रस्ट इसे पार्स करने से पहले पृष्ठ को रेंडर करता है।
Scrapeless मुफ्त योजना से शुरू करें अपनी लक्ष्यों के खिलाफ रेंडर चरण को चलाने के लिए, और जब आप एक कार्य का आकार बनाते हैं तो वर्तमान Scrapeless मूल्य निर्धारण की समीक्षा करें।
अक्सर पूछे जाने वाले प्रश्न
प्रश्न: वेब स्क्रैपिंग के लिए सर्वोत्तम रस्ट क्रेट कौन सा है?
reqwest और scraper अधिकांश कार्यों को कवर करते हैं। reqwest HTTP को एक async-first API के साथ संभालता है, और scraper एक html5ever पार्स पेड़ पर CSS-चयनकर्ता क्वेरी प्रदान करता है। एक हेडलेस-ब्राउज़र क्रेट या एक रेंडरिंग API का उपयोग तब करें जब पृष्ठ अपने सामग्री को क्लाइंट-साइड पर बनाता है।
Q: क्या Rust वेब स्क्रैपिंग के लिए Python की तुलना में अच्छा है?
जब स्क्रैपर लंबे समय तक चलता है, समवर्ती रूप से चलता है, या बिना देखरेख के चलता है, तब Rust एक अच्छा विकल्प है, क्योंकि इसमें कोई गार्बेज-कलेक्टर ठहराव नहीं होता और कंपाइलर हर गलती के रास्ते को संभालने के लिए मजबूर करता है। Python फिर भी एक बार निकासी के लिए पारिस्थितिकी तंत्र की चौड़ाई और पुनरावृत्ति गति में जीतता है। पार्सिंग की अवधारणाएँ उनके बीच सीधे स्थानांतरित होती हैं।
Q: क्या reqwest JavaScript निष्पादित कर सकता है?
नहीं। reqwest एक HTTP क्लाइंट है और सर्वर द्वारा भेजे गए बाइट्स को लौटाता है। एक क्लाइंट-रेंडर्ड पृष्ठ पर, इसका मतलब है कि वैध HTML बिना किसी सामग्री के - उपरोक्त उदाहरण 5,808 बाइट्स को शून्य रिकॉर्ड में पार्स करता है। रेंडरिंग कहीं और होनी चाहिए, या तो एक हेडलेस ब्राउज़र में या एक API के माध्यम से जो रेंडर किया हुआ DOM लौटाता है।
Q: क्या मुझे सरल स्क्रैपर के लिए async और tokio की आवश्यकता है?
कड़ाई से नहीं - reqwest एक ब्लॉकिंग क्लाइन्ट को एक फीचर फ्लैग के पीछे भेजता है, जो एकल अनुक्रमिक अनुरोध के लिए ठीक है। जब आप एक से अधिक पृष्ठ प्राप्त करते हैं, तो async क्लाइन्ट tokio निर्भरता के लायक होता है, क्योंकि यही वह जगह है जहाँ tokio::spawn अनुक्रमिक फ़ेच को समवर्ती रूप में बदल देता है।
Q: मैं कैसे सुनिश्चित करूं कि जब साइट बदलती है तो मेरा Rust स्क्रैपर टूटे नहीं?
संरचना पर भरोसा करने के बजाय उस पर पुष्टि करें। यह सुनिश्चित करें कि कंटेनर चयनकर्ता ने फ़ील्ड निकालने से पहले एक संभावित संख्या में तत्वों का मिलान किया है, और अचानक शून्य को खाली परिणाम के बजाय एक विफलता के रूप में मानें। प्रारंभ में चयनकर्ताओं को संकलित करने से असमान CSS तुरंत प्रदर्शित होती है न कि मध्य-क्रॉल में।
स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।



