Extract the title from a webpage using the python 3 standard lib The aim of this module is to extract the title from possibly broken hypertext. Without using any awesome third party modules like bs4 or lxml.

#-*-coding:utf8;-*-
#qpy:3
#qpy:console

''' 
Extract the title from a web page using
the standard lib.
'''

from html.parser import HTMLParser
from urllib.request import urlopen
import urllib

def error_callback(*_, **__):
    pass

def is_string(data):
    return isinstance(data, str)

def is_bytes(data):
    return isinstance(data, bytes)

def to_ascii(data):
    if is_string(data):
        data = data.encode('ascii', errors='ignore')
    elif is_bytes(data):
        data = data.decode('ascii', errors='ignore')
    else:
        data = str(data).encode('ascii', errors='ignore')
    return data


class Parser(HTMLParser):
    def __init__(self, url):
        self.title = None
        self.rec = False
        HTMLParser.__init__(self)
        try:
            self.feed(to_ascii(urlopen(url).read()))
        except urllib.error.HTTPError:
            return
        except urllib.error.URLError:
            return
        except ValueError:
            return

        self.rec = False
        self.error = error_callback

    def handle_starttag(self, tag, attrs):
        if tag == 'title':
            self.rec = True

    def handle_data(self, data):
        if self.rec:
            self.title = data

    def handle_endtag(self, tag):
        if tag == 'title':
            self.rec = False


def get_title(url):
    return Parser(url).title

print(get_title('http://www.google.com'))
Looks pretty clean to me, good job! Here's what I recommend:


The to_ascii function is small and readable, but in my opinion the is_string and is_bytes functions are overkill:

def to_ascii(data):
    if isinstance(data, str):
        ...
    elif isinstance(data, bytes):
        ...
    else:
        ...

We can make to_ascii less complex by returning immediately:

def to_ascii(data):
    if isinstance(data, str):
        return data.encode("ascii", errors="ignore")
    elif isinstance(data, bytes):
        return data.decode("ascii", errors="ignore")


We can then leave out the else clause:

def to_ascii(data):
    if isinstance(data, str):
        return data.encode("ascii", errors="ignore")
    elif isinstance(data, bytes):
        return data.decode("ascii", errors="ignore")
    return str(data).encode("ascii", errors="ignore")

error_callback is pretty much useless. Maybe you planned on adding more functionality to the Parser class, but as is, you can just leave it out.
I don't expect a parser's constructor to automatically parse the data I pass. How about:

class Parser(HTMLParser):
    def __init__(self, data):
        self._data = data
        ...

    def parse(self):
        self._parsed = self.feed( ... )