Python et le web¶

Pour le protocole http :¶

  • urllib permet d'effectuer facilement des opérations simples (ouvrir une URL comme un fichier, GET et POST)
  • http.server permet de monter un serveur (de test !) en quelques lignes
  • http.client, bas niveau

  • xmlrpc.client

  • xmlrpc.server Web services (minimaliste)

Pour le traitement du HTML :¶

  • html.parser analyse HTML et XHTML
  • html.entities
  • Nombreuses contributions externes, comme Beautiful Soup et requests]
  • Bon support du XML : xml.*

Pour la construction de sites :¶

Django, Turbogears, Flask (frameworks) Zope (serveur d'applications), Plone (gestion de contenu) Bibliothèques (externes) pour SOAP, CORBA, REST ...

Le module urllib¶

Fonctions de base pour lire des données à partir d'une URL. Protocoles : http, https, ftp, gopher, file.

  • urllib.request pour lire et écrire des URLs
  • urllib.error contient les exceptions levées par urllib.request
  • urllib.parse pour analyser les URLs
  • urllib.robotparser pour analyser les fichiers robots.txt
In [1]:
from urllib.request import urlopen
s = urlopen('http://igm.univ-mlv.fr/~jyt/M1_python').read()
In [3]:
print(s)
b'<html>\n\t<body>\n\t\t<H1> M1 Informatique : Python </H1>\n\t\t<ul>\n\t\t\t<li> Commencer ici : <a href="http://www.python.org/">python.org</a>\n\t\t\t<li> La <a href="http://docs.python.org/3.8">documentation</a> officielle\n\t\t\t<li> Le <a href="http://www.pythonchallenge.com/"> Python Challenge</a>\n\t\t\t<li> <a href="https://python.developpez.com/cours/DiveIntoPython/"><i>Dive into Python</i></a>  \n\t\t\tpar <a href="http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29">Mark Pilgrim</a> \n\t\t\t(un livre recommand&eacute; mais ancien);\n\t\t\t<a href="http://diveintopython3.problemsolving.io/">version Python 3</a>. \n\t\t\t<li> La <a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">page</a> de Wikip&eacute;dia contient un bon r&eacute;sum&eacute;\n\t\t\t<li> Introduction au <i>notebook</i> ipython ou jupyter :)\n\t\t\t<ul> \n\t\t\t\t<li> <a href="intro_jupyter.html">Exemple</a>  de ce qu\'on peut obtenir (converti en html)\n\t\t\t\t<li> Le <a href="intro_jupyter.ipynb">code source</a> (fichier <tt>.ipynb</tt>), &agrave; t&eacute;l&eacute;charger\n\t\t\t\t<li> Lancer un terminal, aller dans le dossier contenant le fichier <tt>.ipynb</tt>, et entrer la commande <tt>jupyter notebook</tt>\n\t\t\t</ul>\n\t\t\t<li> Cours 1 (22/9/21) <a href="python3_M1_2021-1.html">html</a> - <a href="python3_M1_2021-1.ipynb">notebook</a>\n\t\t\t<li> <a href="td1_python3_2020.html"> TD 1</a> (22/9/21)\n\t\t\t<li> <a href="script3.py">mod&egrave;le de script</a> avec options et documentation\n\t\t\t<li> <a href="td1_sol_3_2021.html">Corrig&eacute</a> du TD 1\n\t\t\t<li> Cours 2 (29/09/2021) <a href="Python3_M1_2021-2.html">html</a> - <a href="Python3_M1_2021-2.ipynb">notebook</a>\n\t\t\t<li> <a href="td2_2021_texte.html">TD 2</a> (29/09/2021)\n\t\t\t<li> <a href="td2_solpy3_2021.html">Corrig&eacute</a> du TD 2\n\t\t\t<li> Cours 3 (6/10/2021) <a href="python3_M1_2021-3.html">html</a> - <a href="python3_M1_2021-3.ipynb">notebook</a>\n\t\t\t<li> <a href="td3_2021_texte.html">TD 3</a> (6/10/2021)\n\n\n\n\t\t\n\n\n\t\t\t\n\n\t\t</ul>\n\t</body>\n</html>\n\n\t\t\n'
In [4]:
print(s.decode('ascii'))
<html>
	<body>
		<H1> M1 Informatique : Python </H1>
		<ul>
			<li> Commencer ici : <a href="http://www.python.org/">python.org</a>
			<li> La <a href="http://docs.python.org/3.8">documentation</a> officielle
			<li> Le <a href="http://www.pythonchallenge.com/"> Python Challenge</a>
			<li> <a href="https://python.developpez.com/cours/DiveIntoPython/"><i>Dive into Python</i></a>  
			par <a href="http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29">Mark Pilgrim</a> 
			(un livre recommand&eacute; mais ancien);
			<a href="http://diveintopython3.problemsolving.io/">version Python 3</a>. 
			<li> La <a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">page</a> de Wikip&eacute;dia contient un bon r&eacute;sum&eacute;
			<li> Introduction au <i>notebook</i> ipython ou jupyter :)
			<ul> 
				<li> <a href="intro_jupyter.html">Exemple</a>  de ce qu'on peut obtenir (converti en html)
				<li> Le <a href="intro_jupyter.ipynb">code source</a> (fichier <tt>.ipynb</tt>), &agrave; t&eacute;l&eacute;charger
				<li> Lancer un terminal, aller dans le dossier contenant le fichier <tt>.ipynb</tt>, et entrer la commande <tt>jupyter notebook</tt>
			</ul>
			<li> Cours 1 (22/9/21) <a href="python3_M1_2021-1.html">html</a> - <a href="python3_M1_2021-1.ipynb">notebook</a>
			<li> <a href="td1_python3_2020.html"> TD 1</a> (22/9/21)
			<li> <a href="script3.py">mod&egrave;le de script</a> avec options et documentation
			<li> <a href="td1_sol_3_2021.html">Corrig&eacute</a> du TD 1
			<li> Cours 2 (29/09/2021) <a href="Python3_M1_2021-2.html">html</a> - <a href="Python3_M1_2021-2.ipynb">notebook</a>
			<li> <a href="td2_2021_texte.html">TD 2</a> (29/09/2021)
			<li> <a href="td2_solpy3_2021.html">Corrig&eacute</a> du TD 2
			<li> Cours 3 (6/10/2021) <a href="python3_M1_2021-3.html">html</a> - <a href="python3_M1_2021-3.ipynb">notebook</a>
			<li> <a href="td3_2021_texte.html">TD 3</a> (6/10/2021)



		


			

		</ul>
	</body>
