जावा वेब स्क्रैपिंग विथ jsoup: फ़etch, पार्स, और रेंडर
Expert in Web Scraping Technologies
TL;DR:
- jsoup एक Java HTML पार्सर है जिसमें एक CSS सेलेक्टर इंजन है, और यह अपना HTTP क्लाइंट प्रदान करता है, इसलिए एक कार्यशील स्क्रैपर के लिए एक निर्भरता और लगभग बीस लाइनें कोड चाहिए।
- jsoup डिफ़ॉल्ट रूप से प्रतिक्रिया के पहले 2 MiB को ही रखता है। 2,235,648-बाइट के पृष्ठ पर यह ठीक 2,097,152 बाइट्स को रखा, 255 संदर्भ प्रविष्टियों में से 68 को गिरा दिया, एक पूरे अनुभाग को खो दिया, और कुछ भी नहीं फेंका।
attr("href")href को ठीक उसी तरह लौटाता है जैसे HTML में लिखा गया है।attr("abs:href")इसे दस्तावेज़ के आधार URI के खिलाफ हल करता है।- jsoup जावास्क्रिप्ट निष्पादित नहीं करता है। क्लाइंट से रेंडर किए गए पृष्ठ पर वही सेलेक्टर कोड सीधे 0 आइटम्स और प्री-रेंडर किए गए HTML आने पर 10 आइटम्स पाता है।
- स्क्रैपलेस यूनिवर्सल स्क्रैपिंग API के माध्यम से फ़ेच को रूटिंग करने से केवल परिवहन बदलता है — पार्सिंग विधि को छुआ नहीं जाता है।
- स्क्रैपलेस का मुफ्त योजना इस गाइड में हर अनुरोध को चलाने के लिए पर्याप्त है।
Java वह जगह है जहाँ बहुत सारे स्क्रैप किए गए डेटा समाप्त होते हैं। यदि डेटा को उपभोग करने वाली सेवा एक Spring या Quarkus एप्लिकेशन है, तो उसी JVM में निकासी रखने से भाषा की सीमा, एक सीरियलाइजेशन चरण, और एक दूसरा तैनाती लक्ष्य खत्म हो जाता है।
उसको व्यावहारिक बनाने वाली लाइब्रेरी jsoup है। यह वास्तविक दुनिया के HTML को उसी तरह पार्स करता है जैसे एक ब्राउज़र करता है - बंद न किए गए टैग बंद करना, नेस्टिंग को ठीक करना, और एट्रिब्यूट्स को सामान्यीकृत करना, HTML पार्सिंग स्पेसिफिकेशन में त्रुटि-हैंडलिंग नियमों का पालन करना — और फिर परिणाम को CSS सेलेक्टर API के माध्यम से उजागर करता है।
यह गाइड दो लाइव साइटों के खिलाफ एक कार्यशील स्क्रैपर बनाता है, फिर दो व्यवहारों को मापता है जो यह तय करते हैं कि स्क्रैपर सही है या नहीं: एक बड़े पृष्ठ पर jsoup चुपचाप क्या छोड़ता है, और एक पृष्ठ पर जो ब्राउज़र में रेंडर होता है, क्या लौटाता है।
jsoup आपको क्या देता है
jsoup पहले एक पार्सर है और दूसरे स्थान पर एक HTTP क्लाइंट है। Jsoup.connect(url) एक प्रवाहमान अनुरोध बिल्डर लौटाता है; .get() अनुरोध करता है और एक Document लौटाता है जिसे आप सेलेक्टर्स के साथ क्वेरी कर सकते हैं।
java
String url = "https://books.toscrape.com/";
Document doc = Jsoup.connect(url).get();
String title = doc.selectFirst("h1").text();
वह Document एक पार्स की गई ट्री है न कि एक स्ट्रिंग, इसलिए एक दोषपूर्ण पृष्ठ भी एक क्वेरी करने योग्य संरचना देता है। यह वह आधार URI भी रखता है जिससे इसे लाया गया था, जो बाद में पूर्ण URL समाधान को संभव बनाता है।
jsoup जो नहीं करता है वह स्क्रिप्ट चलाना है। इसमें कोई जावास्क्रिप्ट इंजन और न ही कोई DOM इवेंट लूप है। जो कुछ भी सर्वर भेजता है वही आप पार्स करने के लिए पाते हैं।
परियोजना सेट करें
एक निर्भरता पार्सिंग को कवर करती है। Gson यहाँ केवल अंतिम अनुभाग में JSON लिफाफा पढ़ने के लिए है; यदि आप उस अनुभाग को छोड़ते हैं, तो आप इसे हटा सकते हैं।
xml
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.21.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
कंपाइलर प्लगइन को स्पष्ट रूप से पिन करें। Maven 3.8.7 डिफ़ॉल्ट रूप से maven-compiler-plugin 3.1 को बाइंड करता है, जो maven.compiler.release की अनदेखी करता है और Source option 5 is no longer supported के साथ रुक जाता है:
xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>com.example.Scraper</mainClass>
</configuration>
</plugin>
</plugins>
</build>
maven.compiler.release को 17 पर सेट करने से नीचे दिया गया कोड किसी भी वर्तमान JDK पर संकलित होता है। सत्यापन रन ने OpenJDK 21.0.11 का उपयोग किया।
एक पृष्ठ फ़ेच करें और इसे पार्स करें
हर अनुरोध पर एक उपयोगकर्ता एजेंट और एक टाइमआउट सेट करें। jsoup का डिफ़ॉल्ट एजेंट अपने आप को jsoup के रूप में पहचानता है, और कई साइटें केवल उसी पर अपनी प्रतिक्रिया भिन्न करती हैं।
java
static final String USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
static Document fetch(String url) throws IOException {
return Jsoup.connect(url)
.userAgent(USER_AGENT)
.timeout(30_000)
.get();
}
timeout मिलीसेकंड में है और यह कनेक्ट और पढ़ने दोनों पर लागू होता है। एक get() जो इसे पार करता है SocketTimeoutException उठाता है; एक गैर-2xx स्थिति HttpStatusException उठाती है, जिसमें कोड होता है।
डेटा का चयन करें
सेलेक्टर्स मानक CSS हैं। select प्रत्येक मिलान को Elements संग्रह के रूप में लौटाता है; selectFirst एक Element या null लौटाता है।
java
record Book(String title, String price, String url) {}
static List<Book> books(Document doc) {
List<Book> found = new ArrayList<>();
for (Element card : doc.select("article.product_pod")) {
Element link = card.selectFirst("h3 > a");
found.add(new Book(
link.attr("title"),
card.selectFirst("p.price_color").text(),
hi
link.attr("abs:href")));
}
return found;
}
सजीव सूची के खिलाफ यह 20 किताबें वापस करता है, जिसमें पहली किताब 'A Light in the Attic' है जिसकी कीमत '£51.77' है।
उस अंतिम पंक्ति में abs: उपसर्ग सच में काम कर रहा है। स्रोत में एंकर इस प्रकार दिखता है:
text
catalogue/a-light-in-the-attic_1000/index.html
attr("href") आपको वह स्ट्रिंग कच्ची अवस्था में देता है। गुण नाम को abs: के साथ प्रारंभ करना दस्तावेज़ के मूल URI के खिलाफ इसे हल करता है, जैसा कि WHATWG URL मानक में वर्णित एल्गोरिदम का उपयोग करते हुए:
text
https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html
एक स्क्रैपर जो कच्ची मान को संग्रहीत करता है, तब तक ठीक काम करता है जब तक कि कोई और इसे लाने की कोशिश न करे।
सीमाएँ जो आपके डेटा की लागत बनती हैं
jsoup उस प्रतिक्रिया शरीर को पढ़ने की सीमा निर्धारित करता है जिसे वह पढ़ेगा। डिफ़ॉल्ट 2 MiB है, जो jsoup अनुरोध इंटरफेस पर दस्तावेजित किया गया है। उस बिंदु के बाद यह पढ़ना बंद कर देता है और जो कुछ उसके पास है उसे पार्स करता है। यह कोई अपवाद नहीं उठाता, कोई चेतावनी लॉग नहीं करता और Document पर कोई ध्वज नहीं छोड़ता यह बताने के लिए कि शरीर छोटा किया गया था।
जब आप इसे पार्स करने से पहले प्रतिक्रिया देखना चाहते हैं तो get() के बजाय execute() का उपयोग करें:
java
Connection.Response capped = Jsoup.connect(big)
.userAgent(USER_AGENT).timeout(60_000).execute();
Connection.Response whole = Jsoup.connect(big)
.userAgent(USER_AGENT).timeout(60_000).maxBodySize(0).execute();
2,235,648-बाइट विकिपीडिया तुलना पृष्ठ के खिलाफ चलाए जाने पर, दो प्रतिक्रियाएँ बिल्कुल उसी सीमा से भिन्न होती हैं:
text
http स्थिति, डिफ़ॉल्ट सीमा: 200
बाइट प्राप्त, डिफ़ॉल्ट सीमा: 2097152
बाइट प्राप्त, maxBodySize(0): 2235648
तालिका पंक्तियाँ, डिफ़ॉल्ट सीमा: 544
तालिका पंक्तियाँ, maxBodySize(0): 544
संदर्भ प्रविष्टियाँ, डिफ़ॉल्ट सीमा: 187
संदर्भ प्रविष्टियाँ, maxBodySize(0): 255
अंतिम अनुभाग, डिफ़ॉल्ट सीमा: संदर्भ
अंतिम अनुभाग, maxBodySize(0): बाहरी लिंक
दोनों रन ने HTTP 200 लौटाया। दोनों ने एक Document उत्पन्न किया। पृष्ठ का तुलनात्मक तालिका जो इसके लिए मौजूद है, सही ढंग से आई, 544 पंक्तियाँ इस तरह, क्योंकि वे दस्तावेज़ के शीर्ष के पास बैठते हैं।
कट के नीचे सब कुछ बस अनुपस्थित है। संदर्भ सूची ने अपने 255 प्रविष्टियों में से 68 खो दिए, और अंतिम बाहरी लिंक अनुभाग पूरी तरह से कटे हुए पेड़ में मौजूद नहीं है। एक स्क्रैपर जो तालिकाओं को पढ़ रहा है वह कभी भी नहीं देखेगा; एक स्क्रैपर जो संदर्भ एकत्रित कर रहा है वह चुपचाप एक चौथे से कम रिपोर्ट कर सकता है और फिर भी स्वस्थ दिख सकता है।
maxBodySize(0) सीमा को हटा देता है। इसे संकल्पना से सेट करें बजाय कि रिफ्लेक्स द्वारा - अप्रत्याशित प्रतिक्रिया पर अनियंत्रित पढ़ना अपनी अपनी समस्या है - लेकिन इसे चेतनता के साथ सेट करें, और सर्वर रिपोर्ट किए गए Content-Length के खिलाफ तुलना करें, जो HTTP सेमांटिक्स विशिष्टता के रूप में परिभाषित किया गया है।
जब पृष्ठ ब्राउज़र में रेंडर होता है
https://quotes.toscrape.com/js/ सीमा को चिह्नित करता है। यह अपने उद्धरणों को एक जावास्क्रिप्ट सरणी के रूप में सेवा करता है और DOM को क्लाइंट-साइड पर बनाता है, और jsoup इसे सफलतापूर्वक लाता है:
text
प्रत्यक्ष रूप से लाए गए HTML बाइट: 5479
jsoup द्वारा पाए गए उद्धरण: 0
5,479 बाइट, HTTP 200, शून्य परिणाम। मार्कअप जो jsoup को प्राप्त हुआ वास्तव में कोई div.quote तत्व नहीं रखता - वे स्क्रिप्ट चलने के बाद बनाए जाते हैं, और jsoup के पास कोई स्क्रिप्ट इंजन नहीं है।
अधिकांश गाइड इसका उत्तर ब्राउज़र स्वचालन उपकरण में बदलने के द्वारा देते हैं, जिसका अर्थ है कि निष्कर्षण कोड को भी फिर से लिखना। यह व्यापार-ऑफ़ वही है Cheerio और Puppeteer Node में दोनों पक्षों पर विद्यमान हैं, और यह Java में भी वही लागत लगाता है। संकीर्ण समाधान यह है कि केवल यह कैसे HTML आता है, उसे बदलना है। यह कार्य Scrapeless Universal Scraping API का है। यह पृष्ठ को रेंडर करता है और परिणामी HTML को एक स्ट्रिंग के रूप में लौटाता है, जिसे आप उसी पार्सर को सौंपते हैं।
JDK का अंतर्निहित HttpClient पर्याप्त है - कोई HTTP निर्भरता आवश्यक नहीं है:
java
static String render(String url) throws IOException, InterruptedException {
JsonObject input = new JsonObject();
input.addProperty("url", url);
input.addProperty("proxy_country", "US");
input.addProperty("js_render", true);
JsonObject payload = new JsonObject();
payload.addProperty("actor", "unlocker.webunlocker");
payload.add("input", input);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.scrapeless.com/api/v2/unlocker/request"))
.header("Content-Type", "application/json")
.header("x-api-token", System.getenv("SCRAPELESS_API_KEY"))
.timeout(Duration.ofSeconds(180))
hi
.POST(HttpRequest.BodyPublishers.ofString(payload.toString(), StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("अनलॉकर ने HTTP " + response.statusCode() लौटाया);
}
return JsonParser.parseString(response.body())
.getAsJsonObject().get("data").getAsString();
}
प्रतिसाद लिफाफा है {"कोड": ..., "data": "<rendered html>"}। उस स्ट्रिंग को Jsoup.parse(html, url) के साथ पार्स करें - URL पास करना मूल URI को सेट करता है, इसलिए abs:href कार्यशील रहता है - और समान चयनकर्ता विधि को चलाएं:
java
static List<String> quotes(Document doc) {
List<String> found = new ArrayList<>();
for (Element quote : doc.select("div.quote")) {
found.add(quote.selectFirst("span.text").text()
+ " -- " + quote.selectFirst("small.author").text());
}
return found;
}
text
rendered html bytes: 8940
quotes found by the same parser: 10
वही विधि, वही चयनकर्ता, वही Document API। केवल एक चीज़ जो बदली है वह है 8,940 बाइट्स कहाँ से आए। अपने API कुंजी को SCRAPELESS_API_KEY की तरह पर्यावरण में रखें, न कि स्रोत में। यूनिवर्सल स्क्रैपिंग API शुरू करने के लिए गाइड शेष अनुरोध मापदंडों को कवर करती है।
शुरू करना एक मिनट लेता है - एक मुफ्त Scrapeless खाता बनाएँ और मुफ्त योजना इस गाइड में सब कुछ कवर करती है।
इसे चलाएं
क्लास को असेंबल करने के बाद, एक कमांड इसे संकलित और चलाता है:
bash
export SCRAPELESS_API_KEY="your-api-key"
mvn -q -B compile exec:java
पुष्टि चलने का पूरा आउटपुट:
text
jsoup 1.21.1 | java 21.0.11
--- स्थैतिक पृष्ठ, सीधे पार्स किया गया ---
पृष्ठ 1 पर पुस्तकें: 20
पहला शीर्षक: ए लाइट इन द अटिक
पहली कीमत: £51.77
पहली URL: https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html
एचआरएफ जैसा कि एचटीएमएल में लिखा गया: catalogue/a-light-in-the-attic_1000/index.html
--- डिफ़ॉल्ट बॉडी-आकार सीमा ---
HTTP स्थिति, डिफ़ॉल्ट सीमा: 200
बाइट्स प्राप्त हुए, डिफ़ॉल्ट सीमा: 2097152
बाइट्स प्राप्त हुए, अधिकतमबॉडीसाइज़(0): 2235648
टेबल पंक्तियाँ, डिफ़ॉल्ट सीमा: 544
टेबल पंक्तियाँ, अधिकतमबॉडीसाइज़(0): 544
संदर्भ प्रविष्टियाँ, डिफ़ॉल्ट सीमा: 187
संदर्भ प्रविष्टियाँ, अधिकतमबॉडीसाइज़(0): 255
अंतिम अनुभाग, डिफ़ॉल्ट सीमा: संदर्भ
अंतिम अनुभाग, अधिकतमबॉडीसाइज़(0): बाहरी लिंक
--- जावास्क्रिप्ट-जनरेटेड पृष्ठ ---
सीधे_fetch की गई_html बाइट्स: 5479
jsoup द्वारा पाए गए उद्धरण: 0
--- यूनिवर्सल स्क्रैपिंग API के माध्यम से उसी पृष्ठ ---
rendered html bytes: 8940
same parser द्वारा पाए गए उद्धरण: 10
समस्या समाधान
Source option 5 अब समर्थित नहीं है। मावेन ने इसके डिफ़ॉल्ट maven-compiler-plugin के बजाय आपके प्रोग्राम के आधार बंधन को बाधित किया। <बिल्ड><प्लगइन्स> में प्लगइन संस्करण को फिक्स करें जैसा कि ऊपर दिखाया गया है।
selectFirst ने शून्य लौटाया और अगली पंक्ति ने NullPointerException फेंका। चयनकर्ता ने कुछ भी नहीं मिलाया। HTML के खिलाफ तत्व की जांच करें जो वास्तव में jsoup ने प्राप्त किया — doc.html() — न कि ब्राउज़र निरीक्षक जो दिखाता है, जो पोस्ट-स्क्रिप्ट DOM है।
गिनती उस पृष्ठ से कम है जो दिखाता है। response.bodyAsBytes().length की तुलना करें सर्वर के Content-Length के खिलाफ। यदि वे 2,097,152 पर मेल खाते हैं, तो बॉडी-आकार कैप कारण है।
HttpStatusException: 403। सर्वर ने पार्स के बजाय अनुरोध को अस्वीकार कर दिया। पहले एक यथार्थवादी उपयोगकर्ता एजेंट सेट करें; यदि पृष्ठ को भी रेंडर करने की आवश्यकता है, तो ऊपर दिए गए पिवट का उपयोग किया जाता है।
निकाले गए पाठ में मोज़िबेक। jsoup पात्रता को Content-Type हेडर से पढ़ता है, फिर मेटा टैग। जब सर्वर न तो कोई घोषित करता है, तो इसे Jsoup.parse(InputStream, String, String) में स्पष्ट रूप से संचरण करें।
निष्कर्ष
स्थैतिक HTML के लिए, jsoup और JDK पूरी टूलचेन हैं: एक निर्भरता, एक तरल अनुरोध, एक CSS चयनकर्ता API, और एक पार्स किया हुआ वृक्ष जो खराब मार्कअप को सहन करता है। दो व्यवहार जो यह तय करते हैं कि परिणाम विश्वसनीय हैं या नहीं, वे डिफ़ॉल्ट रूप से अदृश्य हैं - 2 MiB बॉडी कैप जो एक स्वस्थ-देखने वाले आंशिक दस्तावेज़ को लौटाता है, और एक क्लींट-रेंडर किया गया पृष्ठ पर खाली परिणाम सेट।
दोनों की जांच करना सस्ता है। प्राप्त बाइट्स की तुलना Content-Length के खिलाफ करें, और चयनकर्ता की गिनती की तुलना करें जो पृष्ठ प्रदर्शित करता है। जब दूसरी जांच विफल होती है क्योंकि मार्कअप खाली आता है, तो सुधार परिवहन को बदलना है और पार्सर को अकेला छोड़ देना है।
तैयार हैं कोशिश करने के लिए? Scrapeless मुफ्त योजना के साथ शुरू करें और उपलब्ध वर्तमान मूल्य निर्धारण देखें उच्च मात्रा के लिए।
अक्सर पूछे जाने वाले सवाल
प्र. क्या jsoup अपने आप में पर्याप्त है, या मुझे Selenium की आवश्यकता है?
सर्वर-रेन्डर्ड HTML के लिए, jsoup अपने आप में पर्याप्त है और यह काफी तेज है, क्योंकि यह कभी भी एक ब्राउज़र शुरू नहीं करता। आपको एक रेंडरिंग चरण की आवश्यकता केवल तब होती है जब डेटा JavaScript द्वारा बनाया जाता है — और जैसा कि ऊपर के मापन में दिखाया गया है, वह चरण एक HTTP कॉल हो सकता है जो रेंडर किया गया HTML लौटाता है न कि आपके प्रक्रिया में एक पूर्ण ब्राउज़र।
प्र. मैं कैसे जानूंगा कि क्या एक पृष्ठ को JavaScript की आवश्यकता है पहले चयनकर्ताओं को लिखने से पहले?
एक साधारण jsoup फेच से doc.html() प्रिंट करें और इसे उस मान के लिए खोजें जिसे आप पृष्ठ पर देख सकते हैं। यदि उस स्ट्रिंग में मान अनुपस्थित है लेकिन ब्राउज़र में दिखाई दे रहा है, तो इसे क्लाइंट-साइड पर रेंडर किया जा रहा है। दृश्य गणना के खिलाफ चयनकर्ता गणना की तुलना वही बात पकड़ती है।
प्र. maxBodySize बदलने का व्यावहारिक कारण क्या है?
डिफ़ॉल्ट 2 MiB रखता है, जो कई सूची, आर्काइव, और तुलना पृष्ठों से छोटा है। यदि आपका लक्ष्य इससे अधिक है, तो कटा हुआ सब कुछ पार्स किए गए पेड़ से गायब है बिना किसी त्रुटि के। जब आपको पूरी दस्तावेज़ की आवश्यकता हो, तो maxBodySize(0) सेट करें, और जब यह महत्वपूर्ण हो, तब प्राप्त बाइट्स की तुलना Content-Length से करें।
प्र. क्या jsoup अपमानजनक HTML को संभालता है?
हाँ। यह उसी त्रुटि-रिकवरी नियमों को लागू करता है जो एक ब्राउज़र करता है, इसलिए बंद न किए गए टैग और गलत नेस्टेड तत्व अभी भी एक क्वेरी करने योग्य पेड़ का उत्पादन करते हैं। यह असली दुनिया के पृष्ठों के लिए एक सख्त XML पार्सर पर इसे पसंद करने का मुख्य कारण है।
प्र. क्या Java के साथ स्क्रेपिंग कानूनी है?
भाषा कानूनी प्रश्न के लिए अप्रासंगिक है। जो महत्वपूर्ण है वह है डेटा जो आप इकट्ठा करते हैं, साइट की शर्तें, रोबोटों के निर्देश जो आप मानते हैं, और जो न्यायालय क्षेत्र में आप कार्य करते हैं। सार्वजनिक पृष्ठों पर संग्रह को सीमित करें, अनुरोध की मात्रा को संयमित रखें, और व्यक्तिगत डेटा से संबंधित किसी भी चीज़ के लिए कानूनी सलाह प्राप्त करें।
स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।



