urllib.request.build_opener mit urllib.request.urlretrieve?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

hab hier gerade ein sehr komisches Problem.
Baue einen Crawler für eine Seite. Muss mich dazu Authentifizieren und bekomme dann Zugriff auf mein Profil.
Dazu habe ich mir mit urllib.request.build_opener einen Opener gebaut der dies tut.
Nun muss ich mit diesem Opener große Dateien runterladen (>1GB). Der direkte weg mit .read() fällt dadurch weg.
Dachte nun das urllib.request.urlretrive ja genau das macht was ich haben will (url -> file). Anscheinend hat der opener diese methode aber nicht (warum auch immer).
Kann mir jemand sagen, wie ich das am besten mache? Muss ich die übliche .read(CHUNK) Route gehen oder gibt es da was eleganteres?

euch einen schönen Abend!
p91
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Weil es ``urlretrieve`` heißt ;-) Falls du Python 2 verwenden solltest, dann ist es es noch ``urllib.urlretrieve`` statt ``urllib.request.urlretrieve``.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Und wie immer die Empfehlung: Schau Dir mal das requests-Modul an! Das hat eine viel angenehmere API als die "ollen" Module der Standard-Lib bezüglich HTTP.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

stimmt, aber es bleibt dabei:
OpenerDirector hat kein urlretrieve, nur ein open() von dem man dann read() oder read(CHUNK) machen kann.
Und das "normale" urlretrieve finde ich zwar aber das geht nicht weil dann nicht die Authentifizierte Verbindung verwendet wird
(wenn ich es richtig verstehe)

Verwende übrigens Python 3.
BlackJack

@p90: Schau Dir mal das `requests`-Paket an.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

urllib.request habe ich mir schon angeguckt aber ich befürchte, dass kann ich nicht verwenden.
Das Problem ist, dass ich gleichzeitig Verbindungen zu verschiedenen Seiten habe und für jede Seite meine Auth halten muss.
Ergo kann ich keinen Default-Handler installieren und dann einfach urllib.reqeust.urlopen() oder urllib.reqeust.urlretrieve() verwenden
sondern habe für jede Seite einen eigenen Handler. Nur stellt dieser Handler AFAIK nur open() bereit aber kein äquivalent zu urlretrieve.

Hab mittlerweile eine Lösung mit shutils aber versuche immer noch Download resume Funktionalität einzubauen und schön ist es auch nicht wirklich.

Hier einfach mal etwas Code, vlt. mache ich ja einen fundamentalen Fehler und kapiere deshalb nicht, was ich falsch mache:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from urllib.parse import urlencode
from urllib.request import build_opener, HTTPCookieProcessor
from http.cookiejar import CookieJar
from time import sleep
from shutil import copyfileobj
import os
import re

class CookieCon_ret(object):
	def __init__(self, encoding='utf-8', userAgent=None):
		self._encoding = encoding
		self._cookiejar = CookieJar()
		self._opener = build_opener(HTTPCookieProcessor(self._cookiejar))
		if not userAgent is None:
			self._opener.addheaders = [('User-agent', userAgent)]

	def urlretrieve(self, url, folder, optname = None):
		"""Used for files"""
		with self._opener.open(url) as sock:
			foo = sock.info()['Content-Disposition']
			if optname is None:
				#yeah, this is not correct but regex is a fucking pain in the ass right now
				filename = foo[22:-1]
			else:
				filename = optname
			filepath = os.path.join(folder, filename)
			if not os.path.exists(filepath):
				with open(filepath, "wb") as file:
					copyfileobj(sock, file)


euch noch einen schönen Tag,
p91
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Es ist nicht urllib.request gemeint, sondern das requests-Modul. Wirf dazu mal die Suchmaschine deiner Wahl an.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Das hatte ich sogar verlinkt ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Oh, Entschuldigung, das hatte ich gar nicht gesehen :oops:
Leider ist requests keine Alternative da das ganze nur mit Python 3.3 Standard Libs gemacht werden sollte (macht den Support einfacher).
Habs aber glaube ich, jetzt gelöst (zumindest hat es bei meinen tests jetzt immer funktioniert)

Code: Alles auswählen

def urlretrieve(self, url, folder, optname = None):
	"""Used for files"""
	with self._opener.open(url) as sock:
		foo = sock.info()['Content-Disposition']
		if optname is None:
			#yeah, this is not correct but regex is a fucking pain in the ass right now
			filename = foo[22:-1]
		else:
			filename = optname
		filepath = os.path.join(folder, filename)
		if not os.path.exists(filepath):
			file_options = "wb"
			currentSize = 0
		else:
			file_option = "ab"
			currentSize = os.path.getsize(filepath)
		maxSize = int(sock.headers['Content-Length'])
	if currentSize < maxSize:
		if currentSize:
			self._opener.add_header("Range","bytes=%s-" % (currentSize))
		with self._opener.open(url) as sock, open(filepath, file_options) as file:
			copyfileobj(sock, file)
		if currentSize:
			self._opener.remove_header("Range","bytes=%s-" % (currentSize))