</html>

		

urlopen renvoie un objet "file-like".

Méthodes :

read(), readline(), readlines(), fileno(), close(), et en plus info(), geturl().

In [5]:
f = urlopen('http://igm.univ-mlv.fr/~jyt/M1_python')
In [6]:
print(dir(f))
['__abstractmethods__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_abc_impl', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_check_close', '_close_conn', '_get_chunk_left', '_method', '_peek_chunked', '_read1_chunked', '_read_and_discard_trailer', '_read_next_chunk_size', '_read_status', '_readall_chunked', '_readinto_chunked', '_safe_read', '_safe_readinto', 'begin', 'chunk_left', 'chunked', 'close', 'closed', 'code', 'debuglevel', 'detach', 'fileno', 'flush', 'fp', 'getcode', 'getheader', 'getheaders', 'geturl', 'headers', 'info', 'isatty', 'isclosed', 'length', 'msg', 'peek', 'read', 'read1', 'readable', 'readinto', 'readinto1', 'readline', 'readlines', 'reason', 'seek', 'seekable', 'status', 'tell', 'truncate', 'url', 'version', 'will_close', 'writable', 'write', 'writelines']
In [7]:
f.code
Out[7]:
200
In [10]:
f.getheaders()
Out[10]:
[('Date', 'Sun, 10 Oct 2021 09:05:33 GMT'),
 ('Server', 'Apache'),
 ('Last-Modified', 'Wed, 06 Oct 2021 06:00:47 GMT'),
 ('ETag', '"1760011-7b3-5cda8de0ac1c0"'),
 ('Accept-Ranges', 'bytes'),
 ('Content-Length', '1971'),
 ('Connection', 'close'),
 ('Content-Type', 'text/html')]
In [11]:
f.geturl()
Out[11]:
'http://igm.univ-mlv.fr/~jyt/M1_python/'

Exemple¶

Pour savoir si un nouveau document a été mis en ligne (le fichier etag_python doit avoir été initialisé) :

In [13]:
url = 'http://igm.univ-mlv.fr/~jyt/M1_python'
t = open('etag_python').read()
d = urlopen(url).info()
s = d['etag']
print (d['last-modified'])
if s != t :
    print ("La page du cours de Python a été modifiée")
    open('etag_python','w').write(s)
else: print ("Aucune modification")
Wed, 06 Oct 2021 06:00:47 GMT
La page du cours de Python a été modifiée

urlopen prend un paramètre optionnel, data. Si data est None, elle envoie une requête GET, sinon une requête POST.

In [14]:
from urllib.parse import urlencode
url='http://oeis.org'
query={'q':'1,1,3,11,49,257','language':'english', 'go':'Search'}
data = urlencode(query)

data
Out[14]:
'q=1%2C1%2C3%2C11%2C49%2C257&language=english&go=Search'
In [15]:
s = urlopen(url,data.encode('ascii')).read()
In [16]:
s[:300]
Out[16]:
b'\n<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<html>\n  \n  <head>\n  <style>\n  tt { font-family: monospace; font-size: 100%; }\n  p.editing { font-family: monospace; margin: 10px; text-indent: -10px; word-wrap:break-word;}\n  p { word-wrap: break-word; }\n  </style>\n  <meta http-equiv="content'

Le module urllib.parse¶

La manipulation des URLs est facilitée par le module urllib.parse

In [17]:
from urllib.parse import unquote, urlsplit, urlunsplit
In [18]:
unquote(data)
Out[18]:
'q=1,1,3,11,49,257&language=english&go=Search'
In [19]:
x='http://www.google.fr/search?as_q=python&hl=fr&num=10&btnG=Recherche+Google&as_epq=&as_oq=&as_eq=&lr=&cr=&as_ft=i&as_filetype=pdf&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=univ-mlv.fr&as_rights=&safe=images'

y = urlsplit(x)
y
Out[19]:
SplitResult(scheme='http', netloc='www.google.fr', path='/search', query='as_q=python&hl=fr&num=10&btnG=Recherche+Google&as_epq=&as_oq=&as_eq=&lr=&cr=&as_ft=i&as_filetype=pdf&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=univ-mlv.fr&as_rights=&safe=images', fragment='')
In [20]:
y.netloc
Out[20]:
'www.google.fr'
In [21]:
y.path
Out[21]:
'/search'
In [22]:
y.query
Out[22]:
'as_q=python&hl=fr&num=10&btnG=Recherche+Google&as_epq=&as_oq=&as_eq=&lr=&cr=&as_ft=i&as_filetype=pdf&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=univ-mlv.fr&as_rights=&safe=images'
In [23]:
urlunsplit(y)
Out[23]:
'http://www.google.fr/search?as_q=python&hl=fr&num=10&btnG=Recherche+Google&as_epq=&as_oq=&as_eq=&lr=&cr=&as_ft=i&as_filetype=pdf&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=univ-mlv.fr&as_rights=&safe=images'

Fonctionalités avancées d'urllib.request¶

La fonction urlopen suffit pour les applications les plus courantes.

Elle supporte les proxys pourvu qu'ils ne demandent pas d'authentification. Il suffit de positionner les variables d'environnement http_proxy, ftp_proxy, etc.

$ http_proxy="http://www.monproxy.com:1234"
$ export http_proxy
$ python

Pour un contrôle plus fin (authentification, user-agent, cookies) on peut utiliser la classe urllib.request.Request (héritée du module urllib2 de Python 2.

In [24]:
from urllib.request import Request 
url='http://oeis.org'
req = Request(url)
print(dir(req))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_data', '_full_url', '_parse', '_tunnel_host', 'add_header', 'add_unredirected_header', 'data', 'fragment', 'full_url', 'get_full_url', 'get_header', 'get_method', 'has_header', 'has_proxy', 'header_items', 'headers', 'host', 'origin_req_host', 'remove_header', 'selector', 'set_proxy', 'type', 'unredirected_hdrs', 'unverifiable']

En spécifiant les en-têtes, on peut par exemple se faire passer pour Internet Explorer

In [25]:
req.add_header('User-agent','Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
req.headers
Out[25]:
{'User-agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}

Ou envoyer un cookie

In [26]:
req.add_header('Cookie', 'info=En-veux-tu%3F%20En%20voil%E0%21')
req.headers
Out[26]:
{'User-agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
 'Cookie': 'info=En-veux-tu%3F%20En%20voil%E0%21'}

Quand on ouvre une URL, on utilise un opener. On peut remplacer l'opener par défaut pour gérer l'authentification, les proxys, etc. Les openers utilisent des handlers.

build_opener est utilisé pour créer des objets opener, qui permettent d'ouvrir des URLs avec des handlers spécifiques.

Les handlers peuvent gérer des cookies, l'authentification, et autres cas communs mais un peu spécifiques.

Les objets Opener ont une méthode open, qui peut être appelée directement pour ouvrir des urls de la même manière que la fonction urlopen.

install_opener peut être utilisé pour rendre l'objet opener l'opener par défaut. Cela signifie que les appels à urlopen l'utiliseront.

Exemple : authentification basique.¶

Pour demander une authentification, le serveur envoie le coded'erreur 401 et un en-tête du type

www-authenticate: SCHEME realm="REALM"

Le client doit alors re-essayer la requête avec un couple (username, password) correct pour le domaine (realm).

On peut gérer cela avec une instance de HTTPBasicAuthHandler et un opener pour utiliser ce handler.

HTTPBasicAuthHandler utilise un "password manager" pour gérer la correspondance entre les URIs et realms (domaines) et les couple (password, username).

En général un seul domaine (realm) par URI : HTTPPasswordMgrWithDefaultRealm.

Exemple de la documentation officielle :

import urllib.request
# Create an OpenerDirector with support for Basic HTTP Authentication...
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='PDQ Application',
                          uri='https://mahler:8092/site-updates.py',
                          user='klem',
                          passwd='kadidd!ehopper')
opener = urllib.request.build_opener(auth_handler)
# ...and install it globally so it can be used with urlopen.
urllib.request.install_opener(opener)
urllib.request.urlopen('http://www.example.com/login.html')

Pour en savoir plus : urllib.request

Le module requests (à installer)¶

Il existe un module tiers (requests) beaucoup plus pratique.

In [27]:
import requests
In [28]:
url = "http://igm.univ-mlv.fr/~jyt/secret/"

Si on essaie d'u accéder, un popup demande un nom d'utilisateur et un mot de passe.

In [29]:
f = urlopen(url).read()
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-29-3ec16db46158> in <module>
----> 1 f = urlopen(url).read()

/usr/lib/python3.8/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    220     else:
    221         opener = _opener
--> 222     return opener.open(url, data, timeout)
    223 
    224 def install_opener(opener):

/usr/lib/python3.8/urllib/request.py in open(self, fullurl, data, timeout)
    529         for processor in self.process_response.get(protocol, []):
    530             meth = getattr(processor, meth_name)
--> 531             response = meth(req, response)
    532 
    533         return response

/usr/lib/python3.8/urllib/request.py in http_response(self, request, response)
    638         # request was successfully received, understood, and accepted.
    639         if not (200 <= code < 300):
--> 640             response = self.parent.error(
    641                 'http', request, response, code, msg, hdrs)
    642 

/usr/lib/python3.8/urllib/request.py in error(self, proto, *args)
    567         if http_err:
    568             args = (dict, 'default', 'http_error_default') + orig_args
--> 569             return self._call_chain(*args)
    570 
    571 # XXX probably also want an abstract factory that knows when it makes

/usr/lib/python3.8/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args)
    500         for handler in handlers:
    501             func = getattr(handler, meth_name)
--> 502             result = func(*args)
    503             if result is not None:
    504                 return result

/usr/lib/python3.8/urllib/request.py in http_error_default(self, req, fp, code, msg, hdrs)
    647 class HTTPDefaultErrorHandler(BaseHandler):
    648     def http_error_default(self, req, fp, code, msg, hdrs):
--> 649         raise HTTPError(req.full_url, code, msg, hdrs, fp)
    650 
    651 class HTTPRedirectHandler(BaseHandler):

HTTPError: HTTP Error 401: Authorization Required
In [30]:
r = requests.get(url,auth=('jyt','toto'))
In [31]:
r.status_code
Out[31]:
200
In [32]:
r.text
Out[32]:
"<html>\n\nIl n'y a rien a voir ici ...\n</html>\n"
In [33]:
r.headers
Out[33]:
{'Date': 'Sun, 10 Oct 2021 09:15:29 GMT', 'Server': 'Apache', 'Last-Modified': 'Sun, 14 Oct 2012 16:46:23 GMT', 'ETag': '"1b2e2c6-2d-4cc07a93c95c0"', 'Accept-Ranges': 'bytes', 'Content-Length': '45', 'Keep-Alive': 'timeout=15, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html'}

Côté serveur¶

Les modules http.server et cgi permettent de mettre en place un serveur web opérationnel en quelques lignes.

En fait, en une ligne :

[jyt@scriabine ~]$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [07/Oct/2019 09:35:32] "GET / HTTP/1.1" 200 -
...

Et sous forme de programme :

import http.server
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()
httpd.serve_forever()

Un serveur de scripts minimal serait

#!/usr/bin/env python

import http.server

PORT = 8888
server_address = ("", PORT)

server = http.server.HTTPServer
handler = http.server.CGIHTTPRequestHandler
handler.cgi_directories = ["/"]
print("Serving on port:", PORT)

httpd = server(server_address, handler)
httpd.serve_forever()

Le script cgi devra être placé dans un sous-répertoire cgi-bin, et le serveur devra avoir le droit d'exécution. Seul root peut lancer le serveur sur le port 80. En tant qu'utilisateur normal, on pourra le lancer sur un port libre, par exemple 8888. Le formulaire sera alors déclaré avec l'action

ACTION="http://127.0.01:8888/cgi-bin/monscript.cgi"

Traitement du HTML¶

Pour extraire des informations d'une page web, on peut parfois se débrouiller avec des expressions régulières.

Mais on a aussi souvent besoin d'une analyse complète.

Il existe pour cela un module html.parser qui exporte une classe HTMLParser.

On l'illustrera sur un exemple tiré de la première édition de "Dive into Python" : traduire à la volée des pages web dans des dialectes farfelus tels que ceux proposés ici.

On trouvera dans le livre de Pilgrim des grammaires simple pour chef, fudd, olde. Les textes sont supposés en anglais, donc en ASCII.

Dans le cours, on utilisera plutôt le touilleur de texte vu en TD pour brouiller une page web sans modifier sa mise en page.

Au passage, voilà la version utilisant une expression régulière :

# module touille.py
import random, re
p = re.compile('(\w)(\w\w+)(\w)', re.M)  # mot d'au moins 4 lettres

def touille(m):
    milieu = list(m.group(2)) # group(2) est le milieu
    random.shuffle(milieu)
    return m.group(1) + ''.join(milieu) + m.group(3)

def blurr(s):
    return p.sub(touille,s)
In [35]:
from touille import blurr
blurr('Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.')
Out[35]:
'Il est plus flacie de se laevr les dnets dans un verre à pied que de se lvear les pdies dans un vrree à dnets.'

Pour d'autres exemples, voir ce site.

Exemple: Elmer Fudd cf. Bugs Bunny)

Les dialectes sont définis par des substitutions, attribut subs d'une sous-classe Dialectizer de BaseHTMLProcessor, elle même dérivée de HTMLParser.

class FuddDialectizer(Dialectizer):
    """convert HTML to Elmer Fudd-speak"""
    subs = ((r'[rl]', r'w'),
            (r'qu', r'qw'),
            (r'th\b', r'f'),
            (r'th', r'd'),
            (r'n[.]', r'n, uh-hah-hah-hah.'))

La classe HTMLParser¶

Elle réalise l'analyse syntaxique du HTML.

Dès qu'un élément utile est identifié (une balise ouvrante, start_tag, par exemple), une méthode handle_starttag, do_tag, ... est appelée.

HTMLParser analyse le HTML en 8 types de données, et appelle une méthode différente pour chaque cas :

  • *start tag* (balise ouvrante) : handle_starttag, handle_startendtag (pour les $<.../ >$ en xhtml)
  • *end tag* (balise fermante) : handle_endtag
  • *character reference* : par exemple &#160; handle_charref
  • *entity reference* : par exemple &copy;. handle_entityref
  • *comment* : handle_comment
  • *processing instruction* : <? ... >. handle_pi
  • _declaration_ : <! ... >. handle_decl
  • _text data_ : handle_data.

Pour l'utiliser, il faut créer une classe dérivée de HTMLParser, et surcharger ces 8 méthodes.

In [36]:
from html.parser import HTMLParser


class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print ("Balise ouvrante :", tag, attrs)

    def handle_endtag(self, tag):
        print ("Balise fermante :", tag)

    def handle_data(self, data):
        print ("Texte  :", repr(data))

# On créee une instance
parser = MyHTMLParser()

# Et on lui donne à manger

from urllib.request import urlopen
s = urlopen('http://igm.univ-mlv.fr/~jyt/M1_python/').read().decode('utf8')
parser.feed(s)
Balise ouvrante : html []
Texte  : '\n\t'
Balise ouvrante : body []
Texte  : '\n\t\t'
Balise ouvrante : h1 []
Texte  : ' M1 Informatique : Python '
Balise fermante : h1
Texte  : '\n\t\t'
Balise ouvrante : ul []
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Commencer ici : '
Balise ouvrante : a [('href', 'http://www.python.org/')]
Texte  : 'python.org'
Balise fermante : a
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' La '
Balise ouvrante : a [('href', 'http://docs.python.org/3.8')]
Texte  : 'documentation'
Balise fermante : a
Texte  : ' officielle\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Le '
Balise ouvrante : a [('href', 'http://www.pythonchallenge.com/')]
Texte  : ' Python Challenge'
Balise fermante : a
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'https://python.developpez.com/cours/DiveIntoPython/')]
Balise ouvrante : i []
Texte  : 'Dive into Python'
Balise fermante : i
Balise fermante : a
Texte  : '  \n\t\t\tpar '
Balise ouvrante : a [('href', 'http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29')]
Texte  : 'Mark Pilgrim'
Balise fermante : a
Texte  : ' \n\t\t\t(un livre recommandé mais ancien);\n\t\t\t'
Balise ouvrante : a [('href', 'http://diveintopython3.problemsolving.io/')]
Texte  : 'version Python 3'
Balise fermante : a
Texte  : '. \n\t\t\t'
Balise ouvrante : li []
Texte  : ' La '
Balise ouvrante : a [('href', 'http://fr.wikipedia.org/wiki/Python_%28langage%29')]
Texte  : 'page'
Balise fermante : a
Texte  : ' de Wikipédia contient un bon résumé\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Introduction au '
Balise ouvrante : i []
Texte  : 'notebook'
Balise fermante : i
Texte  : ' ipython ou jupyter :)\n\t\t\t'
Balise ouvrante : ul []
Texte  : ' \n\t\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'intro_jupyter.html')]
Texte  : 'Exemple'
Balise fermante : a
Texte  : "  de ce qu'on peut obtenir (converti en html)\n\t\t\t\t"
Balise ouvrante : li []
Texte  : ' Le '
Balise ouvrante : a [('href', 'intro_jupyter.ipynb')]
Texte  : 'code source'
Balise fermante : a
Texte  : ' (fichier '
Balise ouvrante : tt []
Texte  : '.ipynb'
Balise fermante : tt
Texte  : '), à télécharger\n\t\t\t\t'
Balise ouvrante : li []
Texte  : ' Lancer un terminal, aller dans le dossier contenant le fichier '
Balise ouvrante : tt []
Texte  : '.ipynb'
Balise fermante : tt
Texte  : ', et entrer la commande '
Balise ouvrante : tt []
Texte  : 'jupyter notebook'
Balise fermante : tt
Texte  : '\n\t\t\t'
Balise fermante : ul
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Cours 1 (22/9/21) '
Balise ouvrante : a [('href', 'python3_M1_2021-1.html')]
Texte  : 'html'
Balise fermante : a
Texte  : ' - '
Balise ouvrante : a [('href', 'python3_M1_2021-1.ipynb')]
Texte  : 'notebook'
Balise fermante : a
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'td1_python3_2020.html')]
Texte  : ' TD 1'
Balise fermante : a
Texte  : ' (22/9/21)\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'script3.py')]
Texte  : 'modèle de script'
Balise fermante : a
Texte  : ' avec options et documentation\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'td1_sol_3_2021.html')]
Texte  : 'Corrigé'
Balise fermante : a
Texte  : ' du TD 1\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Cours 2 (29/09/2021) '
Balise ouvrante : a [('href', 'Python3_M1_2021-2.html')]
Texte  : 'html'
Balise fermante : a
Texte  : ' - '
Balise ouvrante : a [('href', 'Python3_M1_2021-2.ipynb')]
Texte  : 'notebook'
Balise fermante : a
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'td2_2021_texte.html')]
Texte  : 'TD 2'
Balise fermante : a
Texte  : ' (29/09/2021)\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'td2_solpy3_2021.html')]
Texte  : 'Corrigé'
Balise fermante : a
Texte  : ' du TD 2\n\t\t\t'
Balise ouvrante : li []
Texte  : ' Cours 3 (6/10/2021) '
Balise ouvrante : a [('href', 'python3_M1_2021-3.html')]
Texte  : 'html'
Balise fermante : a
Texte  : ' - '
Balise ouvrante : a [('href', 'python3_M1_2021-3.ipynb')]
Texte  : 'notebook'
Balise fermante : a
Texte  : '\n\t\t\t'
Balise ouvrante : li []
Texte  : ' '
Balise ouvrante : a [('href', 'td3_2021_texte.html')]
Texte  : 'TD 3'
Balise fermante : a
Texte  : ' (6/10/2021)\n\n\n\n\t\t\n\n\n\t\t\t\n\n\t\t'
Balise fermante : ul
Texte  : '\n\t'
Balise fermante : body
Texte  : '\n'
Balise fermante : html
Texte  : '\n\n\t\t\n'

