Jak pobrać obraz za pomocą żądań

367

Próbuję pobrać i zapisać obraz z Internetu za pomocą requestsmodułu Pythona .

Oto (działający) kod, którego użyłem:

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

Oto nowy (niedziałający) kod wykorzystujący 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)

Czy możesz mi pomóc w zakresie tego, jaki atrybut odpowiedzi użyć requests?

shkschneider
źródło
15
aby korzystać z r.raw, musisz ustawić stream = True
clsung 23.04.13
Czy to odpowiada na twoje pytanie? Pobierz duży plik w pythonie z żądaniami
AMC

Odpowiedzi:

516

Możesz użyć response.rawobiektu pliku lub powtórzyć odpowiedź.

Aby użyć response.rawobiektu podobnego do pliku, domyślnie nie dekoduje skompresowanych odpowiedzi (za pomocą GZIP lub deflate). W każdym razie możesz zmusić go do dekompresji, ustawiając decode_contentatrybut na True( requestsustawia się Falsena kontrolę samego dekodowania). Następnie możesz użyć shutil.copyfileobj()Pythona do strumieniowego przesyłania danych do obiektu pliku:

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)        

Aby powtórzyć odpowiedź, użyj pętli; iteracja w ten sposób zapewnia dekompresję danych na tym etapie:

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)

Spowoduje to odczyt danych w 128-bajtowych porcjach; jeśli uważasz, że inny rozmiar porcji działa lepiej, użyj Response.iter_content()metody z niestandardowym rozmiarem porcji:

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)

Zauważ, że musisz otworzyć plik docelowy w trybie binarnym, aby upewnić się, że Python nie spróbuje tłumaczyć nowych linii. Ustawiliśmy stream=Truetak, aby requestsnajpierw nie pobierał całego obrazu do pamięci.

Martijn Pieters
źródło
2
Z pomocą twojej odpowiedzi mogłem znaleźć dane w pliku tekstowym, zastosowałem następujące kroki r2 = requests.post(r.url, data); print r2.content. Ale teraz też chcę wiedzieć filename. czy jest ich jakiś oczyszczony sposób? - obecnie znalazłem nazwę pliku w nagłówku - r2.headers['content-disposition'] to daje mi wynik jako: 'attachment; filename=DELS36532G290115.csi' parsuję ten ciąg dla nazwy pliku ... czy jest ich bardziej przejrzysty sposób?
Grijesh Chauhan
6
@GrijeshChauhan: tak, content-dispositionnagłówek jest dobrym sposobem na przejście tutaj; użyj, cgi.parse_header()aby go przeanalizować i uzyskać parametry; params = cgi.parse_header(r2.headers['content-disposition'])[1]potem params['filename'].
Martijn Pieters
1
Aby uzyskać domyślnego 128 bajtów kawałki, trzeba iteracyjne nad requests.Responsesama : for chunk in r: .... Wywołanie iter_content()bez testerachunk_size będzie powtarzane w 1 bajtowych porcjach .
dtk
@dtk: dzięki, zaktualizuję odpowiedź. Iteracja zmieniła się po opublikowaniu mojej odpowiedzi .
Martijn Pieters
1
@KumZ z dwóch powodów: response.oknigdy nie zostało udokumentowane i daje wartość true dla dowolnego statusu 1xx, 2xx lub 3xx, ale tylko 200 odpowiedzi ma treść odpowiedzi.
Martijn Pieters
232

Pobierz z żądania obiekt podobny do pliku i skopiuj go do pliku. Pozwoli to również uniknąć wczytywania całej pamięci do pamięci naraz.

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
źródło
14
Dziękuję bardzo za powrót i udzielenie odpowiedzi. Chociaż druga odpowiedź jest skuteczna, ta jest
szybsza
11
Warto zauważyć, że niewiele serwerów jest skonfigurowanych do GZIP, ponieważ obrazy mają już własną kompresję. Jest bezproduktywny, marnuje cykle procesora z niewielką korzyścią. Chociaż może to być problem z treścią tekstową, szczególnie z obrazami, tak nie jest.
phette23
3
czy istnieje jakiś sposób, aby uzyskać dostęp do oryginalnej nazwy pliku
mahes
@ phette23 Warto również zauważyć, że Google PageSpeed ​​raportuje i robi to domyślnie.
Wernight
8
Należy ustawić r.raw.decode_content = Truewcześniej, shutil.copyfileobj(response.raw, out_file)ponieważ by default, decode compressed responses (with GZIP or deflate), aby uzyskać obraz o zerowym pliku.
Simin Jie,
166

A może to szybkie rozwiązanie.

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
źródło
1
Co rozumiesz przez ! f = open("/Users/apple/Desktop/sample.jpg", 'wb')co masz na myśli mówiąc o tej ścieżce? Chcę pobrać obraz
uśmiech
3
Spowoduje to otwarcie deskryptora pliku w określonej ścieżce, do której można zapisać plik obrazu.
kiranbkrishna
@AndrewGlazkov Myślę, że byłoby bardziej Pythoniczne w użyciuif response.ok:
EndermanAPM
5
Odpowiedź.ok ma wartość Prawda dla dowolnego statusu 1xx, 2xx lub 3xx, ale tylko 200 odpowiedzi ma treść odpowiedzi, jak wspomniano w komentarzach powyżej @Martijn Pieters
Annndrey
75