[EDIT]
Da war ein tab zu viel drin da es Teil einer Klasse ist
Zuletzt geändert von p90 am Samstag 28. Dezember 2013, 16:51, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Es mag sein, dass es an den komischen Einrückungen liegt, die Du verwendest (nimm doch bitte *vier* Spaces - wie es PEP8 vorschreibt), aber Deine Methode ist für meinen Geschmack etwas lang und macht zu viel.

Die Ermittlung des Dateinamens und die Bestimmung der Dateiattribute könnte man locker auslagern.

Dein Docstring ist imho wenig aussagekräftig und passt so gar nicht zum Funktionsnamen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

problematischer ist ja, dass immer zwei mal ein "GET"-Request auf die URL gemacht wird. Wie man aus dem ersten ein "HEAD" macht, müßte man irgendwo nachlesen. Aber mit ein bißchen aufräumen, sieht die Methode gleich viel schlanker aus:

Code: Alles auswählen

    def get_filename_from_url(self, url):
        with self._opener.open(url) as sock:
            return sock.info()['Content-Disposition'].split(':',1)[-1].strip()

    def retrieve_url(self, url, folder, optname=None):
        """Used for urls"""
        filename = optname if optname else self.get_filename_from_url(url)
        filepath = os.path.join(folder, filename)
        if os.path.exists(filepath):
            self._opener.add_header("Range","bytes=%d-" % os.path.getsize(filepath))
        with self._opener.open(url) as sock, open(filepath, "ab") as file:
            copyfileobj(sock, file)
        self._opener.headers.pop('Range', None)
BlackJack

@p90: Das mit `requests` würde ich mir auch noch mal überlegen. Das hat keine weiteren Abhängigkeiten und macht diesen ganzen `urllib`-Murks wesentlich benutzbarer.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hyperion hat geschrieben:Es mag sein, dass es an den komischen Einrückungen liegt, die Du verwendest (nimm doch bitte *vier* Spaces - wie es PEP8 vorschreibt), aber Deine Methode ist für meinen Geschmack etwas lang und macht zu viel.

Die Ermittlung des Dateinamens und die Bestimmung der Dateiattribute könnte man locker auslagern.

Dein Docstring ist imho wenig aussagekräftig und passt so gar nicht zum Funktionsnamen.
ja, die Funktion muss ich noch etwas verfeinern, bin da gerade auch noch dran.
Zu PEP8: ich verwende nur Tabs, das sind beim Editieren dann auch hier Tabs. Warum nun aus einem Tab 8 Leerzeichen in der Forensoftware werden weiß ich leider nicht.
Sirius3 hat geschrieben:problematischer ist ja, dass immer zwei mal ein "GET"-Request auf die URL gemacht wird. Wie man aus dem ersten ein "HEAD" macht, müßte man irgendwo nachlesen. Aber mit ein bißchen aufräumen, sieht die Methode gleich viel schlanker aus:

Code: Alles auswählen

    def get_filename_from_url(self, url):
        with self._opener.open(url) as sock:
            return sock.info()['Content-Disposition'].split(':',1)[-1].strip()

    def retrieve_url(self, url, folder, optname=None):
        """Used for urls"""
        filename = optname if optname else self.get_filename_from_url(url)
        filepath = os.path.join(folder, filename)
        if os.path.exists(filepath):
            self._opener.add_header("Range","bytes=%d-" % os.path.getsize(filepath))
        with self._opener.open(url) as sock, open(filepath, "ab") as file:
            copyfileobj(sock, file)
        self._opener.headers.pop('Range', None)
stimmt, das muss ich noch ändern, zweimal GET ist wirklich nicht so schön. Danke für die Anmerkungen!
BlackJack hat geschrieben:@p90: Das mit `requests` würde ich mir auch noch mal überlegen. Das hat keine weiteren Abhängigkeiten und macht diesen ganzen `urllib`-Murks wesentlich benutzbarer.
Jo, von dem was ich jetzt über requests gelesen habe sieht es schon sehr schön aus. Wenn ich das jetzt nur für mich schreiben würde, würde ich es auch benutzen. Da ich das ganze aber auch an meine Mitstudenten weiter geben möchte (bzw. auch an jeden anderen den es interessiert) ist halt ein "Installiert dir Python3.3" einfacher als ein "Installiere dir Python3, dann installiert dir easy_setup oder pip, dann installiere dir noch requests".
Antworten