So laden Sie Bilder mithilfe von Anfragen herunter

368

Ich versuche, ein Bild mit dem Python- requestsModul aus dem Internet herunterzuladen und zu speichern .

Hier ist der (Arbeits-) Code, den ich verwendet habe:

img = urllib2.urlopen(settings.STATICMAP_URL.format(**data))
with open(path, 'w') as f:
    f.write(img.read())

Hier ist der neue (nicht funktionierende) Code mit requests:

r = requests.get(settings.STATICMAP_URL.format(**data))
if r.status_code == 200:
    img = r.raw.read()
    with open(path, 'w') as f:
        f.write(img)

Können Sie mir helfen, welches Attribut aus der Antwort verwendet werden soll requests?

shkschneider
quelle
15
Um r.raw zu verwenden, müssen Sie stream = True setzen
clsung

Antworten:

516

Sie können entweder die Verwendung response.rawDateiobjekt oder Iterierte über die Antwort.

Wenn Sie das response.rawdateiähnliche Objekt verwenden, werden komprimierte Antworten standardmäßig nicht dekodiert (mit GZIP oder Deflate). Sie können die Dekomprimierung ohnehin für Sie erzwingen, indem Sie das decode_contentAttribut auf setzen True( requestssetzt es auf False, um die Dekodierung selbst zu steuern). Anschließend können Sie shutil.copyfileobj()Python die Daten in ein Dateiobjekt streamen lassen:

import requests
import shutil

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        r.raw.decode_content = True
        shutil.copyfileobj(r.raw, f)        

Verwenden Sie eine Schleife, um die Antwort zu durchlaufen. Wenn Sie so iterieren, wird sichergestellt, dass die Daten in dieser Phase dekomprimiert werden:

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        for chunk in r:
            f.write(chunk)

Dadurch werden die Daten in 128-Byte-Blöcken gelesen. Wenn Sie der Meinung sind, dass eine andere Blockgröße besser funktioniert, verwenden Sie die Response.iter_content()Methode mit einer benutzerdefinierten Blockgröße:

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        for chunk in r.iter_content(1024):
            f.write(chunk)

Beachten Sie, dass Sie die Zieldatei im Binärmodus öffnen müssen, um sicherzustellen, dass Python nicht versucht, Zeilenumbrüche für Sie zu übersetzen. Wir haben auch festgelegt stream=True, dass requestsnicht zuerst das gesamte Bild in den Speicher heruntergeladen wird.

Martijn Pieters
quelle
2
Mit Hilfe Ihrer Antwort konnte ich Daten in einer Textdatei finden r2 = requests.post(r.url, data); print r2.content. Die Schritte, die ich verwendet habe, sind : Aber jetzt will ich es auch wissen filename. Ist ihr Weg gereinigt? - Zur Zeit habe ich den Dateinamen im Header gefunden - r2.headers['content-disposition'] das gibt mir folgende Ausgabe: 'attachment; filename=DELS36532G290115.csi' Ich analysiere diese Zeichenfolge nach Dateinamen ... ist ihre Art sauberer?
Grijesh Chauhan
6
@GrijeshChauhan: Ja, der content-dispositionHeader ist der Weg hierher; Verwenden Sie cgi.parse_header()diese Option, um sie zu analysieren und die Parameter abzurufen. params = cgi.parse_header(r2.headers['content-disposition'])[1]dann params['filename'].
Martijn Pieters
1
Um die Standard-128-Byte-Chunks zu erhalten, müssen Sie über sich requests.Responseselbst iterieren : for chunk in r: .... Anrufe iter_content()ohne a chunk_sizewerden in 1-Byte-Blöcken wiederholt .
dtk
@dtk: danke, ich werde die Antwort aktualisieren. Die Iteration hat sich geändert, nachdem ich meine Antwort gepostet habe .
Martijn Pieters
1
@KumZ zwei Gründe: Wurde response.oknie dokumentiert und erzeugt für jeden 1xx-, 2xx- oder 3xx-Status true, aber nur eine 200-Antwort hat einen Antworttext.
Martijn Pieters
232

