Seite 1 von 1

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

Verfasst: Freitag 27. Dezember 2013, 22:57
von p90
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

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 00:19
von EyDu
Weil es ``urlretrieve`` heißt ;-) Falls du Python 2 verwenden solltest, dann ist es es noch ``urllib.urlretrieve`` statt ``urllib.request.urlretrieve``.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 00:31
von Hyperion
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.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 00:35
von p90
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.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 00:49
von BlackJack
@p90: Schau Dir mal das `requests`-Paket an.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 12:23
von p90
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

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 13:07
von EyDu
Es ist nicht urllib.request gemeint, sondern das requests-Modul. Wirf dazu mal die Suchmaschine deiner Wahl an.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 13:36
von Hyperion
Das hatte ich sogar verlinkt ;-)

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 15:22
von p90
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

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 16:21
von Hyperion
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.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 16:44
von Sirius3
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)

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 16:48
von 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.

Re: urllib.request.build_opener mit urllib.request.urlretrie

Verfasst: Samstag 28. Dezember 2013, 17:03
von p90
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".