2025, Nov 01 02:32

lxml में find(), findall() और xpath() का फर्क: कब क्या चुनें

इस लेख में lxml के find(), findall() और xpath() के बीच अंतर समझें: ElementPath बनाम पूर्ण XPath, सही मेथड कब चुनें, उदाहरण कोड और आम गलतियों से बचाव.

lxml के साथ काम करते समय, अक्सर हम आदतन xpath() का ही इस्तेमाल कर लेते हैं। यह शक्तिशाली है और परिचित भी। लेकिन अगर आपने देखा है कि xpath() की कुछ कॉल्स को findall() से बदला जा सकता है, तो आप सही हैं — दोनों तरीकों का उद्देश्य कई जगह मिलता-जुलता है, पर उनकी क्षमता अलग है। फर्क समझना आपको सही उपकरण चुनने और सूक्ष्म गलतियों से बचने में मदद करता है।

वास्तविक अंतर क्या है?

lxml.etree, ElementTree और Element पर उपलब्ध find, findall और findtext मेथड्स की सरल पाथ सिंटैक्स (मूल ElementTree लाइब्रेरी की ElementPath) को सपोर्ट करता है। lxml-विशिष्ट विस्तार के रूप में, ये क्लासेस xpath() मेथड भी देती हैं, जो पूरी XPath सिंटैक्स के एक्सप्रेशंस और कस्टम एक्सटेंशन फ़ंक्शंस का समर्थन करता है।

ElementPath

ElementTree लाइब्रेरी के साथ एक सरल, XPath-जैसी पाथ भाषा आती है, जिसे ElementPath कहा जाता है। मुख्य अंतर यह है कि ElementPath अभिव्यक्तियों में आप {namespace}tag संकेतन का उपयोग कर सकते हैं। हालांकि, वैल्यू तुलना और फ़ंक्शंस जैसी उन्नत सुविधाएँ उपलब्ध नहीं हैं।

Element.findall() केवल उन तत्वों को ढूंढता है जिनका टैग वर्तमान एलिमेंट के प्रत्यक्ष बच्चे होते हैं.
Element.find() दिए गए टैग वाला पहला बच्चा लौटाता है, और Element.text एलिमेंट की टेक्स्ट सामग्री तक पहुँच देता है.

यानी findall() का पहला आर्ग्युमेंट कोई पूर्ण XPath अभिव्यक्ति नहीं होता। यह एक साधारण ElementPath स्ट्रिंग है, जो प्रत्यक्ष बच्चों के बीच सीधी, संरचनात्मक नेविगेशन के लिए उपयुक्त है। इसके विपरीत, xpath() संदर्भ नोड पर पूरी XPath अभिव्यक्ति का मूल्यांकन करता है।

समस्या का उदाहरण: findall() को ऐसे इस्तेमाल करना मानो वह पूरी XPath समझता हो

नीचे दिया गया स्निपेट findall() के जरिए वंशज td तत्वों से टेक्स्ट नोड्स निकालने की कोशिश करता है। यहीं गलती होती है: ElementPath, text() जैसी फ़ंक्शंस को सपोर्ट नहीं करता।

from lxml import etree
xml_blob = "<table><tr><td>One</td><td>Two</td></tr></table>"
root_node = etree.fromstring(xml_blob)
# यह एक फ़ंक्शन (text()) का उपयोग करता है और पूर्ण XPath समर्थन मानकर चलता है।
# ElementPath इसे संभाल नहीं सकता, इसलिए यह कॉल विफल होती है।
cell_texts = root_node.findall(".//td/text()")

यह विफलता lxml की बग नहीं है। यह ElementPath की क्षमताओं और पूर्ण XPath इंजन की सुविधाओं के बीच असंगति है। जैसा ऊपर बताया गया, ElementPath में फ़ंक्शंस जैसी उन्नत सुविधाएँ लागू नहीं हैं।

क्यों विफल होता है: ElementPath बनाम पूर्ण XPath

