2025, Sep 27 11:33
BeautifulSoup में केवल br पर लाइन स्प्लिट पाने का सही तरीका: unwrap और smooth
BeautifulSoup में केवल br पर टेक्स्ट विभाजन कैसे करें: stripped_strings की दिक्कत समझें, font टैग unwrap करें, dom.smooth() चलाएँ और लाइन-आधारित एक्स्ट्रैक्शन पाएं.
BeautifulSoup के साथ संरचित टेक्स्ट निकालते समय, अक्सर अनजाने में ऐसा होता है कि कंटेंट उन मार्कअप सीमाओं पर टूट जाता है, जिनकी आपको परवाह नहीं है। एक आम स्थिति: आप चाहते हैं कि टेक्स्ट सिर्फ HTML के लाइन ब्रेक्स पर बंटे, लेकिन font जैसे फ़ॉर्मेटिंग टैग उसी पंक्ति को टुकड़ों में बांट देते हैं और आउटपुट में अनावश्यक स्प्लिट जोड़ देते हैं। नीचे इस समस्या का संक्षिप्त विवरण और भरोसेमंद, लाइन-आधारित एक्स्ट्रैक्शन पाने का साफ तरीका दिया गया है।
समस्या का सार
मान लीजिए आप blockquote सेक्शनों पर इटरेट कर रहे हैं और चाहते हैं कि हर पंक्ति सिर्फ वहीं कटे जहां br हो। अगर पंक्ति के बीच में font टैग आ जाए, तो BeautifulSoup का stripped_strings उस एक ही दृश्य पंक्ति को कई हिस्सों में बाँट सकता है। उद्देश्य यह है कि पंक्तियाँ जस की तस रहें और सिर्फ br पर ही विभाजित हों।
पुनरुत्पाद्य इनपुट HTML
यहाँ एक सरल उदाहरण है जो समस्या दिखाता है। एक font टैग उसी तार्किक पंक्ति को बीच में रोक देता है और अवांछित स्प्लिट पैदा करता है:
<blockquote>
<p>RT CLOAK<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesc3.htm#1057"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br><font color="FFFFFF">RT </font>COAT<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesc4.htm#1082"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br>BT GARMENT<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesg1.htm#2099"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br><i>NP ABBA</i><a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesa1.htm#3"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a></p>
<hr width="90%"></blockquote>
<p><a name="3"></a><i>ABBA NP</i></p>
<blockquote>
समस्या दिखाने वाला कोड
यह स्क्रिप्ट पेज पर चलती है, blockquote सेक्शन ढूँढ़ती है और उनसे जुड़ी टेक्स्ट पंक्तियाँ प्रिंट करती है। लेकिन font टैग के कारण एक ही तार्किक पंक्ति भी आउटपुट में टूट जाती है। प्रोग्राम stripped_strings के साथ इटरेट करता है, और यहीं पर यह बिखराव होता है।
from bs4 import BeautifulSoup
import requests
sources = ['https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesa1.htm']
counter = 1
for url in sources:
    resp = requests.get(url)
    markup = resp.text
    dom = BeautifulSoup(markup, "lxml")
    blocks = dom.find_all('blockquote')
    for blk in blocks:
        header = blk.find_previous('p')
        for chunk in blk.stripped_strings:
            print(counter, header.text, chunk)
        counter = counter + 1
आम तौर पर अवांछित आउटपुट में “RT” और “COAT” जैसे भाग अलग-अलग आइटम के रूप में दिखते हैं, जबकि वे दरअसल एक ही पंक्ति के हिस्से हैं जो केवल br से अलग हुए हैं।
ऐसा क्यों होता है
stripped_strings DOM की संरचना के आधार पर टेक्स्ट देता है। font जैसे फ़ॉर्मेटिंग एलिमेंट एक पंक्ति को अलग-अलग टेक्स्ट नोड्स में बाँट सकते हैं। भले ही दिखने में पंक्ति निरंतर हो, टेक्स्ट नोड्स अलग हो जाते हैं और stripped_strings उन्हें स्वतंत्र रूप से लौटाता है।
समाधान का तरीका
यदि आप केवल br पर विभाजन चाहते हैं और फ़ॉर्मेटिंग को अनदेखा करना चाहते हैं, तो बीच के फ़ॉर्मेटिंग नोड्स हटा दें ताकि वे टेक्स्ट को न काटें। इसका सीधा तरीका है font टैग्स को unwrap करना ताकि उनकी जगह सिर्फ उनका टेक्स्ट रह जाए, और फिर stripped_strings के साथ इटरेट करने से पहले पास-पास के टेक्स्ट नोड्स को मिला दें।
Unwrap करने के लिए .unwrap() का उपयोग करें। Unwrap के बाद जो पास-पास टेक्स्ट नोड्स बनते हैं, उन्हें जोड़ने के लिए .smooth() का उपयोग करें। .unwrap() के बाद .smooth() कॉल करना ज़रूरी है ताकि नई सटी हुई स्ट्रिंग्स एक साथ मिल जाएँ।
न्यूनतम कार्यशील उदाहरण
यहाँ ऊपर दिए गए नमूना HTML पर चलने वाला एक संक्षिप्त उदाहरण है, जो अपेक्षित पंक्तियों की सूची लौटाता है:
from bs4 import BeautifulSoup
sample = """
<blockquote>
<p>RT CLOAK<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesc3.htm#1057"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br><font color="FFFFFF">RT </font>COAT<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesc4.htm#1082"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br>BT GARMENT<a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesg1.htm#2099"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a><br><i>NP ABBA</i><a href="https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesa1.htm#3"><img src="./British Museum Object Names Theasarus_ Terms AB-AM_files/link.gif" border="0"></a></p>
<hr width="90%"></blockquote>
<p><a name="3"></a><i>ABBA NP</i></p>
</blockquote>
"""
doc = BeautifulSoup(sample, "lxml")
for node in doc.find_all("font"):
    node.unwrap()