Exemple : URLLister¶

But : une classe pour extraire les liens d'une page web.

In [37]:
from html.parser import HTMLParser

class URLLister(HTMLParser):
    def reset(self):                              
        HTMLParser.reset(self)
        self.urls = []

    def handle_starttag(self, tag, attrs):                     
        href = [v for k, v in attrs if k=='href'] # attrs est une liste de couples
        if href:
            self.urls.extend(href)
In [38]:
s = urlopen('http://igm.univ-mlv.fr/~jyt/M1_python').read().decode('utf8')
p = URLLister()
p.feed(s)
p.close()

for u in p.urls: print (u)
http://www.python.org/
http://docs.python.org/3.8
http://www.pythonchallenge.com/
https://python.developpez.com/cours/DiveIntoPython/
http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29
http://diveintopython3.problemsolving.io/
http://fr.wikipedia.org/wiki/Python_%28langage%29
intro_jupyter.html
intro_jupyter.ipynb
python3_M1_2021-1.html
python3_M1_2021-1.ipynb
td1_python3_2020.html
script3.py
td1_sol_3_2021.html
Python3_M1_2021-2.html
Python3_M1_2021-2.ipynb
td2_2021_texte.html
td2_solpy3_2021.html
python3_M1_2021-3.html
python3_M1_2021-3.ipynb
td3_2021_texte.html