find(), findall() और findtext() एक सरल, XPath-जैसी भाषा ElementPath पर निर्भर करते हैं। इसका दायरा जानबूझकर सीमित रखा गया है। व्यावहारिक रूप से मुख्य सीमा यह है कि ये मेथड्स प्रत्यक्ष बच्चों तक संरचनात्मक नेविगेशन के लिए बने हैं, न कि फ़ंक्शंस, वैल्यू तुलना या जटिल एक्सिस जैसी अभिव्यक्ति-स्तर की सुविधाओं के लिए। दूसरी ओर, xpath() पूरी XPath अभिव्यक्ति चलाता है, जिसमें text() जैसे फ़ंक्शंस भी शामिल हो सकते हैं।

सीख साफ है: जहाँ पूर्ण XPath क्षमताएँ चाहिए, वहाँ xpath() का उपयोग करें। जहाँ सिर्फ टैग के आधार पर प्रत्यक्ष बच्चों तक जाना है, वहाँ find*() पर्याप्त है।

समाधान: पूर्ण अभिव्यक्तियों के लिए xpath(), प्रत्यक्ष बच्चों के लिए findall()

वंशज td तत्वों से टेक्स्ट नोड्स निकालने के लिए xpath() अपनाएँ।

from lxml import etree
xml_payload = "<table><tr><td>One</td><td>Two</td></tr></table>"
doc_root = etree.fromstring(xml_payload)
# पूर्ण XPath अभिव्यक्ति: फ़ंक्शंस और वंशजों के साथ काम करती है
texts = doc_root.xpath(".//td/text()")
# texts == ["One", "Two"]

दूसरी ओर, अगर आपको केवल टैग नाम से प्रत्यक्ष बच्चों पर चलना है, तो findall() ही रखें। यही ElementPath का लक्षित उपयोग है।

from lxml import etree
snippet = "<table><tr><td>One</td><td>Two</td></tr></table>"
root_el = etree.fromstring(snippet)
# ElementPath के जरिए प्रत्यक्ष बच्चों का चयन
rows = root_el.findall("tr")
first_row_cells = rows[0].findall("td")
values = [cell.text for cell in first_row_cells]
# values == ["One", "Two"]

वास्तव में findall() का path आर्ग्युमेंट क्या है

path पैरामीटर एक ElementPath स्ट्रिंग होता है। यह ऊपर बताई गई “सरल पाथ सिंटैक्स” है, कोई पूर्ण XPath अभिव्यक्ति नहीं। इसलिए text() जैसी संरचनाएँ वहाँ काम नहीं करतीं, जबकि xpath() में करती हैं।

यह भेद क्यों अहम है

अभिव्यक्ति की ज़रूरत के अनुसार सही मेथड चुनना नाज़ुक कोड और अजीब त्रुटियों से बचाता है। अगर आपको पूर्ण XPath फीचर्स चाहिए, तो xpath() लें। अगर काम सिर्फ टैग के आधार पर प्रत्यक्ष बच्चों को चुनने और क्वेरी को सरल रखने का है, तो find*() बेहतर है। यह सीमा ध्यान में रखने से XML हैंडलिंग का कोड साफ़ और अधिक पूर्वानुमेय रहता है।

निष्कर्ष

lxml में find(), findall() और findtext(), ElementPath को लागू करते हैं: एक सरल, सीमित पाथ भाषा जो प्रत्यक्ष बच्चों और बुनियादी संरचनात्मक traversal के लिए बनी है। xpath() उन्नत सुविधाओं सहित (जैसे फ़ंक्शंस) पूर्ण XPath अभिव्यक्तियों का मूल्यांकन करता है। टैग के आधार पर प्रत्यक्ष बच्चों का चयन करते समय find*() का उपयोग करें, और फ़ंक्शंस या अधिक जटिल क्वेरी जैसी पूरी XPath क्षमता चाहिए तो xpath() पर जाएँ।

यह लेख StackOverflow पर प्रश्न (लेखक: Moberg) और LMC के उत्तर पर आधारित है।