Holen Sie sich ein dateiähnliches Objekt aus der Anforderung und kopieren Sie es in eine Datei. Dadurch wird auch vermieden, dass das Ganze sofort in den Speicher eingelesen wird.

import shutil

import requests

url = 'http://example.com/img.png'
response = requests.get(url, stream=True)
with open('img.png', 'wb') as out_file:
    shutil.copyfileobj(response.raw, out_file)
del response
Oleh Prypin
quelle
14
Vielen Dank, dass Sie zurückgekommen sind und darauf geantwortet haben. Obwohl die andere Antwort funktioniert, ist diese sprunghaft einfacher
dkroy
11
Es ist erwähnenswert, dass nur wenige Server ihre Bilder auf GZIP einstellen, da Bilder bereits eine eigene Komprimierung haben. Es ist kontraproduktiv und verschwendet CPU-Zyklen mit geringem Nutzen. Dies kann zwar ein Problem mit Textinhalten sein, insbesondere mit Bildern.
Phette23
3
Gibt
@ phette23 Es ist auch erwähnenswert, dass Google PageSpeed ​​dies meldet und dies standardmäßig tut.
Nacht
8
Sollte r.raw.decode_content = Truevorher eingestellt werden, shutil.copyfileobj(response.raw, out_file)weil by default, decode compressed responses (with GZIP or deflate), so erhalten Sie ein Zero-File-Image.
Simin Jie
166

Wie wäre es damit, eine schnelle Lösung.

import requests

url = "http://craphound.com/images/1006884_2adf8fc7.jpg"
response = requests.get(url)
if response.status_code == 200:
    with open("/Users/apple/Desktop/sample.jpg", 'wb') as f:
        f.write(response.content)
kiranbkrishna
quelle
1
Was meinst du mit ! f = open("/Users/apple/Desktop/sample.jpg", 'wb')Was meinst du mit diesem Weg? Ich möchte Bild herunterladen
Lächeln
3
Dadurch wird ein Dateideskriptor in dem angegebenen Pfad geöffnet, in den die Bilddatei geschrieben werden kann.
Kiranbkrishna
@ AndrewGlazkov Ich denke, es wäre mehr Pythonic zu verwendenif response.ok:
EndermanAPM
5
response.ok ist wahr für jeden 1xx-, 2xx- oder 3xx-Status, aber nur eine 200-Antwort hat einen Antworttext wie @Martijn Pieters, der in den obigen Kommentaren erwähnt wird
annndrey
75

Ich habe das gleiche Bedürfnis, Bilder mithilfe von Anfragen herunterzuladen. Ich habe zuerst die Antwort von Martijn Pieters versucht, und es funktioniert gut. Aber als ich ein Profil für diese einfache Funktion erstellt habe, habe ich festgestellt, dass sie im Vergleich zu urllib und urllib2 so viele Funktionsaufrufe verwendet.

Ich habe dann den vom Autor des Anforderungsmoduls empfohlenen Weg ausprobiert :

import requests
from PIL import Image
# python2.x, use this instead  
# from StringIO import StringIO
# for python3.x,
from io import StringIO

r = requests.get('https://example.com/image.jpg')
i = Image.open(StringIO(r.content))

Dadurch wurde die Anzahl der Funktionsaufrufe erheblich reduziert und meine Anwendung beschleunigt. Hier ist der Code meines Profilers und das Ergebnis.

#!/usr/bin/python
import requests
from StringIO import StringIO
from PIL import Image
import profile

def testRequest():
    image_name = 'test1.jpg'
    url = 'http://example.com/image.jpg'

    r = requests.get(url, stream=True)
    with open(image_name, 'wb') as f:
        for chunk in r.iter_content():
            f.write(chunk)

def testRequest2():
    image_name = 'test2.jpg'
    url = 'http://example.com/image.jpg'

    r = requests.get(url)

    i = Image.open(StringIO(r.content))
    i.save(image_name)

