HOWTO: Internetressourcen mit dem urllib-Paket abrufen¶
- Autor:
Einleitung¶
urllib.request ist ein Python-Modul zum Abrufen von URLs (Uniform Resource Locators). Es bietet eine sehr einfache Schnittstelle in Form der Funktion urlopen. Diese ist in der Lage, URLs über eine Vielzahl von Protokollen abzurufen. Es bietet auch eine etwas komplexere Schnittstelle zur Handhabung gängiger Situationen – wie Basis-Authentifizierung, Cookies, Proxies und so weiter. Diese werden durch Objekte namens Handler und Opener bereitgestellt.
urllib.request unterstützt das Abrufen von URLs für viele "URL-Schemata" (identifiziert durch die Zeichenkette vor dem ":" in der URL – zum Beispiel ist "ftp" das URL-Schema von "ftp://pythonlang.de/") unter Verwendung ihrer zugehörigen Netzwerkprotokolle (z. B. FTP, HTTP). Dieses Tutorial konzentriert sich auf den häufigsten Fall, HTTP.
Für einfache Situationen ist urlopen sehr einfach zu bedienen. Aber sobald Sie Fehler oder nicht-triviale Fälle beim Öffnen von HTTP-URLs antreffen, benötigen Sie ein gewisses Verständnis des HyperText Transfer Protocol. Die umfassendste und maßgeblichste Referenz zu HTTP ist RFC 2616. Dies ist ein technisches Dokument und nicht leicht zu lesen. Dieses HOWTO zielt darauf ab, die Verwendung von urllib mit genügend Details über HTTP zu veranschaulichen, um Ihnen zu helfen. Es ist nicht dazu bestimmt, die Dokumentation zu urllib.request zu ersetzen, sondern ergänzt sie.
URLs abrufen¶
Der einfachste Weg, urllib.request zu verwenden, ist wie folgt:
import urllib.request
with urllib.request.urlopen('https://pythonlang.de/') as response:
html = response.read()
Wenn Sie eine Ressource über eine URL abrufen und in einem temporären Speicherort speichern möchten, können Sie dies über die Funktionen shutil.copyfileobj() und tempfile.NamedTemporaryFile() tun.
import shutil
import tempfile
import urllib.request
with urllib.request.urlopen('https://pythonlang.de/') as response:
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
shutil.copyfileobj(response, tmp_file)
with open(tmp_file.name) as html:
pass
Viele Verwendungen von urllib werden so einfach sein (beachten Sie, dass wir anstelle einer 'http:'-URL eine URL verwenden könnten, die mit 'ftp:', 'file:' usw. beginnt). Der Zweck dieses Tutorials ist es jedoch, die komplizierteren Fälle zu erklären, wobei der Schwerpunkt auf HTTP liegt.
HTTP basiert auf Anfragen und Antworten – der Client stellt Anfragen und Server senden Antworten. urllib.request spiegelt dies mit einem Request-Objekt wider, das die von Ihnen gestellte HTTP-Anfrage repräsentiert. In seiner einfachsten Form erstellen Sie ein Request-Objekt, das die abzurufende URL angibt. Das Aufrufen von urlopen mit diesem Request-Objekt gibt ein Response-Objekt für die angeforderte URL zurück. Diese Antwort ist ein dateiähnliches Objekt, was bedeutet, dass Sie beispielsweise .read() für die Antwort aufrufen können.
import urllib.request
req = urllib.request.Request('https://pythonlang.de/')
with urllib.request.urlopen(req) as response:
the_page = response.read()
Beachten Sie, dass urllib.request dieselbe Request-Schnittstelle verwendet, um alle URL-Schemata zu behandeln. Sie können beispielsweise eine FTP-Anfrage wie folgt stellen:
req = urllib.request.Request('ftp://example.com/')
Im Falle von HTTP gibt es zwei zusätzliche Dinge, die Request-Objekte ermöglichen: Erstens können Sie Daten zum Senden an den Server übergeben. Zweitens können Sie zusätzliche Informationen ("Metadaten") über die Daten oder über die Anfrage selbst an den Server übergeben – diese Informationen werden als HTTP-"Header" gesendet. Betrachten wir jeden dieser Punkte der Reihe nach.
Daten¶
Manchmal möchten Sie Daten an eine URL senden (oft verweist die URL auf ein CGI-Skript (Common Gateway Interface) oder eine andere Webanwendung). Mit HTTP geschieht dies häufig mit einer sogenannten **POST**-Anfrage. Dies ist oft das, was Ihr Browser tut, wenn Sie ein HTML-Formular absenden, das Sie im Web ausgefüllt haben. Nicht alle POSTs müssen von Formularen stammen: Sie können POST verwenden, um beliebige Daten an Ihre eigene Anwendung zu übertragen. Im häufigen Fall von HTML-Formularen müssen die Daten in einer Standardweise codiert und dann als data-Argument an das Request-Objekt übergeben werden. Die Codierung erfolgt über eine Funktion aus der Bibliothek urllib.parse.
import urllib.parse
import urllib.request
url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
data = urllib.parse.urlencode(values)
data = data.encode('ascii') # data should be bytes
req = urllib.request.Request(url, data)
with urllib.request.urlopen(req) as response:
the_page = response.read()
Beachten Sie, dass manchmal andere Codierungen erforderlich sind (z. B. für den Datei-Upload aus HTML-Formularen – siehe HTML-Spezifikation, Formularübermittlung für weitere Details).
Wenn Sie das data-Argument nicht übergeben, verwendet urllib eine **GET**-Anfrage. Eine Art, wie GET- und POST-Anfragen sich unterscheiden, ist, dass POST-Anfragen oft "Nebeneffekte" haben: Sie verändern den Zustand des Systems in irgendeiner Weise (zum Beispiel durch eine Bestellung bei der Website für einhundert Kilogramm Dosen-Schinken, die zu Ihnen nach Hause geliefert werden sollen). Obwohl der HTTP-Standard klarstellt, dass POSTs immer Nebeneffekte verursachen sollen und GET-Anfragen niemals Nebeneffekte verursachen sollen, hindert nichts eine GET-Anfrage daran, Nebeneffekte zu haben, noch eine POST-Anfrage daran, keine Nebeneffekte zu haben. Daten können auch in einer HTTP-GET-Anfrage übergeben werden, indem sie in der URL selbst codiert werden.
Dies geschieht wie folgt:
>>> import urllib.request
>>> import urllib.parse
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.parse.urlencode(data)
>>> print(url_values) # The order may differ from below.
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib.request.urlopen(full_url)
Beachten Sie, dass die vollständige URL durch Hinzufügen eines ? zur URL, gefolgt von den codierten Werten, erstellt wird.
Header¶
Wir werden hier einen bestimmten HTTP-Header besprechen, um zu veranschaulichen, wie Header zu Ihrer HTTP-Anfrage hinzugefügt werden.
Einige Websites [1] mögen es nicht, von Programmen durchsucht zu werden, oder senden unterschiedliche Versionen an unterschiedliche Browser [2]. Standardmäßig identifiziert sich urllib als Python-urllib/x.y (wobei x und y die Haupt- und Nebenversionsnummern der Python-Version sind, z. B. Python-urllib/2.5), was die Website verwirren oder einfach nicht funktionieren kann. Die Art und Weise, wie sich ein Browser identifiziert, geschieht über den User-Agent-Header [3]. Wenn Sie ein Request-Objekt erstellen, können Sie ein Wörterbuch von Headern übergeben. Das folgende Beispiel stellt dieselbe Anfrage wie oben, identifiziert sich aber als eine Version von Internet Explorer [4].
import urllib.parse
import urllib.request
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
values = {'name': 'Michael Foord',
'location': 'Northampton',
'language': 'Python' }
headers = {'User-Agent': user_agent}
data = urllib.parse.urlencode(values)
data = data.encode('ascii')
req = urllib.request.Request(url, data, headers)
with urllib.request.urlopen(req) as response:
the_page = response.read()
Die Antwort hat auch zwei nützliche Methoden. Sehen Sie sich den Abschnitt über info und geturl an, nachdem wir uns angesehen haben, was passiert, wenn etwas schiefgeht.
Ausnahmen behandeln¶
urlopen löst URLError aus, wenn es eine Antwort nicht verarbeiten kann (obwohl, wie üblich bei Python-APIs, integrierte Ausnahmen wie ValueError, TypeError usw. ebenfalls ausgelöst werden können).
HTTPError ist die Unterklasse von URLError, die im spezifischen Fall von HTTP-URLs ausgelöst wird.
Die Ausnahmeklassen werden aus dem Modul urllib.error exportiert.
URLError¶
Oft wird URLError ausgelöst, weil keine Netzwerkverbindung besteht (keine Route zum angegebenen Server) oder der angegebene Server nicht existiert. In diesem Fall hat die ausgelöste Ausnahme ein 'reason'-Attribut, das ein Tupel mit einem Fehlercode und einer Textfehlermeldung enthält.
z. B.
>>> req = urllib.request.Request('http://www.pretend_server.org')
>>> try: urllib.request.urlopen(req)
... except urllib.error.URLError as e:
... print(e.reason)
...
(4, 'getaddrinfo failed')
HTTPError¶
Jede HTTP-Antwort vom Server enthält einen numerischen "Statuscode". Manchmal zeigt der Statuscode an, dass der Server die Anfrage nicht erfüllen kann. Die Standardhandler behandeln einige dieser Antworten für Sie (zum Beispiel, wenn die Antwort eine "Weiterleitung" ist, die den Client auffordert, das Dokument von einer anderen URL abzurufen, erledigt urllib das für Sie). Für die, die es nicht handhaben kann, löst urlopen eine HTTPError aus. Typische Fehler sind '404' (Seite nicht gefunden), '403' (Anfrage verboten) und '401' (Authentifizierung erforderlich).
Siehe Abschnitt 10 von RFC 2616 für eine Referenz zu allen HTTP-Fehlercodes.
Die ausgelöste HTTPError-Instanz hat ein ganzzahliges 'code'-Attribut, das dem vom Server gesendeten Fehler entspricht.
Fehlercodes¶
Da die Standardhandler Weiterleitungen (Codes im Bereich 300) behandeln und Codes im Bereich 100-299 Erfolg anzeigen, werden Sie normalerweise nur Fehlercodes im Bereich 400-599 sehen.
http.server.BaseHTTPRequestHandler.responses ist ein nützliches Wörterbuch von Antwortcodes, das alle von RFC 2616 verwendeten Antwortcodes anzeigt. Ein Auszug aus dem Wörterbuch ist unten dargestellt:
responses = {
...
<HTTPStatus.OK: 200>: ('OK', 'Request fulfilled, document follows'),
...
<HTTPStatus.FORBIDDEN: 403>: ('Forbidden',
'Request forbidden -- authorization will '
'not help'),
<HTTPStatus.NOT_FOUND: 404>: ('Not Found',
'Nothing matches the given URI'),
...
<HTTPStatus.IM_A_TEAPOT: 418>: ("I'm a Teapot",
'Server refuses to brew coffee because '
'it is a teapot'),
...
<HTTPStatus.SERVICE_UNAVAILABLE: 503>: ('Service Unavailable',
'The server cannot process the '
'request due to a high load'),
...
}
Wenn ein Fehler ausgelöst wird, antwortet der Server mit einem HTTP-Fehlercode und einer Fehlerseite. Sie können die HTTPError-Instanz als Antwort auf die zurückgegebene Seite verwenden. Das bedeutet, dass sie neben dem Code-Attribut auch die Methoden read, geturl und info hat, wie sie vom Modul urllib.response zurückgegeben werden.
>>> req = urllib.request.Request('https://pythonlang.de/fish.html')
>>> try:
... urllib.request.urlopen(req)
... except urllib.error.HTTPError as e:
... print(e.code)
... print(e.read())
...
404
b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n\n\n<html
...
<title>Page Not Found</title>\n
...
Zusammenfassung¶
Wenn Sie also auf HTTPError oder URLError vorbereitet sein möchten, gibt es zwei grundlegende Ansätze. Ich bevorzuge den zweiten Ansatz.
Nummer 1¶
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
req = Request(someurl)
try:
response = urlopen(req)
except HTTPError as e:
print('The server couldn\'t fulfill the request.')
print('Error code: ', e.code)
except URLError as e:
print('We failed to reach a server.')
print('Reason: ', e.reason)
else:
# everything is fine
Hinweis
Das except HTTPError muss zuerst kommen, sonst fängt except URLError auch eine HTTPError ab.
Nummer 2¶
from urllib.request import Request, urlopen
from urllib.error import URLError
req = Request(someurl)
try:
response = urlopen(req)
except URLError as e:
if hasattr(e, 'reason'):
print('We failed to reach a server.')
print('Reason: ', e.reason)
elif hasattr(e, 'code'):
print('The server couldn\'t fulfill the request.')
print('Error code: ', e.code)
else:
# everything is fine
info und geturl¶
Die von urlopen zurückgegebene Antwort (oder die HTTPError-Instanz) hat zwei nützliche Methoden: info() und geturl() und ist im Modul urllib.response definiert.
geturl – gibt die tatsächliche URL der abgerufenen Seite zurück. Dies ist nützlich, da
urlopen(oder das verwendete Opener-Objekt) möglicherweise einer Weiterleitung gefolgt ist. Die URL der abgerufenen Seite ist möglicherweise nicht dieselbe wie die angeforderte URL.info – gibt ein wörterbuchähnliches Objekt zurück, das die abgerufene Seite beschreibt, insbesondere die vom Server gesendeten Header. Es handelt sich derzeit um eine Instanz von
http.client.HTTPMessage.
Typische Header sind 'Content-length', 'Content-type' und so weiter. Siehe die Schnellreferenz für HTTP-Header für eine nützliche Auflistung von HTTP-Headern mit kurzen Erklärungen ihrer Bedeutung und Verwendung.
Opener und Handler¶
Wenn Sie eine URL abrufen, verwenden Sie einen Opener (eine Instanz des vielleicht verwirrend benannten urllib.request.OpenerDirector). Normalerweise haben wir den Standard-Opener verwendet – über urlopen – aber Sie können benutzerdefinierte Opener erstellen. Opener verwenden Handler. Die gesamte "schwerstarbeit" wird von den Handlern geleistet. Jeder Handler weiß, wie URLs für ein bestimmtes URL-Schema (http, ftp usw.) geöffnet werden oder wie ein Aspekt des URL-Öffnens gehandhabt wird, zum Beispiel HTTP-Weiterleitungen oder HTTP-Cookies.
Sie möchten Opener erstellen, wenn Sie URLs mit spezifischen installierten Handlern abrufen möchten, zum Beispiel um einen Opener zu erhalten, der Cookies behandelt, oder um einen Opener zu erhalten, der keine Weiterleitungen behandelt.
Um einen Opener zu erstellen, instanziieren Sie eine OpenerDirector und rufen Sie dann wiederholt .add_handler(some_handler_instance) auf.
Alternativ können Sie build_opener verwenden, eine Hilfsfunktion zum Erstellen von Opener-Objekten mit einem einzigen Funktionsaufruf. build_opener fügt standardmäßig mehrere Handler hinzu, bietet aber eine schnelle Möglichkeit, weitere hinzuzufügen und/oder die Standardhandler zu überschreiben.
Andere Arten von Handlern, die Sie möglicherweise wünschen, können Proxies, Authentifizierung und andere gängige, aber leicht spezialisierte Situationen behandeln.
install_opener kann verwendet werden, um ein opener-Objekt zum (globalen) Standard-Opener zu machen. Das bedeutet, dass Aufrufe von urlopen den von Ihnen installierten Opener verwenden.
Opener-Objekte haben eine open-Methode, die direkt aufgerufen werden kann, um URLs auf die gleiche Weise wie die Funktion urlopen abzurufen: es ist nicht notwendig, install_opener aufzurufen, außer als Bequemlichkeit.
Basis-Authentifizierung¶
Um die Erstellung und Installation eines Handlers zu veranschaulichen, verwenden wir den HTTPBasicAuthHandler. Für eine detailliertere Diskussion dieses Themas – einschließlich einer Erklärung, wie Basis-Authentifizierung funktioniert – siehe das Tutorial zur Basis-Authentifizierung.
Wenn Authentifizierung erforderlich ist, sendet der Server einen Header (zusätzlich zum 401-Fehlercode), der zur Authentifizierung auffordert. Dieser gibt das Authentifizierungsschema und einen 'Realm' an. Der Header sieht so aus: WWW-Authenticate: SCHEME realm="REALM".
z. B.
WWW-Authenticate: Basic realm="cPanel Users"
Der Client sollte dann die Anfrage mit dem entsprechenden Namen und Passwort für den Realm, der als Header in der Anfrage enthalten ist, erneut stellen. Dies ist die "Basis-Authentifizierung". Um diesen Prozess zu vereinfachen, können wir eine Instanz von HTTPBasicAuthHandler und einen Opener erstellen, der diesen Handler verwendet.
Der HTTPBasicAuthHandler verwendet ein Objekt namens Passwort-Manager, um die Zuordnung von URLs und Realms zu Passwörtern und Benutzernamen zu handhaben. Wenn Sie den Realm kennen (aus dem vom Server gesendeten Authentifizierungsheader), können Sie einen HTTPPasswordMgr verwenden. Häufig kümmert man sich nicht darum, was der Realm ist. In diesem Fall ist es praktisch, HTTPPasswordMgrWithDefaultRealm zu verwenden. Dies ermöglicht es Ihnen, einen Standardbenutzernamen und ein Standardpasswort für eine URL anzugeben. Dies wird im Falle des Fehlens einer alternativen Kombination für einen bestimmten Realm geliefert. Wir kennzeichnen dies, indem wir None als Realm-Argument an die Methode add_password übergeben.
Die oberste URL ist die erste URL, die eine Authentifizierung erfordert. URLs, die "tiefer" als die an .add_password() übergebene URL sind, werden ebenfalls übereinstimmen.
# create a password manager
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# Add the username and password.
# If we knew the realm, we could use it instead of None.
top_level_url = "http://example.com/foo/"
password_mgr.add_password(None, top_level_url, username, password)
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
# create "opener" (OpenerDirector instance)
opener = urllib.request.build_opener(handler)
# use the opener to fetch a URL
opener.open(a_url)
# Install the opener.
# Now all calls to urllib.request.urlopen use our opener.
urllib.request.install_opener(opener)
Hinweis
Im obigen Beispiel haben wir unseren HTTPBasicAuthHandler nur an build_opener übergeben. Standardmäßig haben Opener die Handler für normale Situationen – ProxyHandler (wenn eine Proxy-Einstellung wie die Umgebungsvariable http_proxy gesetzt ist), UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, DataHandler, HTTPErrorProcessor.
top_level_url ist tatsächlich entweder eine vollständige URL (einschließlich der 'http:'-Komponente und des Hostnamens und optional der Portnummer) z. B. "http://example.com/" oder eine "Autorität" (d. h. der Hostname, optional einschließlich der Portnummer) z. B. "example.com" oder "example.com:8080" (letzteres Beispiel enthält eine Portnummer). Die Autorität, falls vorhanden, darf NICHT die "userinfo"-Komponente enthalten – zum Beispiel ist "joe:password@example.com" nicht korrekt.
Proxies¶
urllib erkennt Ihre Proxy-Einstellungen automatisch und verwendet diese. Dies geschieht über den ProxyHandler, der Teil der normalen Handler-Kette ist, wenn eine Proxy-Einstellung erkannt wird. Normalerweise ist das gut, aber es gibt Fälle, in denen es nicht hilfreich sein kann [5]. Eine Möglichkeit, dies zu tun, ist die Einrichtung unseres eigenen ProxyHandler, ohne Proxies zu definieren. Dies geschieht mit ähnlichen Schritten wie die Einrichtung eines Handlers für die Basis-Authentifizierung.
>>> proxy_support = urllib.request.ProxyHandler({})
>>> opener = urllib.request.build_opener(proxy_support)
>>> urllib.request.install_opener(opener)
Hinweis
Derzeit unterstützt urllib.request nicht das Abrufen von https-Speicherorten über einen Proxy. Dies kann jedoch durch Erweiterung von urllib.request aktiviert werden, wie im Rezept [6] gezeigt.
Hinweis
HTTP_PROXY wird ignoriert, wenn eine Variable REQUEST_METHOD gesetzt ist; siehe die Dokumentation zu getproxies().
Sockets und Layer¶
Die Python-Unterstützung für das Abrufen von Ressourcen aus dem Web ist geschichtet. urllib verwendet die Bibliothek http.client, die wiederum die Socket-Bibliothek verwendet.
Seit Python 2.3 können Sie festlegen, wie lange ein Socket auf eine Antwort warten soll, bevor er abläuft. Dies kann in Anwendungen nützlich sein, die Webseiten abrufen müssen. Standardmäßig hat das Socket-Modul kein Timeout und kann hängen bleiben. Derzeit ist der Socket-Timeout nicht auf der Ebene von http.client oder urllib.request zugänglich. Sie können jedoch den Standard-Timeout global für alle Sockets einstellen mit:
import socket
import urllib.request
# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)
# this call to urllib.request.urlopen now uses the default timeout
# we have set in the socket module
req = urllib.request.Request('http://www.voidspace.org.uk')
response = urllib.request.urlopen(req)
Fußnoten¶
Dieses Dokument wurde von John Lee überprüft und überarbeitet.