En appliquant ceci à la page du cours, on pourrait tester (avec une regexp) si un nouveau pdf a été mis en ligne et le récuperer automatiquement ...

La traduction d'une page HTML¶

Il s'agit de reproduire à l'identique le document HTML, en traduisant seulement le texte, sauf s'il est encadré par une balise <pre> (on ne traduirait pas un programme).

Pour varier les plaisirs, on pourra traduire de l'anglais en texan :

(^|" ")"American"           changeCase(" Amerkin");
(^|" ")"California"         changeCase(" Caleyfornyuh");
(^|" ")"Dallas"             changeCase(" Big D.");
(^|" ")"Fort Worth"         changeCase(" Fowert Wurth");
(^|" ")"Houston"            changeCase(" Useton");
(^|" ")"I don't know"           changeCase(" I-O-no");
(^|" ")"I will"|" I'll"         changeCase(" Ahl");
...

La classe BaseHTMLProcessor¶

On commence par construire une classe dérivée qui ne fait rien : elle recompose la page analysée sans la modifier.

On surchargera ensuite la méthode handle_data pour modifier le texte à notre convenance.

In [39]:
from html.entities import entitydefs

class BaseHTMLProcessor(HTMLParser):
    def reset(self):                        
        self.pieces = []
        HTMLParser.reset(self)

    def handle_starttag(self, tag, attrs): 
        strattrs = "".join([' %s="%s"' % (key, value) 
                            for key, value in attrs])
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

    def handle_endtag(self, tag):          
        self.pieces.append("</%(tag)s>" % locals())

    def handle_charref(self, ref):          
        self.pieces.append("&#%(ref)s;" % locals())

    def handle_entityref(self, ref):
        self.pieces.append("&%(ref)s" % locals())
        if htmlentitydefs.entitydefs.has_key(ref):
            self.pieces.append(";")

    def handle_data(self, text):  # A surcharger         
        self.pieces.append(text)

    def handle_comment(self, text):         
        self.pieces.append("<!--%(text)s-->" % locals())
        
    def handle_pi(self, text):              
        self.pieces.append("<?%(text)s>" % locals())

    def handle_decl(self, text):
        self.pieces.append("<!%(text)s>" % locals())

    def output(self):               
            """Return processed HTML as a single string"""
            return "".join(self.pieces) 
