Cách tải hình ảnh bằng yêu cầu


368

Tôi đang cố tải xuống và lưu hình ảnh từ web bằng requestsmô-đun của python .

Đây là mã (làm việc) tôi đã sử dụng:

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

Đây là mã mới (không hoạt động) bằng cách sử dụng 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)

Bạn có thể giúp tôi về thuộc tính từ phản ứng để sử dụng từ requests?


15
để sử dụng r.raw, bạn cần đặt stream = True
clsung

Điều này có trả lời câu hỏi của bạn không? Tải xuống tệp lớn trong python với các yêu cầu
AMC

Câu trả lời:


516

Bạn có thể sử dụng response.rawđối tượng tệp hoặc lặp lại qua phản hồi.

Theo response.rawmặc định, để sử dụng đối tượng giống như tệp, sẽ giải mã các phản hồi được nén (với GZIP hoặc khử rung). Dù sao, bạn có thể buộc nó giải nén cho bạn bằng cách đặt decode_contentthuộc tính thành True( requestsđặt nó Falsethành điều khiển giải mã chính nó). Sau đó, bạn có thể sử dụng shutil.copyfileobj()để Python truyền dữ liệu đến một đối tượng tệp:

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)        

Để lặp lại qua phản hồi, sử dụng một vòng lặp; Lặp đi lặp lại như thế này đảm bảo rằng dữ liệu được giải nén theo giai đoạn này:

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)

Điều này sẽ đọc dữ liệu trong khối 128 byte; nếu bạn cảm thấy kích thước khối khác hoạt động tốt hơn, hãy sử dụng Response.iter_content()phương pháp với kích thước khối tùy chỉnh:

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)

Lưu ý rằng bạn cần mở tệp đích ở chế độ nhị phân để đảm bảo python không thử và dịch các dòng mới cho bạn. Chúng tôi cũng thiết lập stream=Trueđể requestskhông tải toàn bộ hình ảnh vào bộ nhớ trước.


2
Với sự giúp đỡ của câu trả lời của bạn, tôi có thể tìm thấy dữ liệu trong tệp văn bản, các bước tôi đã sử dụng là r2 = requests.post(r.url, data); print r2.content. Nhưng bây giờ tôi cũng muốn biết filename. là cách làm sạch của họ? - hiện tại tôi đã tìm thấy tên tệp trong tiêu đề - r2.headers['content-disposition'] điều đó mang lại cho tôi đầu ra là: 'attachment; filename=DELS36532G290115.csi' Tôi đang phân tích chuỗi này cho tên tệp ... có cách nào sạch hơn không?
Grijesh Chauhan

6
@GrijeshChauhan: vâng, content-dispositiontiêu đề là cách để đi đến đây; sử dụng cgi.parse_header()để phân tích nó và nhận các tham số; params = cgi.parse_header(r2.headers['content-disposition'])[1]sau đó params['filename'].
Martijn Pieters

1
Để có được khối 128 byte mặc định, bạn cần lặp lại requests.Responsechính nó : for chunk in r: .... Gọi iter_content()mà không có chunk_sizeý chí sẽ lặp lại trong các đoạn 1 byte .
dtk

@dtk: cảm ơn, tôi sẽ cập nhật câu trả lời. Lặp lại thay đổi sau khi tôi đăng câu trả lời của tôi .
Martijn Pieters

1
@KumZ hai lý do: response.okkhông bao giờ được ghi lại và nó tạo ra đúng cho bất kỳ trạng thái 1xx, 2xx hoặc 3xx nào, nhưng chỉ có 200 phản hồi có nội dung phản hồi.
Martijn Pieters

232

Lấy một đối tượng giống như tệp từ yêu cầu và sao chép nó vào một tệp. Điều này cũng sẽ tránh đọc toàn bộ vào bộ nhớ cùng một lúc.

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
Cảm ơn bạn rất nhiều vì đã trở lại và trả lời này. Mặc dù câu trả lời khác là có hiệu quả, nhưng câu trả lời này là bước nhảy vọt đơn giản hơn
dkroy

11
Điều đáng chú ý là có rất ít máy chủ được đặt thành GZIP hình ảnh của họ vì hình ảnh đã có nén riêng. Nó phản tác dụng, lãng phí chu kỳ CPU với rất ít lợi ích. Vì vậy, trong khi điều này có thể là một vấn đề với nội dung văn bản, cụ thể là với hình ảnh thì không.
phette23

3
Có cách nào chúng ta có thể truy cập tên tệp gốc
mahes

@ phette23 Cũng đáng lưu ý rằng Google PageSpeed ​​báo cáo và thực hiện điều đó theo mặc định.
Wernight

8
Nên đặt r.raw.decode_content = Truetrước shutil.copyfileobj(response.raw, out_file)by default, decode compressed responses (with GZIP or deflate), vì vậy bạn sẽ có được hình ảnh không có tệp.
Simin Jie

166

Làm thế nào về điều này, một giải pháp nhanh chóng.

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
bạn có ý gì với ! f = open("/Users/apple/Desktop/sample.jpg", 'wb')bạn có ý nghĩa gì với con đường này!? tôi muốn tải hình ảnh
cười

3
Điều đó mở ra một mô tả tập tin trong đường dẫn được chỉ định mà tập tin hình ảnh có thể được viết.
kiranbkrishna

@AndrewGlazkov Tôi nghĩ rằng nó sẽ được sử dụng nhiều Pythonic hơnif response.ok:
EndermanAPM

