अनंत स्क्रोल को स्क्रैप करना: यह अनुमान लगाना बंद करें कि कितनी बार स्क्रोल करना है
Advanced Data Extraction Specialist
TL;DR:
- अनंत-scroll पृष्ठ अपने HTML में लगभग कुछ भी नहीं भेजता है। यहाँ उपयोग किया गया डेमो 2,671 बाइट्स भेजता है जिसमें शून्य उद्धरण तत्व होते हैं; हर आइटम उसके बाद आता है।
- एक निश्चित स्क्रॉल गिनती वह विफलता है जिसे सभी भेजते हैं। पांच स्क्रॉल ने 100 में से 60 आइटम लौटाए — कोई त्रुटि, कोई चेतावनी नहीं, 40% डेटा गायब के साथ एक साफ़ बाहर निकलना।
- इसके बजाय तब तक स्क्रॉल करें जब तक आइटम की गिनती बढ़ना बंद न हो जाए। यह सभी 100 लौटाता है।
- स्क्रॉल की संख्या साइट की एक विशेषता नहीं है। एक ही लूप को स्थानीय रूप से 11 स्क्रॉल क्रियाएं और एक क्लाउड ब्राउज़र पर 3 की आवश्यकता थी, दोनों बार 100 तक पहुँचते हैं।
- पृष्ठ ने 10 कॉल्स की
/api/quotes?page=N। उस एंडपॉइंट को सीधे पढ़ने से वही 100 आइटम मिलते हैं औरhas_nextकी रिपोर्ट होती है, इसलिए लूप जानता है कि यह कब समाप्त हुआ। - इस गाइड में क्लाउड-ब्राउज़र रन के लिए Scrapeless मुफ्त योजना कवर करती है।
अनंत स्क्रॉल चुपचाप स्क्रैपर्स को तोड़ता है। अनुरोध सफल होता है, चयनकर्ता मेल खाते हैं, स्क्रिप्ट शून्य पर बाहर निकलती है — और डेटासेट छोटा होता है। रन में कुछ भी नहीं बताता कि आप कितने चूक गए, क्योंकि पृष्ठ ने कभी वादा नहीं किया कि वहाँ कितना था।
यह गाइड एक लाइव डेमो पृष्ठ पर इसका माप करती है। यह लूप का निर्माण करती है जिसे ज्यादातर ट्यूटोरियल भेजते हैं, यह बताती है कि यह ठीक से क्या छोड़ता है, इसे एक ऐसा लूप से बदलती है जिसमें वास्तविक स्टॉपिंग कंडीशन होती है, और फिर पृष्ठ द्वारा हमेशा बनाई जा रही अनुरोध को खोजती है।
सर्वर वास्तव में क्या भेजता है
ब्राउज़र के बिना पृष्ठ को लाएं और इसे पार्स करने के लिए कुछ भी नहीं है:
python
def served_html() -> tuple[int, int]:
request = urllib.request.Request(SCROLL_URL, headers={"User-Agent": UA})
with urllib.request.urlopen(request, timeout=45) as response:
html = response.read().decode("utf-8", "replace")
return len(html), html.count('class="quote"')
text
स्क्रॉल पृष्ठ बाइट्स : 2671
HTML में उद्धरण तत्व : 0
2,671 बाइट्स और एक भी आइटम नहीं। मार्कअप एक खोल है; सामग्री पृष्ठ लोड होने के बाद स्क्रिप्ट द्वारा लायी जाती है, आमतौर पर जब नीचे के निकट एक संवेदनशील तत्व दृश्य में आता है — यह तंत्र Intersection Observer विशेषSpecification द्वारा परिभाषित है। एक CSS चयनकर्ता इससे अधिक साफ़ नहीं हो सकता — यह कुछ भी नहीं होता क्योंकि वहाँ कुछ भी नहीं होता।
इंस्टॉल करें
bash
pip install playwright
playwright install chromium
दूसरा कमांड ब्राउज़र बाइनरी डाउनलोड करता है; केवल पिप पैकेज लांच नहीं करेगा। सत्यापन चलाने में Playwright 1.59.0 का उपयोग किया गया।
लूप जिसे सभी भेजते हैं
मानक नुस्खा निश्चित संख्या में स्क्रॉल करना और फिर पृष्ठ पढ़ना है। page.mouse.wheel() एक वास्तविक पहिया घटना वितरित करता है जिसकी तरह UI Events विशेषSpecification परिभाषित करता है, जो कि पृष्ठ का अपना श्रोता भी प्रतीक्षा कर रहा है:
python
def scroll_fixed(page, times: int, pause: float) -> int:
for _ in range(times):
page.mouse.wheel(0, 20000)
time.sleep(pause)
return page.locator("div.quote").count()
स्थायी पृष्ठ के खिलाफ पांच स्क्रॉल:
text
5 स्क्रॉल के बाद उद्धरण : 60
व्यतीत सेकंड : 5.1
साठ आइटम। पृष्ठ में सौ हैं। स्क्रिप्ट ने कुछ नहीं उठाया, चयनकर्ता कार्य कर गया, और रन बाहरी से सफल प्रतीत होता है — यही इस बग का महंगा संस्करण बनाता है। पाँच चुनें क्योंकि यह एक बार काम किया, और हर बाद वाला रन चुपचाप 40% कमियों को विरासत में लेता है।
संख्याओं को बढ़ाना भी एक सुधार नहीं है। यह अंडर-कलेक्शन को बर्बाद स्क्रॉल के साथ बदलता है, और यह अभी भी साइट के बारे में एक अनुमान को कोड करता है जो जब चाहे अपनी बैच आकार बदल सकता है।
तब तक स्क्रॉल करें जब तक यह बढ़ना बंद न हो जाए
वास्तव में आप जो कंडीशन चाहते हैं वह प्रेक्षणीय है: आइटम काउंट बढ़ता रहे, जबकि स्क्रॉल करें, और जब यह स्थिर हो जाए तब रोकें।
python
def scroll_until_stable(page, pause: float, no_growth_limit: int = 2) -> tuple[int, int]:
seen = page.locator("div.quote").count()
scrolls = 0
stable = 0
while stable < no_growth_limit:
page.mouse.wheel(0, 20000)
time.sleep(pause)
scrolls += 1
count = page.locator("div.quote").count()
if count == seen:
stable += 1
else:
stable = 0
seen = count
return seen, scrolls
no_growth_limit यह निर्धारित करता है कि आपको पृष्ठ समाप्त होने के विश्वास के लिए कितना सबूत चाहिए। एक सपाट पढ़ाई कोई प्रमाण नहीं है — यात्रा में अभी भी एक बैच लगता है जिसकी पहचान सूची के अंत के समान होती है। दो लगातार सपाट पढ़ाई की आवश्यकता एक अतिरिक्त स्क्रॉल की कीमत चुकाती है और उस अस्पष्टता को हटा देती है।
text
एकत्रित उद्धरण : 100
किया गया स्क्रॉल क्रियाएं : 11
व्यतीत सेकंड : 11.1
पृष्ठ ने किए गए एपीआई कॉल्स: 10
पहला एपीआई URL : https://quotes.toscrape.com/api/quotes?page=1
सभी 100, और लूप ने जोर देकर गिनती की बजाय इसे उतारा। नोट करें कि व्यतीत आंकड़ा मुख्य रूप से इस लूप द्वारा चुने गए विराम का समय है — प्रत्येक एक सेकंड पर ग्यारह स्क्रॉल। यह लूप की एक विशेषता है, साइट की नहीं।
स्क्रॉल गिनती साइट की एक विशेषता नहीं है
हराता समान कार्य एक बादल ब्राउज़र के खिलाफ स्थानीय क्रोमियम के बजाय:
text
एकत्रित उद्धरण : 100
स्क्रोल क्रियाएँ प्रदर्शन की गईं : 3
समान कार्य, समान पृष्ठ, समान 100 आइटम — 3 स्क्रोल क्रियाएँ 11 के बजाय। व्यू पोर्ट की ऊँचाई और प्रत्येक बैच कितनी तेजी से आता है यह तय करता है कि एक व्हील इवेंट पृष्ठ को कितना आगे बढ़ाता है, और इनमें से कोई भी आपके नियंत्रण में नहीं है। व्यू पोर्ट का आकार जिस पर स्क्रोलिंग को मापा जाता है वह CSSOM व्यू मॉड्यूल द्वारा निर्दिष्ट है। कोई भी हार्ड-कोडेड स्क्रॉल गणना एक मशीन के विंडो आकार के अनुसार कैलिब्रेट की गई है।
उस रन ने Scrapeless स्क्रेपिंग ब्राउज़र का उपयोग किया, जो Playwright को क्रोम देव उपकरण प्रोटोकॉल के माध्यम से जोड़ता है। केवल कनेक्शन की रेखा बदली जाती है:
python
endpoint = (
"wss://browser.scrapeless.com/api/v2/browser"
f"?token={os.environ['SCRAPELESS_API_KEY']}&sessionTTL=180&proxyCountry=ANY"
)
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(endpoint, timeout=90000)
page = browser.new_page()
page.goto(SCROLL_URL, wait_until="domcontentloaded")
cloud_total, cloud_scrolls = scroll_until_stable(page, pause=1.0)
connect_over_cdp chromium.launch() को बदलता है और Chrome DevTools प्रोटोकॉल के माध्यम से एक सॉकेट पर बात करता है, न कि स्थानीय प्रक्रिया से, इसलिए स्क्रोल लूप, चयनकर्ता और निष्कर्षण बिल्कुल वैसा ही रहता है जैसा थे। SCRAPELESS_API_KEY के रूप में वातावरण में कुंजी रखें; स्क्रेपिंग ब्राउज़र का परिचय शेष सत्र के पैरामीटर का दस्तावेजीकरण करता है।
शुरू करने में एक मिनट लगता है — एक मुफ्त Scrapeless खाता बनाएं और मुफ्त योजना इस रन को कवर करती है।
देखें कि पृष्ठ क्या अनुरोध कर रहा है
स्क्रोल लूप पृष्ठ से डेटा लाने का एक तरीका है। अनुरोधों को सुनना यह दिखाता है कि यह क्या लाता है:
python
calls: list[str] = []
context = browser.new_context(user_agent=UA)
context.on("request", lambda r: calls.append(r.url) if "/api/quotes" in r.url else None)
दस कॉल, पहला https://quotes.toscrape.com/api/quotes?page=1। पृष्ठ एक JSON अंतिम बिंदु को पन्ना कर रहा है और परिणामों को प्रस्तुत कर रहा है; स्क्रोल करना केवल ट्रिगर है।
उस अंतिम बिंदु को सीधे पढ़ा जा सकता है, और यह उस प्रश्न का उत्तर देता है जिसे स्क्रोल लूप को अनुकरण करना पड़ा:
python
def read_api() -> tuple[int, int]:
quotes = 0
page = 1
while True:
request = urllib.request.Request(API_URL.format(page=page), headers={"User-Agent": UA})
with urllib.request.urlopen(request, timeout=45) as response:
payload = json.loads(response.read().decode())
quotes += len(payload["quotes"])
if not payload["has_next"]:
return quotes, page
page += 1
text
एकत्रित उद्धरण : 100
एपीआई पृष्ठ अनुरोध किए गए : 10
व्यतीत_seconds : 3.8
समान 100 आइटम, has_next एक स्पष्ट समाप्ति स्थिति के रूप में "गणना बदलना बंद कर दी" के बजाय। प्रतिक्रिया पहले से ही संरचित है, इसलिए आपके और फ़ील्ड के बीच कोई चयनकर्ता नहीं बैठता है — जिसका अर्थ है कि कोई चयनकर्ता भी नहीं टूटा जब मार्कअप को फिर से शैलीबद्ध किया गया।
इससे पहले कि कोई स्क्रोल लूप लिखा जाए इस पर जांच करना उचित है। यह हमेशा मौजूद नहीं होता: कई साइटें स्क्रोल पर सर्वर-साइड रेंडर करती हैं, अपने अनुरोधों पर हस्ताक्षर करती हैं, या JSON के बजाय HTML फ़्रागमेंट लौटाती हैं। जब यह मौजूद है, तो यह अधिक टिकाऊ लक्ष्य होता है, और ब्राउज़र अभी भी आपको दिखाता है कि यह वहाँ है। अन्य पन्नीकरण आकारों के लिए — अगले बटन, क्रमांकित पृष्ठ, लोड-ओर — पूर्ण पन्नीकरण गाइड प्रत्येक प्रकार को कवर करता है।
इसे चलाएँ
bash
export SCRAPELESS_API_KEY="your-api-key"
python3 scroll_demo.py
पुष्टि रन से पूर्ण आउटपुट:
text
playwright 1.59.0
--- जो सर्वर वास्तव में भेजता है ---
स्क्रोल पृष्ठ बाइट्स : 2671
html में उद्धरण तत्व: 0
--- स्क्रोल का निश्चित संख्या ---
5 स्क्रोल के बाद उद्धरण : 60
व्यतीत_seconds : 5.1
--- गणना बढ़ना बंद होने तक स्क्रोल ---
एकत्रित उद्धरण : 100
स्क्रोल क्रियाएँ प्रदर्शन की गईं : 11
व्यतीत_seconds : 11.1
पृष्ठ ने बनाए एपीआई कॉल : 10
पहला एपीआई यूआरएल : https://quotes.toscrape.com/api/quotes?page=1
--- स्क्रेपिंग ब्राउज़र पर समान लूप ---
एकत्रित उद्धरण : 100
स्क्रोल क्रियाएँ प्रदर्शन की गईं : 3
--- सीधे उसी एपीआई को पढ़ना ---
एकत्रित उद्धरण : 100
एपीआई पृष्ठ अनुरोध किए गए : 10
व्यतीत_seconds : 3.8
ब्रोसर/एपीआई समय अनुपात: 3x
निम्नलिखित पंक्ति में अनुपात ब्रोसर लूप की वॉल क्लॉक को सीधे पठन के मुकाबले में दर्शाता है। ब्रोसर पक्ष का अधिकांश हिस्सा स्क्रॉल के बीच जानबूझकर एक सेकंड का ठहराव है, इसलिए इसे एक ऐसी स्थिति के लिए पृष्ठ को पॉलिंग करने की लागत के रूप में पढ़ें जो कभी रिपोर्ट नहीं करती — न कि ब्रोसर स्वयं के एक बेंचमार्क के रूप में।
समस्या निवारण
गिनती कभी भी नहीं रुकती। कुछ पृष्ठ अपने सामग्री को लूप करते हैं। लूप को अधिकतम स्क्रॉल गिनती के साथ सीमित करें और स्थिरता जांच के साथ मिलाकर इसे परे करें, और उस सीमा तक पहुँचने को पृष्ठ का निरीक्षण करने का संकेत मानें न कि एक पूर्ण रूप से चले जाने के रूप में।
स्क्रॉल करने के बाद भी शून्य आइटम। कंटेनर विंडो के बजाय स्क्रॉल हो सकता है। खुद तत्व को स्क्रॉल करें page.locator(...).hover() के साथ इसके बाद page.mouse.wheel(...), या अंतिम आइटम पर scroll_into_view_if_needed() का कॉल करें।
गिनती बढ़ती है, फिर पृष्ठ शून्य हो जाता है। लंबे सूचियाँ अक्सर वर्चुअलाइज की जाती हैं, इसलिए पंक्तियाँ तब DOM से हटा दी जाती हैं जब वे व्यूपोर्ट से बाहर जाती हैं। आइटम को इकट्ठा करें जबकि आप आगे बढ़ रहे हैं न कि अंत में सभी को पढ़ें।
यह मौलिक रूप से काम करता है और न कि हेडलेस। दोनों के बीच व्यूपोर्ट का आकार भिन्न होता है, जो यह बदलता है कि एक व्हील इवेंट कितनी दूर जाता है और क्या लोडर दृश्य में आता है। संधर्भ पर एक स्पष्ट व्यूपोर्ट सेट करें।
playwright._impl._errors.Error: Executable doesn't exist। ब्राउज़र बाइनरी कभी डाउनलोड नहीं की गई थी। playwright install chromium चलाएं।
निष्कर्ष
अनंत स्क्रॉल "क्या मैंने सब कुछ प्राप्त किया?" को एक सवाल में बदल देता है जिसका उत्तर आपका स्क्रैपर खुद देता है, और एक निश्चित स्क्रॉल गिनती उसे अनुमान लगाकर उत्तर देती है। यहाँ की माप दर्शाती है कि इसका क्या खर्च है: 100 में से 60 आइटम पांच स्क्रॉल से, और एक स्क्रॉल गिनती जो अलग ब्राउज़र में जाने से 11 से 3 में बदल गई।
आपस में देखी गई स्थिति पर लूप करें — लगातार पठन में आइटम की गिनती स्थिर — न कि किसी संख्या पर जिसे आपने चुना। और लूप लिखने से पहले, पृष्ठ की अनुरोधों पर नजर रखें: जब सामग्री JSON के साथ has_next फ्लैग के रूप में आती है, तो पृष्ठ ने पहले ही आपको बता दिया है कि आप कब समाप्त हो गए हैं।
क्या आप इसे आज़माने के लिए तैयार हैं? Scrapeless मुफ्त योजना से शुरुआत करें और वर्तमान मूल्य निर्धारण उच्च मात्रा के लिए देखें।
सामान्य प्रश्न
प्रश्न: मुझे कितनी बार स्क्रॉल करना चाहिए?
एक संख्या न चुनें। उपरोक्त माप ने एक लूप में दो ब्राउज़रों के खिलाफ 100 आइटम के लिए एक में 11 स्क्रॉल और दूसरे में 3 की आवश्यकता थी, क्योंकि व्यूपोर्ट की ऊँचाई और बैच समय निर्धारित करते हैं कि प्रत्येक व्हील इवेंट कितनी दूर जाता है। जबकि आइटम की गिनती बढ़ रही है तब लूप करें और उसके स्थिर रहने पर रुकें।
प्रश्न: मुझे कैसे पता चलेगा कि पृष्ठ ने सब कुछ लोड करना समाप्त कर दिया है?
दो लगातार पठन जिनमें आइटम की गिनती अपरिवर्तित है यह व्यावहारिक संकेत है, क्योंकि एक एकल फ्लैट रीड एक बैच के उड़ान में होने से भिन्न नहीं होती। यदि पृष्ठ की अंतर्निहित अनुरोध एक फ्लैग जैसे has_next को उजागर करती है, तो यह एक निश्चित उत्तर है न कि एक अनुमान।
प्रश्न: क्या मुझे अनंत स्क्रॉल के लिए ब्रोसर की आवश्यकता है?
अधिकतर नहीं। यहाँ का डेमो पृष्ठ अपने सामग्री को एक JSON अंतिम बिंदु से लोड करता है जो सभी 100 आइटमों के लिए सीधे पढ़ा जा सकता है। ब्रोसर अब भी वह तरीका है जिससे आप उस अंतिम बिंदु को खोजते हैं — एक अनुरोध श्रोता को संलग्न करें, एक बार स्क्रॉल करें, और URLs पढ़ें। साइटें जो स्क्रॉल पर सर्वर-साइड रेंडर करती हैं या अपने अनुरोधों को हस्ताक्षरित करती हैं, उनके लिए ब्रोसर की आवश्यकता होती है।
प्रश्न: मेरा स्क्रैपर पृष्ठ पर प्रदर्शित आइटम से कम आइटम क्यों लौटाता है?
या तो लूप जल्दी रुक गया, या सूची वर्चुअलाइज की गई थी और पंक्तियाँ दृश्य से बाहर जाने पर DOM से हटा दी गईं। हर स्क्रॉल के बाद आइटम की गिनती प्रिंट करें: एक गिनती जो चढ़ती है और फिर गिरती है वर्चुअलाइजेशन का संकेत देती है, और एक गिनती जो तब भी चढ़ती है जब लूप समाप्त होता है यह संकेत देती है कि लूप बहुत जल्दी रुक गया है।
प्रश्न: क्या wait_until="networkidle" इसे हल करता है?
नहीं। यह प्रारंभिक लोड के स्थिर होने की प्रतीक्षा करता है, जो किसी भी स्क्रॉलिंग ने फेच को ट्रिगर करने से पहले होता है। बैच स्क्रॉल इवेंट्स के प्रति प्रतिक्रिया में आते हैं, इसलिए पृष्ठ एक ही समय में सुस्त और लगभग खाली हो सकता है — जो कि वह 2,671-बाइट स्थिति है जो शुरू में मापी गई थी।
स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।