In [40]:
b = BaseHTMLProcessor()
b.feed(s)
In [41]:
b.close()
In [42]:
print (b.pieces)
['<html>', '\n\t', '<body>', '\n\t\t', '<h1>', ' M1 Informatique : Python ', '</h1>', '\n\t\t', '<ul>', '\n\t\t\t', '<li>', ' Commencer ici : ', '<a href="http://www.python.org/">', 'python.org', '</a>', '\n\t\t\t', '<li>', ' La ', '<a href="http://docs.python.org/3.8">', 'documentation', '</a>', ' officielle\n\t\t\t', '<li>', ' Le ', '<a href="http://www.pythonchallenge.com/">', ' Python Challenge', '</a>', '\n\t\t\t', '<li>', ' ', '<a href="https://python.developpez.com/cours/DiveIntoPython/">', '<i>', 'Dive into Python', '</i>', '</a>', '  \n\t\t\tpar ', '<a href="http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29">', 'Mark Pilgrim', '</a>', ' \n\t\t\t(un livre recommandé mais ancien);\n\t\t\t', '<a href="http://diveintopython3.problemsolving.io/">', 'version Python 3', '</a>', '. \n\t\t\t', '<li>', ' La ', '<a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">', 'page', '</a>', ' de Wikipédia contient un bon résumé\n\t\t\t', '<li>', ' Introduction au ', '<i>', 'notebook', '</i>', ' ipython ou jupyter :)\n\t\t\t', '<ul>', ' \n\t\t\t\t', '<li>', ' ', '<a href="intro_jupyter.html">', 'Exemple', '</a>', "  de ce qu'on peut obtenir (converti en html)\n\t\t\t\t", '<li>', ' Le ', '<a href="intro_jupyter.ipynb">', 'code source', '</a>', ' (fichier ', '<tt>', '.ipynb', '</tt>', '), à télécharger\n\t\t\t\t', '<li>', ' Lancer un terminal, aller dans le dossier contenant le fichier ', '<tt>', '.ipynb', '</tt>', ', et entrer la commande ', '<tt>', 'jupyter notebook', '</tt>', '\n\t\t\t', '</ul>', '\n\t\t\t', '<li>', ' Cours 1 (22/9/21) ', '<a href="python3_M1_2021-1.html">', 'html', '</a>', ' - ', '<a href="python3_M1_2021-1.ipynb">', 'notebook', '</a>', '\n\t\t\t', '<li>', ' ', '<a href="td1_python3_2020.html">', ' TD 1', '</a>', ' (22/9/21)\n\t\t\t', '<li>', ' ', '<a href="script3.py">', 'modèle de script', '</a>', ' avec options et documentation\n\t\t\t', '<li>', ' ', '<a href="td1_sol_3_2021.html">', 'Corrigé', '</a>', ' du TD 1\n\t\t\t', '<li>', ' Cours 2 (29/09/2021) ', '<a href="Python3_M1_2021-2.html">', 'html', '</a>', ' - ', '<a href="Python3_M1_2021-2.ipynb">', 'notebook', '</a>', '\n\t\t\t', '<li>', ' ', '<a href="td2_2021_texte.html">', 'TD 2', '</a>', ' (29/09/2021)\n\t\t\t', '<li>', ' ', '<a href="td2_solpy3_2021.html">', 'Corrigé', '</a>', ' du TD 2\n\t\t\t', '<li>', ' Cours 3 (6/10/2021) ', '<a href="python3_M1_2021-3.html">', 'html', '</a>', ' - ', '<a href="python3_M1_2021-3.ipynb">', 'notebook', '</a>', '\n\t\t\t', '<li>', ' ', '<a href="td3_2021_texte.html">', 'TD 3', '</a>', ' (6/10/2021)\n\n\n\n\t\t\n\n\n\t\t\t\n\n\t\t', '</ul>', '\n\t', '</body>', '\n', '</html>', '\n\n\t\t\n']
In [43]:
b.output()
Out[43]:
'<html>\n\t<body>\n\t\t<h1> M1 Informatique : Python </h1>\n\t\t<ul>\n\t\t\t<li> Commencer ici : <a href="http://www.python.org/">python.org</a>\n\t\t\t<li> La <a href="http://docs.python.org/3.8">documentation</a> officielle\n\t\t\t<li> Le <a href="http://www.pythonchallenge.com/"> Python Challenge</a>\n\t\t\t<li> <a href="https://python.developpez.com/cours/DiveIntoPython/"><i>Dive into Python</i></a>  \n\t\t\tpar <a href="http://en.wikipedia.org/wiki/Mark_Pilgrim_%28software_developer%29">Mark Pilgrim</a> \n\t\t\t(un livre recommandé mais ancien);\n\t\t\t<a href="http://diveintopython3.problemsolving.io/">version Python 3</a>. \n\t\t\t<li> La <a href="http://fr.wikipedia.org/wiki/Python_%28langage%29">page</a> de Wikipédia contient un bon résumé\n\t\t\t<li> Introduction au <i>notebook</i> ipython ou jupyter :)\n\t\t\t<ul> \n\t\t\t\t<li> <a href="intro_jupyter.html">Exemple</a>  de ce qu\'on peut obtenir (converti en html)\n\t\t\t\t<li> Le <a href="intro_jupyter.ipynb">code source</a> (fichier <tt>.ipynb</tt>), à télécharger\n\t\t\t\t<li> Lancer un terminal, aller dans le dossier contenant le fichier <tt>.ipynb</tt>, et entrer la commande <tt>jupyter notebook</tt>\n\t\t\t</ul>\n\t\t\t<li> Cours 1 (22/9/21) <a href="python3_M1_2021-1.html">html</a> - <a href="python3_M1_2021-1.ipynb">notebook</a>\n\t\t\t<li> <a href="td1_python3_2020.html"> TD 1</a> (22/9/21)\n\t\t\t<li> <a href="script3.py">modèle de script</a> avec options et documentation\n\t\t\t<li> <a href="td1_sol_3_2021.html">Corrigé</a> du TD 1\n\t\t\t<li> Cours 2 (29/09/2021) <a href="Python3_M1_2021-2.html">html</a> - <a href="Python3_M1_2021-2.ipynb">notebook</a>\n\t\t\t<li> <a href="td2_2021_texte.html">TD 2</a> (29/09/2021)\n\t\t\t<li> <a href="td2_solpy3_2021.html">Corrigé</a> du TD 2\n\t\t\t<li> Cours 3 (6/10/2021) <a href="python3_M1_2021-3.html">html</a> - <a href="python3_M1_2021-3.ipynb">notebook</a>\n\t\t\t<li> <a href="td3_2021_texte.html">TD 3</a> (6/10/2021)\n\n\n\n\t\t\n\n\n\t\t\t\n\n\t\t</ul>\n\t</body>\n</html>\n\n\t\t\n'