5
answer.ok đúng với mọi trạng thái 1xx, 2xx hoặc 3xx, nhưng chỉ có 200 phản hồi có nội dung phản hồi như @Martijn Pieters đã đề cập trong các nhận xét ở trên
annndrey

75

Tôi có cùng nhu cầu tải hình ảnh bằng các yêu cầu. Lần đầu tiên tôi đã thử câu trả lời của Martijn Pieters, và nó hoạt động tốt. Nhưng khi tôi thực hiện một hồ sơ về chức năng đơn giản này, tôi thấy rằng nó sử dụng rất nhiều lệnh gọi hàm so với urllib và urllib2.

Sau đó tôi đã thử cách được đề xuất bởi tác giả của mô-đun yêu cầu:

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

Điều này làm giảm nhiều hơn các cuộc gọi chức năng, do đó tăng tốc ứng dụng của tôi. Đây là mã hồ sơ của tôi và kết quả.

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

Kết quả cho testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

Và kết quả cho testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds

13
Điều này là do bạn chưa chỉ định chunk_sizetham số mặc định là 1, do đó, iter_contentlặp đi lặp lại trên luồng kết quả 1 byte mỗi lần. Xem tài liệu python-requests.org/en/latest/api/ .
CadentOrange

10
Điều này cũng tải toàn bộ phản hồi vào bộ nhớ, mà bạn có thể muốn tránh. Không có để sử dụng PILở đây, chỉ with open(image_name, 'wb') as outfile: outfile.write(r.content)là đủ.
Martijn Pieters

3
PILcũng không có trong thư viện tiêu chuẩn làm cho nó ít di động hơn.
jjj

2
@ZhenyiZhang iter_contentchậm vì bạn chunk_sizequá nhỏ, nếu bạn tăng lên 100k thì sẽ nhanh hơn rất nhiều.
Vương

Đây là câu trả lời tốt nhất. Không phải lúc nào cũng tốt nhất để đọc tệp vào bộ nhớ, nhưng OP đã chỉ định "hình ảnh" nghĩa là các tệp thường sẽ có dung lượng dưới 4 MB, do đó có tác động nhỏ đến bộ nhớ.
Chris Conlan

51

Điều này có thể dễ dàng hơn sử dụng requests. Đây là lần duy nhất tôi từng đề nghị không sử dụngrequests để làm công cụ HTTP.

Hai lớp lót sử dụng urllib:

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

Ngoài ra còn có một mô-đun Python đẹp có tên wgetkhá dễ sử dụng. Tìm thấy ở đây .

Điều này thể hiện sự đơn giản của thiết kế:

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

Thưởng thức.

Chỉnh sửa: Bạn cũng có thể thêm một outtham số để chỉ định đường dẫn.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)

Tôi đã sử dụng wgetmà không gặp rắc rối. Cảm ơn bạn đã nêu những lợi ích của việc sử dụngurllib3
h3xh4wk

1
Lưu ý rằng câu trả lời này là dành cho Python 2. Đối với Python 3 bạn cần làm urllib.request.urlretrieve("http://example.com", "file.ext").
Husky

1
Cảm ơn @Husky. Cập nhật.
Blairg23

28

Đoạn mã sau tải xuống một tập tin.

Các tập tin được lưu với tên tệp của nó như trong url được chỉ định.

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

Có 2 cách chính:

  1. Sử dụng .content(đơn giản / chính thức) (xem câu trả lời của Zhenyi Zhang ):

    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. Sử dụng .raw(xem câu trả lời của Martijn Pieters ):

    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.

Thời gian cả hai cho thấy không có sự khác biệt đáng chú ý.


2
Tôi đã thử một loạt các câu trả lời và 1.câu trả lời của bạn (sử dụng io.BytesIOImage) là câu trả lời đầu tiên phù hợp với tôi trên Python 3.6. Đừng quên from PIL import Image(và pip install Pillow).
colllin

Có gì khác nhau giữa .content và .raw?
foxiris

13

Dễ dàng nhập hình ảnh và yêu cầu

from PIL import Image
import requests

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

4

Dưới đây là một câu trả lời thân thiện hơn mà vẫn sử dụng phát trực tuyến.

Chỉ cần xác định các chức năng và gọi getImage(). Nó sẽ sử dụng cùng tên tệp với url và ghi vào thư mục hiện tại theo mặc định, nhưng cả hai đều có thể được thay đổi.

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

Các requestcan đảm getImage()dựa trên câu trả lời ở đây và can đảm getImageFast()dựa trên câu trả lời ở trên .


3

Tôi sẽ đăng một câu trả lời vì tôi không có đủ đại diện để bình luận, nhưng với wget như được đăng bởi Blairg23, bạn cũng có thể cung cấp một tham số ngoài cho đường dẫn.

 wget.download(url, out=path)

2

Đây là phản hồi đầu tiên xuất hiện cho các tìm kiếm của google về cách tải xuống tệp nhị phân có yêu cầu. Trong trường hợp bạn cần tải xuống một tệp tùy ý với các yêu cầu, bạn có thể sử dụng:

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
Đẹp! Nó thậm chí có một ẩn .close(). Đây là câu trả lời tốt nhất tính đến năm 2019 tôi đoán.
Daniel W.

2

Đây là cách tôi đã làm nó

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

Bạn có thể làm một cái gì đó như thế này:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.