if __name__ == '__main__':
    profile.run('testUrllib()')
    profile.run('testUrllib2()')
    profile.run('testRequest()')

Das Ergebnis für testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

Und das Ergebnis für testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds
Zhenyi Zhang
quelle
13
Dies liegt daran, dass Sie nicht den chunk_sizeParameter angegeben haben , der standardmäßig 1 ist, sondern jeweils iter_content1 Byte über den Ergebnisstrom iteriert. Siehe die Dokumentation python-requests.org/en/latest/api/… .
CadentOrange
10
Dadurch wird auch die gesamte Antwort in den Speicher geladen, was Sie möglicherweise vermeiden möchten. Auch PILhier gibt es keine Verwendung , with open(image_name, 'wb') as outfile: outfile.write(r.content)es reicht einfach aus.
Martijn Pieters
3
PIList auch nicht in der Standardbibliothek, was dies etwas weniger portabel macht.
JJJ
2
@ZhenyiZhang iter_contentist langsam, weil Ihr chunk_sizezu klein ist. Wenn Sie es auf 100.000 erhöhen, wird es viel schneller.
Wang
Dies ist die beste Antwort. Es ist nicht immer am besten, die Datei in den Speicher einzulesen, aber OP-spezifizierte "Bilder" bedeuten, dass die Dateien normalerweise weniger als 4 MB groß sind, was sich nur geringfügig auf den Speicher auswirkt.
Chris Conlan
51

Dies ist möglicherweise einfacher als die Verwendung requests. Dies ist das einzige Mal, dass ich vorschlagen werde, es nicht zu verwendenrequests HTTP zu verwenden.

Zwei Liner mit urllib:

>>> import urllib
>>> urllib.request.urlretrieve("http://www.example.com/songs/mp3.mp3", "mp3.mp3")

Es gibt auch ein schönes Python-Modul namens wget, das ziemlich einfach zu bedienen ist. gefunden hier .

Dies zeigt die Einfachheit des Designs:

>>> import wget
>>> url = 'http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3'
>>> filename = wget.download(url)
100% [................................................] 3841532 / 3841532>
>> filename
'razorback.mp3'

Genießen.

Bearbeiten: Sie können auch einen outParameter hinzufügen , um einen Pfad anzugeben.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)
Blairg23
quelle
Ich habe wgetohne Probleme verwendet. Vielen Dank, dass Sie die Vorteile der Verwendung vonurllib3
h3xh4wk
1
Beachten Sie, dass diese Antwort für Python 2 gilt. Für Python 3 müssen Sie dies tun urllib.request.urlretrieve("http://example.com", "file.ext").
Husky
1
Danke @Husky. Aktualisiert.
Blairg23
28

Das folgende Code-Snippet lädt eine Datei herunter.

Die Datei wird mit ihrem Dateinamen wie in der angegebenen URL gespeichert.

import requests

url = "http://example.com/image.jpg"
filename = url.split("/")[-1]
r = requests.get(url, timeout=0.5)

if r.status_code == 200:
    with open(filename, 'wb') as f:
        f.write(r.content)
Katja Süss
quelle
16

Es gibt zwei Hauptwege:

  1. Verwenden .content(am einfachsten / offiziellsten) (siehe Zhenyi Zhangs Antwort ):

    import io  # Note: io.BytesIO is StringIO.StringIO on Python2.
    import requests
    
    r = requests.get('http://lorempixel.com/400/200')
    r.raise_for_status()
    with io.BytesIO(r.content) as f:
        with Image.open(f) as img:
            img.show()
  2. Verwenden .raw(siehe Martijn Pieters Antwort ):

    import requests
    
    r = requests.get('http://lorempixel.com/400/200', stream=True)
    r.raise_for_status()
    r.raw.decode_content = True  # Required to decompress gzip/deflate compressed responses.
    with PIL.Image.open(r.raw) as img:
        img.show()
    r.close()  # Safety when stream=True ensure the connection is released.