Commentaires sur la syntaxe¶

Ce code utilise quelques astuces typiquement pythonesques.

locals() et globals() renvoient des dictionnaires de variables locales et globales ...

In [44]:
def f(x):
    y = 'toto'
    print (locals())
In [45]:
f(42)
{'x': 42, 'y': 'toto'}

Si on venait de lancer l'interpréteur, on aurait

>>> print (globals())
{'f': <function f at 0x402d35a4>, '__builtins__': 
<module '__builtin__' (built-in)>, '__name__': '__main__', 
'__doc__': None}
>>> dir()
['__builtins__', '__doc__', '__name__', 'f']

Mais dans un notebook jupyter, on en a beaucoup trop pour pouvoir les afficher ...

Les lignes

if __name__ == "__main__":
    for k, v in globals().items():
        print (k, "=", v)

ajoutées à la fin du fichier BaseHTMLProcessor.py décrit dans le livre produiraient l'effet suivant, quand le programme est lancé en ligne de commande :

$ python BaseHTMLProcessor.py
__copyright__ = Copyright (c) 2001 Mark Pilgrim
HTMLParser = HTMLParser.HTMLParser
__license__ = Python
__builtins__ = <module '__builtin__' (built-in)>
__file__ = BaseHTMLProcessor.py  [ ... snip ...]