Mam taką samą potrzebę pobierania zdjęć za pomocą żądań. Najpierw wypróbowałem odpowiedź Martijna Pietersa i działa ona dobrze. Ale kiedy stworzyłem profil dla tej prostej funkcji, odkryłem, że używa ona tak wielu wywołań funkcji w porównaniu do urllib i urllib2.

Następnie wypróbowałem sposób zalecany przez autora modułu żądań:

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))

To znacznie bardziej zmniejszyło liczbę wywołań funkcji, a tym samym przyspieszyło moją aplikację. Oto kod mojego profilera i wynik.

#!/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()')

Wynik testu testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

Wynik testu testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds
Zhenyi Zhang
źródło
12
Wynika to z faktu, że nie określono chunk_sizeparametru, który domyślnie ma wartość 1, więc iter_contentiteruje się w strumieniu wyników 1 bajt naraz. Zobacz dokumentację python-requests.org/en/latest/api/… .
CadentOrange
9
To również ładuje całą odpowiedź do pamięci, której możesz chcieć uniknąć. Tu też nie ma z czego korzystać PIL, with open(image_name, 'wb') as outfile: outfile.write(r.content)wystarczy.
Martijn Pieters
3
PILnie ma go również w standardowej bibliotece, co czyni go nieco mniej przenośnym.
jjj
2
@ZhenyiZhang iter_contentjest wolny, ponieważ twój chunk_sizejest za mały, jeśli zwiększysz go do 100k, będzie on znacznie szybszy.
Wang
To najlepsza odpowiedź. Nie zawsze najlepiej jest wczytywać plik do pamięci, ale określone przez OP „obrazy” oznaczają, że pliki zwykle mają mniej niż 4 MB, co ma trywialny wpływ na pamięć.
Chris Conlan
51

To może być łatwiejsze niż używanie requests. To jedyny raz, kiedy zasugeruję, aby nie używać requestsdo robienia rzeczy HTTP.

Dwie wkładki przy użyciu urllib:

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

Jest też ładny moduł o nazwie Python, wgetktóry jest dość łatwy w użyciu. Znaleziono tutaj .

To pokazuje prostotę projektu:

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

Cieszyć się.

Edycja: Możesz także dodać outparametr, aby określić ścieżkę.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)
Blairg23
źródło
Użyłem wgetbez żadnych problemów. Dziękujemy za stwierdzenie korzyści płynących z używaniaurllib3
h3xh4wk
1
Zauważ, że ta odpowiedź dotyczy Python 2. W przypadku Python 3 musisz to zrobić urllib.request.urlretrieve("http://example.com", "file.ext").
Husky
1
Dzięki @Husky. Zaktualizowano
Blairg23
28

Poniższy fragment kodu pobiera plik.

Plik jest zapisywany z nazwą pliku podaną w określonym adresie URL.

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
źródło
16

Istnieją 2 główne sposoby:

  1. Za pomocą .content(najprostszy / oficjalny) (patrz odpowiedź Zhenyi Zhanga ):

    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. Używając .raw(patrz odpowiedź Martijna Pietersa ):

    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.

Oba czasy nie wykazują zauważalnej różnicy.

Wernight
źródło
2
Próbowałem wielu odpowiedzi, a twoja 1.odpowiedź (używając io.BytesIOi Image) była pierwszą, która działała dla mnie w Pythonie 3.6. Nie zapomnij from PIL import Image(i pip install Pillow).
Colllin
Czym różnią się .content i .raw?
foxiris
13

Tak proste, jak importowanie obrazu i wniosków

from PIL import Image
import requests

img = Image.open(requests.get(url, stream = True).raw)
img.save('img1.jpg')
Riccardo D.
źródło
4

Oto bardziej przyjazna dla użytkownika odpowiedź, która nadal korzysta z przesyłania strumieniowego.

Wystarczy zdefiniować te funkcje i wywołać getImage(). Będzie używał tej samej nazwy pliku co adres URL i zapisuje domyślnie w bieżącym katalogu, ale oba można zmienić.

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')

W requestwnętrzności getImage()są oparte na odpowiedź tutaj i wnętrzności getImageFast()są oparte na odpowiedź powyżej .

Chris Redford
źródło
3

Zamierzam opublikować odpowiedź, ponieważ nie mam wystarczającej liczby przedstawicieli do skomentowania, ale dzięki wget opublikowanemu przez Blairg23 możesz również podać parametr out dla ścieżki.

 wget.download(url, out=path)
justincc
źródło
2

Jest to pierwsza odpowiedź, która pojawia się w przypadku wyszukiwań Google dotyczących sposobu pobierania pliku binarnego z żądaniami. Jeśli chcesz pobrać dowolny plik z żądaniami, możesz użyć:

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
źródło
1
Miły! Ma nawet domniemany .close(). To chyba najlepsza odpowiedź na 2019 rok.
Daniel W.
2

Tak to zrobiłem

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
źródło
-1

Możesz zrobić coś takiego:

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
źródło