urllib - semplice interfaccia per accedere a risorse di rete

Scopo Accede a risorse remote che non necessitano di autenticazione, cookie ecc.
Versione Python 1.4 e successivo

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

HTTP GET

Il server di test per questi esempi è BaseHTTPServer_GET.py, dagli esempi di BaseHTTPServer. Lanciare il server da una finestra di terminale, quindi eseguire gli esempi da un'altra.

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

Codificare gli Argomenti

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/

HTTP POST

Il server di test per questi esempi è BaseHTTPServer_POST.py, dagli esempi per BaseHTTPServer. Lanciare il server in una finestra di terminale, quindi eseguire questi esempi in un'altra.

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.

Percorsi contro URL

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

Semplice Recupero con Cache

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

URLopener

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.

Vedere anche:

urllib
La documentazione della libreria standard per questo modulo
urllib2
API aggiornate per lavorare con servizi basati sugli URL
urlparse
Lavora con la stringa dell'URL