Rappel : formatage par dictionnaire

In [46]:
d = {'animal':'cheval', 'parent':'cousin', 'aliment':'foin', 'jour':'dimanche'}
s = 'Le %(animal)s de mon %(parent)s ne mange du %(aliment)s que le %(jour)s'

s % d
Out[46]:
'Le cheval de mon cousin ne mange du foin que le dimanche'

On peut donc utiliser locals() pour remettre en place les attributs des balises sans avoir à les connaître :

def handle_starttag(self, tag, attrs):
        strattrs = "".join([' %s="%s"' % (key, value) 
                             for key, value in attrs]) 
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

C'est ce procédé qui permet de reconstituer (essentiellement) le HTML qu'on ne souhaite pas modifier.

Pourquoi essentiellement ? A cause des "guillemets":

In [47]:
htmlSource = """        
    <html>
     <head>
     <title>Test page</title>
     </head>
     <body>
     <ul>
     <li><a href=index.html>Home</a></li>
     <li><a href=toc.html>Table of contents</a></li>
     <li><a href=history.html>Revision history</a></li>
     </body>
     </html>"""


parser = BaseHTMLProcessor()
parser.feed(htmlSource) 
parser.close()
print (parser.output())   
        
    <html>
     <head>
     <title>Test page</title>
     </head>
     <body>
     <ul>
     <li><a href="index.html">Home</a></li>
     <li><a href="toc.html">Table of contents</a></li>
     <li><a href="history.html">Revision history</a></li>
     </body>
     </html>

Le dialectiseur¶

On construit ensuite une classe Dialectizer qui dérive de BaseHTMLProcessor. Son rôle est de "traduire" le texte de la page, sauf lorsqu'il doit être rendu verbatim (<pre>...</pre>).

Il lui faudra donc un attribut verbatim qui permet de savoir si l'on doit traduire ou pas :

Ceci étant acquis, on peut maintenant surcharger handle_data :

def handle_data(self, text):                                         
    self.pieces.append(self.verbatim 
                       and text 
                       or self.process(text))

La méthode process dépendra de la traduction désirée.

On remarquera l'usa ge astucieux des booléens

>>> (1==1) and 'toto'
'toto'
>>> (1==0) and 'toto'
False
>>> (1==0) or 'toto'
'toto'
>>> (1==0) and 'toto' or 'titi'
'titi'

