Scopo | Accede a risorse remote che non necessitano di autenticazione, cookie ecc. |
Versione Python | 1.4 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 urllib fornisce semplice interfaccia per accedere a risorse di rete. Sebbene urllib possa essere usato con gopher e ftp gli esempi seguenti usano tutti http
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 urllib
response = urllib.urlopen('http://localhost:8080/')
print 'RISPOSTA :', response
print 'URL :', response.geturl()
headers = response.info()
print 'DATA :', headers['date']
print 'HEADERS :'
print '---------'
print headers
data = response.read()
print 'LUNGHEZZA:', 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 urllib_urlopen.py RISPOSTA :> URL : http://localhost:8080/ DATA : Sat, 05 Jul 2014 09:11:22 GMT HEADERS : --------- Server: BaseHTTP/0.3 Python/2.7.6 Date: Sat, 05 Jul 2014 09:11:22 GMT LUNGHEZZA: 321 DATI : --------- VALORI DEL CLIENT: client_address=('127.0.0.1', 40969) (localhost) command=GET path=/ real path=/ query= request_version=HTTP/1.0 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: host=localhost:8080 user-agent=Python-urllib/1.17
L'oggetto di tipo file ritornato da
urlopen()
è iterabile:
import urllib
response = urllib.urlopen('http://localhost:8080/')
for line in response:
print line.rstrip()
Visto che le righe sono resituite con ritorni a capo ed avanti riga intatti, questo esempio li elimina prima di stampare l'output
$ python urllib_urlopen_iterator.py VALORI DEL CLIENT: client_address=('127.0.0.1', 40996) (localhost) command=GET path=/ real path=/ query= request_version=HTTP/1.0 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: host=localhost:8080 user-agent=Python-urllib/1.17
Gli argomenti possono essere passati al server codificandoli ed accodandoli all'URL
import urllib
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
print 'Codificato:', encoded_args
url = 'http://localhost:8080/?' + encoded_args
print urllib.urlopen(url).read()
Si noti che la query, nella lista dei valori del client, contiene gli argomenti di ricerca codificati.
$ python urllib_urlencode.py Codificato: q=stringa+di+richiesta&foo=bar VALORI DEL CLIENT: client_address=('127.0.0.1', 41208) (localhost) command=GET path=/?q=stringa+di+richiesta&foo=bar real path=/ query=q=stringa+di+richiesta&foo=bar request_version=HTTP/1.0 VALORI DEL SERVER: server_version=BaseHTTP/0.3 sys_version=Python/2.7.6 protocol_version=HTTP/1.0 INTESTAZIONI RICEVUTE: host=localhost:8080 user-agent=Python-urllib/1.17
Per passare una sequenza di valori usando occorrenze distinte della variabile nella stringa di query, si imposta
doseq
a
True
nella chiamata di
urlencode()
import urllib
query_args = { 'foo':['foo1', 'foo2'] }
print 'Singola :', urllib.urlencode(query_args)
print 'Sequenza:', urllib.urlencode(query_args, doseq=True )
$ python urllib_urlencode_doseq.py Singola : foo=%5B%27foo1%27%2C+%27foo2%27%5D Sequenza: foo=foo1&foo=foo2
Per decodificare la stringa di query, impostare la classe
FieldStorage
dal modulo
cgi
.
Caratteri speciali all'interno degli argomenti di query che potrebbero causare problemi di elaborazione sono racchiusi tra virgolette quando passati ad
urlencode
. Per fare questo localmente per ottenere versioni sicure delle stringhe, si possono usare direttamente le funzioni
quote()
oppure
quote_plus()
.
import urllib
url = 'http://localhost:8080/~dhellmann/'
print 'urlencode() :', urllib.urlencode({'url':url})
print 'quote() :', urllib.quote(url)
print 'quote_plus():', urllib.quote_plus(url)
Si noti che
quote_plus()
è più aggressivo rispetto ai caratteri che sostituisce.
$ python urllib_quote.py urlencode() : url=http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F quote() : http%3A//localhost%3A8080/%7Edhellmann/ quote_plus(): http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F
Per invertire l'operazione di inserimento virgolette si usa
quote()
oppure
unquote_plus()
a seconda delle esigenze.
import urllib
print urllib.unquote('http%3A//localhost%3A8080/%7Edhellmann/')
print urllib.unquote_plus('http%3A%2F%2Flocalhost%3A8080%2F%7Edhellmann%2F')
$ python urllib_unquote.py http://localhost:8080/~dhellmann/ http://localhost:8080/~dhellmann/
Se si usa POST per inviare dati al server remoto, invece che usare GET, si passano gli argomenti codificati della query come dati ad
urlopen()
invece di accodarli all'URL.
import urllib
query_args = { 'q':'query string', 'foo':'bar' }
encoded_args = urllib.urlencode(query_args)
url = 'http://localhost:8080/'
print urllib.urlopen(url, encoded_args).read()
$ python urllib_urlopen_post.py Client: ('127.0.0.1', 41520) User-agent: Python-urllib/1.17 Path: / Dati form: q=stringa di richiesta foo=bar
Si può inviare qualsiasi stringa di byte come dato, nel caso in cui il server si aspetti qualcosa di diverso da argomenti di form codificati nei dati inviati.
Alcuni sistemi operativi usano valori diversi dagli URL per separare i componenti dei percorsi per i file locali. Per rendere il proprio codice portabile, si dovrebbero usare le funzioni
pathname2url()
e
url2pathname()
per convertire nei due sensi. Visto che sto lavorando (Doug Hellman - n.d.t.) su di un Mac, devo importare esplicitamente le versioni Windows delle funzioni. Usando le versioni delle funzioni esportate da
urllib
si ottengono i valori predefiniti corretti per la propria piattaforma, quindi questo non è necessario
import os
from urllib import pathname2url, url2pathname
print '== Predefinito =='
path = '/a/b/c'
print 'Originale:', path
print 'URL :', pathname2url(path)
print 'Percorso :', url2pathname('/d/e/f')
print
from nturl2path import pathname2url, url2pathname
print '== Windows, senza lettere di drive =='
path = path.replace('/', '\\')
print 'Originale:', path
print 'URL :', pathname2url(path)
print 'Percorso :', url2pathname('/d/e/f')
print
print '== Windows, con lettere di drive =='
path = 'C:\\' + path.replace('/', '\\')
print 'Originale:', path
print 'URL :', pathname2url(path)
print 'Percorso :', url2pathname('/d/e/f')
Ci sono due esempi per Windows, con o senza l'identificativo del drive all'inizio del percorso
$ python urllib_pathnames.py == Predefinito == Originale: /a/b/c URL : /a/b/c Percorso : /d/e/f == Windows, senza lettere di drive == Originale: \a\b\c URL : /a/b/c Percorso : \d\e\f == Windows, con lettere di drive == Originale: C:\\a\b\c URL : ///C:/a/b/c Percorso : \d\e\f
Recuperare dati è una operazione comune, ed
urllib
comprende la funzione
urlretrieve()
in modo che non serve scriversene una.
urlretrieve()
ottiene argomenti che rappresentano l'URL, un file temporaneo per contenere i dati, una funzione per registrare la progressione dello scaricamento e dati da passare se l'URL fa riferimento ad un form dove i dati devono essere inviati in formato di POST. Se non viene passato alcun nome di file,
urlretrieve()
crea un file temporaneo. Esso può essere rimosso dal programmatore oppure si può trattare il file come se fosse cache ed usare
urlcleanup()
per eliminarlo.
L'esempio seguente utilizza GET per recuperare qualche dato da un server web:
import os
import urllib
import os
def reporthook(blocks_read, block_size, total_size):
if not blocks_read:
print 'Connessione aperta'
return
if total_size < 0:
# Dimensione sconosciuta
print 'Letti %d blocchi' % blocks_read
else:
amount_read = blocks_read * block_size
print 'Letti %d blocchi, oppure %d/%d' % (blocks_read, amount_read, total_size)
return
try:
filename, msg = urllib.urlretrieve('http://blog.doughellmann.com/', reporthook=reporthook)
print
print 'File:', filename
print 'Headers:'
print msg
print 'Il file esiste prima della pulizia:', os.path.exists(filename)
finally:
urllib.urlcleanup()
print 'Il file esiste ancora:', os.path.exists(filename)
Visto che il server non ritorna un header
Content-length
,
urlretrieve()
non conosce la dimensione dei dati e passa -1 come argomento del parametro
total_size
a
reporthook()
.
$ python urllib_urlretrieve.py Connessione aperta Letti 1 blocchi, oppure 8192/30877 Letti 2 blocchi, oppure 16384/30877 Letti 3 blocchi, oppure 24576/30877 Letti 4 blocchi, oppure 32768/30877 File: /tmp/tmp_CVth3 Headers: Date: Sat, 05 Jul 2014 09:54:24 GMT Server: Apache Last-Modified: Mon, 23 Jun 2014 19:20:01 GMT ETag: "789d-4fc85bac27a40" Accept-Ranges: bytes Content-Length: 30877 Vary: Accept-Encoding Connection: close Content-Type: text/html X-Pad: avoid browser bug Il file esiste prima della pulizia: True Il file esiste ancora: False
urllib
fornisce una classe base
URLopener
e
FancyURLopener
per la gestione predefinita dei protocolli supportati. Se si desidera modificare questo comportamento, è meglio utilizzare il modulo
urllib2
incluso nella verione Python 2.1.