2025, Nov 01 07:32
Selenium में stale elements से बचाव: WebDriverWait और href बफरिंग
Selenium में WebDriverWait का उपयोग सीखें और नेविगेशन के बाद stale element त्रुटि से बचें: पहले hrefs लें, फिर हर पेज पर एलिमेंट को दुबारा लोकेट करें—भरोसेमंद
Selenium WebElements के किसी कलेक्शन पर लूप चलाना, हर लिंक पर जाना और थोड़ा‑सा डेटा निकालना सुनने में बहुत आसान लगता है—जब तक कि बीच में नेविगेशन न आ जाए। जैसे ही ब्राउज़र मौजूदा पेज से हटता है, पहले से मिले एलिमेंट अमान्य हो जाते हैं, और सीधी‑सादी वेट रणनीतियाँ TypeError या StaleElementReferenceException के साथ तुरंत टूट जाती हैं। नीचे इस फेल्योर मोड की साफ व्याख्या और उसका भरोसेमंद समाधान दिया गया है।
समस्या की रूपरेखा
शुरुआत एक फ़ंक्शन से होती है जो लिस्टिंग पेज खोलता है, कार्ड्स की सूची पर इटरेट करता है, हर कार्ड के भीतर मौजूद एंकर तक पहुँचता है, डिटेल पेज पर जाता है, एक तारीख निकालता है और फिर वही चक्र दोहराता है। कोड कुछ ऐसा दिखता है:
def pull_posting_dates(browser, start_url):
date_bins = []
waiter = WebDriverWait(browser, 5)
browser.get(start_url)
# cards - वेब एलिमेंट्स की सूची
cards = browser.find_elements(By.CLASS_NAME, 'object-cards-block.d-flex.cursor-pointer')
for card in cards:
try:
anchor = waiter(card, 5).until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'div.main-container-margins.width-100 > a')
)
)
except NoSuchElementException:
print('no such element')
# लिंक पर जाएँ
browser.get(anchor.get_attribute('href'))
stamp = browser.find_element(
By.XPATH, '//*[@id="Data"]/div/div/div[4]/div[2]/span'
).text
date_bins.append(stamp)
यह क्यों टूटता है
पहली समस्या है TypeError: WebDriverWait is not callable. WebDriverWait में WebElement पास करते हुए waiter(card, 5) लिखना मानो वेट ऑब्जेक्ट को एक फ़ंक्शन की तरह कॉल करना है—जबकि वह कॉल करने योग्य नहीं है। सही तरीका यह है कि ड्राइवर और टाइमआउट के साथ WebDriverWait बनाएँ, और फिर expected condition देकर until को कॉल करें। किसी एकल एलिमेंट से वेट को इस तरह “बाँधने” की कोशिश करने पर यही callable त्रुटि आती है।
दूसरी और अधिक बुनियादी दिक्कत नेविगेशन है। एक बार get() कॉल होते ही ड्राइवर का “व्यू” पिछले पेज से हट जाता है। Selenium ब्राउज़र की मेमरी में DOM ऑब्जेक्ट्स के रेफरेंस रखता है। नई पेज पर नेविगेट करने से वह DOM पूरी तरह बदल जाता है, इसलिए पिछले पेज से संग्रहीत रेफरेंस अमान्य हो जाते हैं। यही वजह है कि पहली नेविगेशन के बाद StaleElementReferenceException दिखती है और get() से पहले इकट्ठे किए गए एलिमेंट दुबारा इस्तेमाल करने पर फेल हो जाते हैं। पूरा नेविगेशन न होने पर भी, DOM में बदलाव से ऑब्जेक्ट्स की मेमरी स्थिति बदल सकती है, इसलिए एलिमेंट्स को फिर से ढूँढना पड़ता है।
उपाय: पहले सभी HREF इकट्ठा करें, फिर हर पेज पर जाएँ
इसे संभालने के कई तरीके हैं, पर सबसे सरल उपाय है—नेविगेट करने से पहले सभी href एट्रिब्यूट निकाल लेना। जब सभी लिंक एक Python सूची में बफर हो जाएँ, तो आप सुरक्षित रूप से उन पर इटरेट कर सकते हैं, हर लिंक के लिए get() कॉल कर सकते हैं और डिटेल पेज पर लक्ष्य एलिमेंट का इंतज़ार कर सकते हैं। यह सीधा, भरोसेमंद तरीका है और stale रेफरेंस दोबारा उपयोग करने की समस्या से बचाता है। यह सबसे कुशल पैटर्न नहीं है, लेकिन समझने में आसान है और काम करता है।
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
SOURCE_URL = "https://upn.ru/kupit/kvartiry"
def scrape_post_dates(session, entry_url):
session.get(entry_url)
gate = WebDriverWait(session, 10)
predicate = EC.presence_of_all_elements_located
container = (By.CLASS_NAME, "object-cards-block.d-flex.cursor-pointer")
links = []
for node in gate.until(predicate(container)):
a_tag = node.find_element(
By.CSS_SELECTOR, "div.main-container-margins.width-100 > a"
)
href_val = a_tag.get_attribute("href")
if href_val is not None:
links.append(href_val)
stamps = []
for link_url in links:
session.get(link_url)
single_pred = EC.presence_of_element_located
spot = (By.XPATH, "//*[@id='Data']/div/div/div[4]/div[2]/span")
target = gate.until(single_pred(spot))
txt = target.text
stamps.append(txt)
print(txt)
return stamps
if __name__ == "__main__":
opts = ChromeOptions()
opts.add_argument("--headless=new")
with Chrome(options=opts) as drv:
results = scrape_post_dates(drv, SOURCE_URL)
उदाहरण आउटपुट (आंशिक):
Размещено: 16.06.2025 11
Размещено: 06.06.2025 6
Размещено: 02.06.2025 57
Размещено: 19.04.2025 42
Размещено: 03.04.2025 29
Размещено: 25.03.2025 63
...
यह क्यों मायने रखता है
यह समझना अहम है कि Selenium DOM नोड्स को कैसे ट्रैक करता है। नेविगेशन या किसी बड़े DOM अपडेट के बाद पुराने रेफरेंस वैध नहीं रहते, क्योंकि पेज स्ट्रक्चर बदल जाता है। भरोसेमंद पैटर्न यह है कि शुरुआत में ही URLs या IDs जैसे प्रिमिटिव डेटा निकाल लें, फिर नेविगेट करके नई पेज पर ज़रूरी एलिमेंट्स को दुबारा लोकेट करें। इससे स्क्रैपर स्थिर रहता है, बेवजह के flaky वेट्स घटते हैं, और फेल्योर अलग‑थलग करना आसान होता है।
अगर आपको बेहतर थ्रूपुट चाहिए तो मल्टीथ्रेडेड तरीका कहीं अधिक प्रभावी होगा, लेकिन सिद्धांत वही रहता है: पेज लोड्स के बीच WebElement रेफरेंस पर कभी भरोसा न करें।
अंतिम टिप्पणियाँ
WebDriverWait को फ़ंक्शन समझकर कॉल न करें और न ही उसे WebElements से जोड़ें। एक ड्राइवर संदर्भ पर एक ही वेट रखें और until को expected conditions दें। नेविगेशन के पार एलिमेंट्स को दोबारा उपयोग न करें; पहले hrefs इकट्ठा करें, फिर उन पर इटरेट करते हुए हर गंतव्य पेज पर ज़रूरी चीज़ों को दुबारा ढूँढें। इस पैटर्न से TypeError खत्म होती है, stale रेफरेंस से बचाव होता है और स्क्रैपर पूर्वानुमेय बनता है। बाद में अगर परफ़ॉर्मेंस चिंता बने, तो इसी दृष्टिकोण पर concurrency आज़माएँ।