Explication (attention à l'ordre !) :

>>> 'toto' and (1==1)
True
>>> 'toto' and (1==0)
False
>>>

La sémantique est

x or y   -->  if x is false, then y, else x
x and y  -->  if x is false, then x, else y

Brouilleur de page web : le code complet¶

Il faut un module de traduction, par exemple celui-ci :¶

# module touille.py
import random, re

p = re.compile('(\w)(\w+)(\w)', re.M)

def touille(m):
    milieu = list(m.group(2))
    random.shuffle(milieu)
    return m.group(1) + ''.join(milieu) + m.group(3)


def blurr(s):
    return p.sub(touille,s)

La classe BaseHTMLProcessor¶

# file newHTMLProcessor.py
"""Base class for creating HTML processing modules

This class is designed to take HTML as input and spit out equivalent
HTML as output.  By itself it's not very interesting; you use it by
subclassing it and providing the methods you need to create your HTML
transformation.

Adapted from Mark Pilgrim's book "Dive into Python".
sgmllib.SGMLParser replaced in Python 3 by html.parser.HTMLParser.

"""

from html.parser import HTMLParser
from html.entities import entitydefs

class BaseHTMLProcessor(HTMLParser):
    def reset(self):
        # extend (called by HTMLParser.__init__)
        self.pieces = []
        HTMLParser.reset(self)

    def handle_starttag(self, tag, attrs):
        # called for each start tag
        # attrs is a list of (attr, value) tuples
        # e.g. for <pre class="screen">, tag="pre", attrs=[("class", "screen")]
        # Ideally we would like to reconstruct original tag and attributes, but
        # we may end up quoting attribute values that weren't quoted in the source
        # document, or we may change the type of quotes around the attribute value
        # (single to double quotes).
        # Note that improperly embedded non-HTML code (like client-side Javascript)
        # may be parsed incorrectly by the ancestor, causing runtime script errors.
        # All non-HTML code must be enclosed in HTML comment tags (<!-- code -->)
        # to ensure that it will pass through this parser unaltered (in handle_comment).
        strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

    def handle_endtag(self, tag):
        # called for each end tag, e.g. for </pre>, tag will be "pre"
        # Reconstruct the original end tag.
        self.pieces.append("</%(tag)s>" % locals())

    def handle_charref(self, ref):
        # called for each character reference, e.g. for "&#160;", ref will be "160"
        # Reconstruct the original character reference.
        self.pieces.append("&#%(ref)s;" % locals())

    def handle_entityref(self, ref):
        # called for each entity reference, e.g. for "&copy;", ref will be "copy"
        # Reconstruct the original entity reference.
        self.pieces.append("&%(ref)s" % locals())
        # standard HTML entities are closed with a semicolon; other entities are not
        if htmlentitydefs.entitydefs.has_key(ref):
            self.pieces.append(";")

    def handle_data(self, text): 
        # called for each block of plain text, i.e. outside of any tag and
        # not containing any character or entity references
        # Store the original text verbatim.
        self.pieces.append(text)

    def handle_comment(self, text): 
        # called for each HTML comment, e.g. <!-- insert Javascript code here -->
        # Reconstruct the original comment.
        # It is especially important that the source document enclose client-side
        # code (like Javascript) within comments so it can pass through this
        # processor undisturbed; see comments in unknown_starttag for details.
        self.pieces.append("<!--%(text)s-->" % locals())

    def handle_pi(self, text): 
        # called for each processing instruction, e.g. <?instruction>
        # Reconstruct original processing instruction.
        self.pieces.append("<?%(text)s>" % locals())

    def handle_decl(self, text): 
        # called for the DOCTYPE, if present, e.g.
        # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        #    "http://www.w3.org/TR/html4/loose.dtd">
        # Reconstruct original DOCTYPE
        self.pieces.append("<!%(text)s>" % locals())

    def output(self):
        """Return processed HTML as a single string"""
        return "".join(self.pieces)

if __name__ == "__main__":
    for k, v in globals().items():
        print (k, "=", v)

Le programme principal blurweb3.py¶

#!/usr/bin/env python
"""Web page blurrer for Python

This program is adapted from "Dive Into Python", a free Python book for
experienced programmers.  Visit http://diveintopython.org/ for the
latest version.

New version using Python 3, html.parser and including images.
"""

__author__ = "Mark Pilgrim (mark@diveintopython.org)"
__updated_by__ = "Jean-Yves Thibon"
__version__ = "$Revision: 3.0 $"
__date__ = "$Date: 2019/10/07 $"
__copyright__ = "Copyright (c) 2001 Mark Pilgrim"
__license__ = "Python"

import re
from newHTMLProcessor import BaseHTMLProcessor
from touille import blurr
import codecs
from urllib.parse import urljoin

class Dialectizer(BaseHTMLProcessor):
        subs = ()
        def __init__(self,root_url=None):# ajout
                BaseHTMLProcessor.__init__(self)
                self.url = root_url

        def __compl(self, x):# pair (key, value)
                if x[0] == 'href' or x[0] == 'src':
                        return (x[0], urljoin(self.url,x[1]))
                else: return x

        def reset(self):
                # extend (called from __init__ in ancestor)
                # Reset all data attributes
                self.verbatim = 0
                BaseHTMLProcessor.reset(self)

        def handle_starttag(self,tag,args):
                if self.url:
                        args = [self.__compl(x) for x in args]
                if tag in ["pre","script","style"]: self.verbatim += 1
                strattrs = "".join([' %s="%s"' % (key, value) for key, value in args])
                self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

        def handle_endtag(self,tag):
                # called for every </pre> tag in HTML source
                # Decrement verbatim mode count
                if tag in ["pre","script","style"]: self.verbatim -= 1
                self.pieces.append("</%(tag)s>" % locals())

        def handle_data(self, text):
                # override
                # called for every block of text in HTML source
                # If in verbatim mode, save text unaltered;
                # otherwise process the text with a series of substitutions
                self.pieces.append(self.verbatim and text or self.process(text))

        def process(self, text):
                # called from handle_data
                text = blurr(text)
                return text


def translate(url):
        """fetch URL and blurr"""
        from urllib.request import urlopen
        sock = urlopen(url)
        htmlSource = sock.read()
        sock.close()
        s = htmlSource.decode('utf-8')
        parser = Dialectizer(url)#test
        parser.feed(s)
        parser.close()
        return parser.output()

def test(url):
        """test  against URL"""
        outfile = "touillage.html"
        fsock = codecs.open(outfile, "wb",encoding='UTF-8')
        fsock.write(translate(url))
        fsock.close()
        import webbrowser
        K = webbrowser.Mozilla('firefox')
        webbrowser.register('firefox', None, K)


        K.open_new(outfile)

if __name__ == "__main__":
        s = test("http://igm.univ-mlv.fr/~jyt/")

screen8.png

Ce code est instructif, mais assez compliqué. On peut faire plus simple avec BeautifulSoup.

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: