Scopo | Una libreria per aprire gli URL che può essere estesa definendo gestori di protocollo personalizzati. |
Versione Python | 2.1 e successivo |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per le versioni 3.x di Python
Il modulo urllib2 fornisce una API aggiornata per usare le risorse internet identificate da URL . E' progettato per essere esteso da applicazioni individuali per supportare nuovi protocolli od aggiungere variazioni a quelli esistenti (tipo la gestione di base dell'autenticazione HTTP).
Così come per
urllib
una operazione HTTP GET costituisce l'uso più semplice di
urllib2
. Si passa l'URL ad
urlopen()
per ottenere un
handle
di tipo file per i dati remoti.
import urllib2
response = urllib2.urlopen('http://localhost:8080/')
print 'RISPOSTA:', response
print 'URL :', response.geturl()
headers = response.info()
print 'DATA :', headers['date']
print 'HEADER :'
print '---------'
print headers
data = response.read()
print 'LUNGH. :', len(data)
print 'DATI :'
print '---------'
print data
Il server di esempio accetta i valori in arrivo e formatta una risposta in testo semplice da restituire. Il valore di ritorno da
urllopen()
fornisce l'accesso agli
header
dal server HTTP tramite il metodo
info()
, ed i dati dalla risorsa remota tramite metodi tipo
read()
e
readlines()
.
$ python urllib2_urlopen.py RISPOSTA: <addinfourl at 140688029658360 whose fp = <socket._fileobject object at 0x7ff47c065bd0>> URL : http://localhost:8080/ DATA : Mon, 30 Jun 2014 19:24:48 GMT HEADER : --------- Server: BaseHTTP/0.3 Python/2.7.6 Date: Mon, 30 Jun 2014 19:24:48 GMT LUNGH. : 364 DATI : --------- VALORI DEL CLIENT: client_address=('127.0.0.1', 37150) (localhost) command=GET path=/ real path=/ query= request_version=HTTP/1.1 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: accept-encoding=identity connection=close host=localhost:8080 user-agent=Python-urllib/2.7
L'oggetto di tipo file ritornato da
urlopen()
è iterabile:
import urllib2
response = urllib2.urlopen('http://localhost:8080/')
for line in response:
print line.rstrip()
Questo esempio elimina i ritorni a capo ed avanti riga prima di stampare l'output
$ python urllib2_urlopen_iterator.py VALORI DEL CLIENT: client_address=('127.0.0.1', 37264) (localhost) command=GET path=/ real path=/ query= request_version=HTTP/1.1 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: accept-encoding=identity connection=close host=localhost:8080 user-agent=Python-urllib/2.7
Gli argomenti possono essere passati al server codificandoli con
urllib.urlencode()
ed accodati all'URL
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
print 'Codificato:', encoded_args
url = 'http://localhost:8080/?' + encoded_args
print urllib2.urlopen(url).read()
L'elenco di valori del client restituiti nell'output di esempio contengono gli argomenti di ricerca codificati
$ python urllib2_http_get_args.py Codificato: q=query+string&foo=bar VALORI DEL CLIENT: client_address=('127.0.0.1', 44153) (localhost) command=GET path=/?q=query+string&foo=bar real path=/ query=q=query+string&foo=bar request_version=HTTP/1.1 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: accept-encoding=identity connection=close host=localhost:8080 user-agent=Python-urllib/2.7
Se si usa POST per inviare dati da form codificati al server remoto, invece che usare GET, si passano gli argomenti codificati della query come dati ad
urlopen()
.
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
url = 'http://localhost:8080/'
print urllib2.urlopen(url, encoded_args).read()
Il server può decodificare i dati del form ed accedere ai valori individuali tramite nome
$ python urllib2_urlopen_post.py Client: ('127.0.0.1', 44414) User-agent: Python-urllib/2.7 Path: / Dati form: q=query string foo=bar
urlopen()
è una funzione di convenienza che nasconde alcuni dei dettagli di come la richiesta sia fatta e gestita per conto del programmatore. Per un controllo più preciso, si dovrebbe istanziare ed usare direttamente un oggetto
Request
.
Come dimostrato dall'esempio qui sopra il valore di header predefinito
User-agent
è costruito dalla costante
Python-urllib
, seguita dalla versione dell'interprete di Python. Se si sta creando una applicazione che dovrà accedere a risorse web di terze parti, è cortesia includere informazioni reali circa l'
User-agent
nella propria richiesta, in modo che essi possano identificare la sorgente del contatto più agevolmente. Usare un
agent
personalizzato consente anche di controllare i crawler usando un file robots.txt (vedi
robotparser
).
import urllib2
request = urllib2.Request('http://localhost:8080/')
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
response = urllib2.urlopen(request)
data = response.read()
print data
Dopo avere creato un oggetto
Request
, si usa
add_header()
per impostare il valore di
User-agent
prima di aprire la richiesta. L'ultima riga dell'output mostra il valore personalizzato.
$ python urllib2_request_header.py VALORI DEL CLIENT: client_address=('127.0.0.1', 44537) (localhost) command=GET path=/ real path=/ query= request_version=HTTP/1.1 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: accept-encoding=identity connection=close host=localhost:8080 user-agent=PyMOTW (http://www.doughellmann.com/PyMOTW/)
Si possono impostare i dati in uscita in
Request
per inviare i dati al server.
import urllib
import urllib2
query_args = { 'q':'query string', 'foo':'bar' }
request = urllib2.Request('http://localhost:8080/')
print 'Metodo Request prima dei dati:', request.get_method()
request.add_data(urllib.urlencode(query_args))
print 'Metodo Request dopo i dati :', request.get_method()
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
print
print 'DATI IN USCITA :'
print request.get_data()
print
print 'RISPOSTA DEL SERVER:'
print urllib2.urlopen(request).read()
Il metodo HTTP usato da
Request
cambia da GET a POST automaticamente dopo che i dati sono stati aggiunti.
$ python urllib2_request_post.py Metodo Request prima dei dati: GET Metodo Request dopo i dati : POST DATI IN USCITA : q=query+string&foo=bar RISPOSTA DEL SERVER: Client: ('127.0.0.1', 44636) User-agent: PyMOTW (http://www.doughellmann.com/PyMOTW/) Path: / Dati form: q=query string foo=bar
add_data()
(aggiungi dati - n.d.t.), il suo effetto
non
è cumulativo. Ogni chiamata sostituisce i dati precedenti.
Codificare file per l'invio richiede maggior lavoro dei semplici form. Un messaggio MIME completo deve essere costruito nel corpo della richiesta, in modo che il server possa distinguere i campi del forma in arrivo dai file inviati.
import itertools
import mimetools
import mimetypes
from cStringIO import StringIO
import urllib
import urllib2
class MultiPartForm(object):
"""Accumula i dati da usare quando si invia un form."""
def __init__(self):
self.form_fields = []
self.files = []
self.boundary = mimetools.choose_boundary()
return
def get_content_type(self):
return 'multipart/form-data; boundary=%s' % self.boundary
def add_field(self, name, value):
"""Aggiunge un semplice campi ai dati del form."""
self.form_fields.append((name, value))
return
def add_file(self, fieldname, filename, fileHandle, mimetype=None):
"""Aggiunge un file da inviare."""
body = fileHandle.read()
if mimetype is None:
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
self.files.append((fieldname, filename, mimetype, body))
return
def __str__(self):
"""Ritorna una stringa che rappresenta i dati del form, compresi i file allegati."""
# Costruisce una lista di liste, ognuna contenente righe ("lines") della
# richiesta. Ogni parte è separata da una stringa di limite.
# Una volta costruita la lista, si ritorna una stringa con ciascuna riga
# separata da '\r\n'.
parts = []
part_boundary = '--' + self.boundary
# Aggiunge i campi del form
parts.extend(
[ part_boundary,
'Content-Disposition: form-data; name="%s"' % name,
'',
value,
]
for name, value in self.form_fields
)
# Aggiunge i file da inviare
parts.extend(
[ part_boundary,
'Content-Disposition: file; name="%s"; filename="%s"' % \
(field_name, filename),
'Content-Type: %s' % content_type,
'',
body,
]
for field_name, filename, content_type, body in self.files
)
# Riunisce le liste ed aggiunge il marcatori di limite di chiusura,
# poi ritorna i dati separati da CR/LF
flattened = list(itertools.chain(*parts))
flattened.append('--' + self.boundary + '--')
flattened.append('')
return '\r\n'.join(flattened)
if __name__ == '__main__':
# Crea il form con semplici campi
form = MultiPartForm()
form.add_field('firstname', 'Doug')
form.add_field('lastname', 'Hellmann')
# Aggiunge un falso file
form.add_file('biography', 'bio.txt',
fileHandle=StringIO('Python developer and blogger.'))
# Costruisce la richiesta
request = urllib2.Request('http://localhost:8080/')
request.add_header('User-agent', 'PyMOTW (http://www.doughellmann.com/PyMOTW/)')
body = str(form)
request.add_header('Content-type', form.get_content_type())
request.add_header('Content-length', len(body))
request.add_data(body)
print
print 'DATI IN USCITA :'
print request.get_data()
print
print 'RISPOSTA DEL SERVER:'
print urllib2.urlopen(request).read()
La classe
MultiPartForm
può rappresentare un form arbitrario come messaggio multi-part MIME con file allegati.
$ python urllib2_upload_files.py DATI IN USCITA : --127.0.1.1.1000.9990.1404243104.851.1 Content-Disposition: form-data; name="firstname" Doug --127.0.1.1.1000.9990.1404243104.851.1 Content-Disposition: form-data; name="lastname" Hellmann --127.0.1.1.1000.9990.1404243104.851.1 Content-Disposition: file; name="biography"; filename="bio.txt" Content-Type: text/plain Python developer and blogger. --127.0.1.1.1000.9990.1404243104.851.1-- RISPOSTA DEL SERVER: Client: ('127.0.0.1', 44954) User-agent: PyMOTW (http://www.doughellmann.com/PyMOTW/) Path: / Dati form: lastname=Hellmann Inviato biography as "bio.txt" (29 bytes) firstname=Doug
urllib2
ha supporto
built-in
per accesso ad HTTP(S), FTP e file locali. Se si deve aggiungere supporto per altri tipi di
URL
si può registrare il proprio gestore di protocollo che verrà chiamato quando necessario. Ad esempio, se si vuole supportare il puntamento di URL a file arbitrari su server NFS remoti, senza richiedere che i propri utenti montino il percorso manualmente, si dovrebbe creare una classe derivata da
BaseHandler
con un metodo
nfs_open()
.
Il metodo
nfs_open()
riceve un singolo argomento, l'istanza di
Request
, e dovrebbe ritornare un oggetto con un metodo
read()
che può essere usato per leggere dati, un metodo
info()
che restituisce gli header della risposta, e
geturl()
che ritorna il vero URL del file che si sta leggendo. Un semplice modo per ottenere questo è di creare una istanza di
urllib.addurlinfo
, passandole gli header, URL e l'handle del file aperto nel costruttore.
import mimetypes
import os
import tempfile
import urllib
import urllib2
class NFSFile(file):
def __init__(self, tempdir, filename):
self.tempdir = tempdir
file.__init__(self, filename, 'rb')
def close(self):
print
print 'NFSFile:'
print ' sto smontando %s' % self.tempdir
print ' quando %s è chiuso' % os.path.basename(self.name)
return file.close(self)
class FauxNFSHandler(urllib2.BaseHandler):
def __init__(self, tempdir):
self.tempdir = tempdir
def nfs_open(self, req):
url = req.get_selector()
directory_name, file_name = os.path.split(url)
server_name = req.get_host()
print
print 'FauxNFSHandler simula il mount:'
print ' Percorso remoto: %s' % directory_name
print ' Server : %s' % server_name
print ' Percorso locale: %s' % tempdir
print ' Nome file : %s' % file_name
local_file = os.path.join(tempdir, file_name)
fp = NFSFile(tempdir, local_file)
content_type = mimetypes.guess_type(file_name)[0] or 'application/octet-stream'
stats = os.stat(local_file)
size = stats.st_size
headers = { 'Content-type': content_type,
'Content-length': size,
}
return urllib.addinfourl(fp, headers, req.get_full_url())
if __name__ == '__main__':
tempdir = tempfile.mkdtemp()
try:
# Popola il file temporaneo per la simulazione
with open(os.path.join(tempdir, 'file.txt'), 'wt') as f:
f.write('Contenuto di file.txt')
# Costruisce un oggetto per l'apertura con l'handler NFS
# e lo registra come predifinito.
opener = urllib2.build_opener(FauxNFSHandler(tempdir))
urllib2.install_opener(opener)
# Apre il file tramite un URL
response = urllib2.urlopen('nfs://server_remoto/percorso/a/file.txt')
print
print 'LEGGE CONTENUTO:', response.read()
print 'URL :', response.geturl()
print 'HEADERS:'
for name, value in sorted(response.info().items()):
print ' %-15s = %s' % (name, value)
response.close()
finally:
os.remove(os.path.join(tempdir, 'file.txt'))
os.removedirs(tempdir)
Le classi
FauxNFSHandler
e
NFSFile
stampano messaggi per illustrare dove una vera implementazione avrebbe aggiunto le chiamate per il montaggio e lo smontaggio. Visto che si tratta solamente di una simulazione, a
FauxNFSHandler
viene assegnato il nome di una directory temporanea dove dovrebbe cercare tutti i propri file.
$ python urllib2_nfs_handler.py FauxNFSHandler simula il mount: Percorso remoto: /percorso/a Server : server_remoto Percorso locale: /tmp/tmps9C2Yq Nome file : file.txt LEGGE CONTENUTO: Contenuto di file.txt URL : nfs://server_remoto/percorso/a/file.txt HEADERS: Content-length = 21 Content-type = text/plain NFSFile: sto smontando /tmp/tmps9C2Yq quando file.txt è chiuso
Vedere anche: