html parser?

leonardr leonard.richardson at gmail.com
Wed Oct 19 18:34:24 CEST 2005


To extract links without the overhead of Beautiful Soup, one option is
to copy what Beautiful Soup does, and write a SGMLParser subclass that
only looks at 'a' tags. In general I think writing SGMLParser
subclasses is a big pain (which is why I wrote Beautiful Soup), but
since you only care about one type of tag it's not so difficult:

from sgmllib import SGMLParser

class LinkParser(SGMLParser):
    def __init__(self):
        SGMLParser.__init__(self)
        self.links = []
        self.currentLink = None
        self.currentLinkText = []

    def start_a(self, attrs):
        #If we encounter a nested a tag, end the current a tag and
        #start a new one.
        if self.currentLink != None:
            self.end_a()
        for attr, value in attrs:
            if attr == 'href':
                self.currentLink = value
                break
        if self.currentLink == None:
            self.currentLink = ''

    def handle_data(self, data):
        if self.currentLink != None:
            self.currentLinkText.append(data)

    def end_a(self):
        self.links.append([self.currentLink,
                           "".join(self.currentLinkText)])
        self.currentLink = None
        self.currentLinkText = []

Since this ignores any tags other than 'a', it will strip out all tags
from the text within an 'a' tag (this might be what you want, since
your example shows an 'img' tag being stripped out). It will also close
one 'a' tag when it finds another, rather than attempting to nest them.

<a href="foo.php">This text has <b>embedded HTML tags</b></a>
=>
[['foo.php', 'This text has embedded HTML tags']]

<a href="foo.php">This text has <a name="anchor">an embedded
anchor</a>.
=>
[['foo.php', 'This text has '], ['', 'an embedded anchor']]

Alternatively, you can subclass a Beautiful Soup class to ignore all
tags except for 'a' tags and the tags that they contain. This will give
you the whole Beautiful Soup API, but it'll be faster because Beautiful
Soup will only build a model of the parts of your document within 'a'
tags. The following code seems to work (and it looks like a good
candidate for inclusion in the core package).

from BeautifulSoup import BeautifulStoneSoup

class StrainedStoneSoup(BeautifulStoneSoup):
    def __init__(self, interestingTags=["a"], *args):
        args = list(args)
        args.insert(0, self)
        self.interestingMap = {}
        for tag in interestingTags:
            self.interestingMap[tag] = True
        apply(BeautifulStoneSoup.__init__, args)

    def unknown_starttag(self, name, attrs, selfClosing=0):
        if self.interestingMap.get(name) or len(self.tagStack) > 1:
            BeautifulStoneSoup.unknown_starttag(self, name, attrs,
                                                selfClosing)

    def unknown_endtag(self, name):
        if len(self.tagStack) > 1:
            BeautifulStoneSoup.unknown_endtag(self, name)

    def handle_data(self, data):
        if len(self.tagStack) > 1:
            BeautifulStoneSoup.handle_data(self, data)




More information about the Python-list mailing list