doc.smooth()
for bq in doc.find_all("blockquote"):
    print(list(bq.stripped_strings))
आउटपुट साफ-सुथरी पंक्तियों की सूची है:
['RT CLOAK', 'RT COAT', 'BT GARMENT', 'NP ABBA']
सुधारा हुआ एंड-टू-एंड स्क्रिप्ट
इसी विचार को पहले वाले पेज-वॉकर पर लागू करने से—इटरेशन से पहले unwrapping और smoothing करने पर—stripped_strings सिर्फ वास्तविक लाइन ब्रेक्स का सम्मान करता है। तर्क वही रहता है; DOM में विभाजन के लिए कम फ़ॉर्मेटिंग सीमाएँ बचती हैं।
from bs4 import BeautifulSoup
import requests
sources = ['https://terminology.collectionstrust.org.uk/British-Museum-objects/Obthesa1.htm']
counter = 1
for url in sources:
    resp = requests.get(url)
    markup = resp.text
    dom = BeautifulSoup(markup, "lxml")
    for f in dom.find_all("font"):
        f.unwrap()
    dom.smooth()
    quotes = dom.find_all('blockquote')
    for quote in quotes:
        title = quote.find_previous('p')
        for line in quote.stripped_strings:
            print(counter, title.text, line)
        counter += 1
यह बारीकी क्यों मायने रखती है
जब आप डाउनस्ट्रीम कार्यों के लिए HTML को सामान्यीकृत करते हैं, तो सुसंगत लाइन विभाजन अत्यंत महत्वपूर्ण होता है। यदि आप केवल br पर विभाजन करना चाहते हैं, तो इनलाइन फ़ॉर्मेटिंग को टेक्स्ट तोड़ने देने से टोकन बिखरेंगे और वह तर्क बिगड़ेगा जो प्रति पंक्ति निरंतर स्ट्रिंग्स पर निर्भर है। टेक्स्ट निकालने से पहले अप्रासंगिक संरचनात्मक सीमाएँ हटाने से आउटपुट अनुमानित और प्रोसेस करना आसान हो जाता है।
यदि आपको केवल br पर ही विभाजन करना है, तो एक व्यावहारिक तरीका यह है कि HTML में br को किसी अद्वितीय सेपरेटर से बदल दें, समूचा टेक्स्ट एकत्र कर लें, और फिर उसी सेपरेटर पर split करें। यह तब खास तौर पर उपयोगी है जब आप अन्य टैग्स को हटा नहीं सकते या हटाना नहीं चाहते।
मुख्य बातें
स्वच्छ टेक्स्ट पर इटरेट करने के लिए stripped_strings का उपयोग करें, पर ध्यान रहे कि यह DOM सीमाओं का अनुसरण करता है। यदि इनलाइन फ़ॉर्मेटिंग आपकी पंक्तियाँ तोड़ रही है, तो पहले समस्या पैदा करने वाले टैग्स को unwrap करें और पास-पास के टेक्स्ट नोड्स को जोड़ने के लिए smooth कॉल करें। इस तरह आपका विभाजन वास्तविक लाइन ब्रेक्स पर आधारित रहेगा, न कि स्टाइलिंग की वजह से बने कृत्रिम बिंदुओं पर।
यह लेख StackOverflow पर प्रश्न (लेखक: James Brian) और jqurious के उत्तर पर आधारित है।