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?


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.


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

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)

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

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)

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)

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.


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

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 .


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)

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)

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

-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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.