Das Timing zeigt keinen merklichen Unterschied.

Wernight
quelle
2
Ich habe eine Reihe von Antworten ausprobiert, und Ihre 1.Antwort (mit io.BytesIOund Image) war die erste, die für mich unter Python 3.6 funktioniert hat. Vergiss nicht from PIL import Image(und pip install Pillow).
Colllin
Was ist anders zwischen .content und .raw?
Foxiris
13

So einfach wie das Importieren von Bildern und Anfragen

from PIL import Image
import requests

img = Image.open(requests.get(url, stream = True).raw)
img.save('img1.jpg')
Riccardo D.
quelle
4

Hier ist eine benutzerfreundlichere Antwort, die weiterhin Streaming verwendet.

Definieren Sie einfach diese Funktionen und rufen Sie auf getImage(). Es wird den gleichen Dateinamen wie die URL verwenden und standardmäßig in das aktuelle Verzeichnis schreiben, aber beide können geändert werden.

import requests
from StringIO import StringIO
from PIL import Image

def createFilename(url, name, folder):
    dotSplit = url.split('.')
    if name == None:
        # use the same as the url
        slashSplit = dotSplit[-2].split('/')
        name = slashSplit[-1]
    ext = dotSplit[-1]
    file = '{}{}.{}'.format(folder, name, ext)
    return file

def getImage(url, name=None, folder='./'):
    file = createFilename(url, name, folder)
    with open(file, 'wb') as f:
        r = requests.get(url, stream=True)
        for block in r.iter_content(1024):
            if not block:
                break
            f.write(block)

def getImageFast(url, name=None, folder='./'):
    file = createFilename(url, name, folder)
    r = requests.get(url)
    i = Image.open(StringIO(r.content))
    i.save(file)

if __name__ == '__main__':
    # Uses Less Memory
    getImage('http://www.example.com/image.jpg')
    # Faster
    getImageFast('http://www.example.com/image.jpg')

Die requestEingeweide von getImage()basieren auf der Antwort hier und die Eingeweide von getImageFast()basieren auf der obigen Antwort .

Chris Redford
quelle
3

Ich werde eine Antwort posten, da ich nicht genug Repräsentanten habe, um einen Kommentar abzugeben, aber mit wget, wie von Blairg23 gepostet, können Sie auch einen out-Parameter für den Pfad angeben.

 wget.download(url, out=path)
justincc
quelle
2

Dies ist die erste Antwort, die bei Google-Suchanfragen zum Herunterladen einer Binärdatei mit Anforderungen angezeigt wird. Falls Sie eine beliebige Datei mit Anforderungen herunterladen müssen, können Sie Folgendes verwenden:

import requests
url = 'https://s3.amazonaws.com/lab-data-collections/GoogleNews-vectors-negative300.bin.gz'
open('GoogleNews-vectors-negative300.bin.gz', 'wb').write(requests.get(url, allow_redirects=True).content)
duhaime
quelle
1
Nett! Es hat sogar eine implizite .close(). Dies ist die beste Antwort ab 2019, denke ich.
Daniel W.
2

So habe ich es gemacht

import requests
from PIL import Image
from io import BytesIO

url = 'your_url'
files = {'file': ("C:/Users/shadow/Downloads/black.jpeg", open('C:/Users/shadow/Downloads/black.jpeg', 'rb'),'image/jpg')}
response = requests.post(url, files=files)

img = Image.open(BytesIO(response.content))
img.show()
Harshit Singhai
quelle
-1

Sie können so etwas tun:

import requests
import random

url = "https://images.pexels.com/photos/1308881/pexels-photo-1308881.jpeg? auto=compress&cs=tinysrgb&dpr=1&w=500"
name=random.randrange(1,1000)
filename=str(name)+".jpg"
response = requests.get(url)
if response.status_code.ok:
   with open(filename,'w') as f:
    f.write(response.content)
Jyotiprakash Das
quelle