วิธีดาวน์โหลดรูปภาพโดยใช้คำขอ


368

ฉันกำลังพยายามดาวน์โหลดและบันทึกภาพจากเว็บโดยใช้requestsโมดูลของไพธ อน

นี่คือรหัส (ที่ใช้งานได้) ที่ฉันใช้:

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

นี่คือรหัสใหม่ (ไม่ทำงาน) โดยใช้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)

คุณสามารถช่วยฉันในสิ่งที่แอตทริบิวต์จากการตอบสนองต่อการใช้งานจากrequests?


15
ในการใช้ r.raw คุณต้องตั้งค่า stream = True
clsung

สิ่งนี้ตอบคำถามของคุณหรือไม่ ดาวน์โหลดไฟล์ขนาดใหญ่ในไพ ธ อนพร้อมคำขอ
AMC

คำตอบ:


516

คุณสามารถใช้response.rawวัตถุไฟล์หรือทำซ้ำมากกว่าการตอบสนอง

หากต้องการใช้response.rawวัตถุคล้ายไฟล์จะไม่ถอดรหัสการตอบกลับที่ถูกบีบอัด (ด้วย GZIP หรือยุบ) คุณสามารถบังคับให้คลายการบีบอัดสำหรับคุณได้โดยตั้งค่าdecode_contentแอตทริบิวต์เป็นTrue( requestsตั้งค่าเป็นFalseควบคุมการถอดรหัสเอง) จากนั้นคุณสามารถใช้shutil.copyfileobj()เพื่อให้ Python สตรีมข้อมูลไปยังวัตถุไฟล์:

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)        

หากต้องการวนซ้ำการตอบกลับให้ใช้การวนซ้ำ การวนซ้ำเช่นนี้ทำให้มั่นใจได้ว่าข้อมูลจะถูกขยายโดยขั้นตอนนี้:

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)

นี่จะอ่านข้อมูลในหน่วยจำนวน 128 ไบต์; หากคุณรู้สึกว่าขนาดก้อนอื่นทำงานได้ดีขึ้นให้ใช้Response.iter_content()วิธีการที่มีขนาดก้อนแบบกำหนดเอง:

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)

โปรดทราบว่าคุณต้องเปิดไฟล์ปลายทางในโหมดไบนารี่เพื่อให้มั่นใจว่าไพ ธ อนไม่ได้พยายามแปลบรรทัดใหม่สำหรับคุณ นอกจากนี้เรายังตั้งค่าstream=Trueเพื่อrequestsไม่ให้ดาวน์โหลดภาพทั้งหมดลงในหน่วยความจำก่อน


2
r2 = requests.post(r.url, data); print r2.contentด้วยความช่วยเหลือของคำตอบของคุณฉันจะสามารถที่จะหาข้อมูลในแฟ้มข้อความขั้นตอนที่ผมใช้อยู่ filenameแต่ตอนนี้ผมยังต้องการที่จะรู้ว่า มีวิธีทำความสะอาดของพวกเขา? - ปัจจุบันฉันพบชื่อไฟล์ในส่วนหัว - r2.headers['content-disposition'] ที่ให้ฉันออกเป็น: 'attachment; filename=DELS36532G290115.csi' ฉันกำลังแยกสตริงนี้สำหรับชื่อไฟล์ ... เป็นวิธีที่สะอาดขึ้นหรือไม่
Grijesh Chauhan

6
@GrijeshChauhan: ใช่content-dispositionส่วนหัวเป็นวิธีที่จะไปที่นี่; ใช้cgi.parse_header()ในการแยกวิเคราะห์และรับพารามิเตอร์; แล้วก็params = cgi.parse_header(r2.headers['content-disposition'])[1] params['filename']
Martijn Pieters

1
เพื่อให้ได้เริ่มต้น 128 ชิ้นไบต์คุณจะต้องย้ำมากกว่าrequests.Responseตัวเองfor chunk in r: ... : โทรiter_content()โดยไม่ต้องchunk_sizeจะย้ำใน 1 ชิ้นไบต์
dtk

@dtk: ขอบคุณฉันจะอัปเดตคำตอบ ย้ำการเปลี่ยนแปลงหลังจากที่ผมโพสต์คำตอบของฉัน
Martijn Pieters

1
@KumZ ด้วยเหตุผลสองประการ: response.okไม่เคยมีการบันทึกและจะสร้างจริงสำหรับสถานะ 1xx, 2xx หรือ 3xx ใด ๆ แต่การตอบกลับ 200 ครั้งเท่านั้นที่มีเนื้อหาการตอบกลับ
Martijn Pieters

232

รับวัตถุเหมือนไฟล์จากคำขอและคัดลอกไปยังไฟล์ นอกจากนี้ยังจะหลีกเลี่ยงการอ่านสิ่งทั้งหมดลงในหน่วยความจำทันที

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
ขอบคุณมากสำหรับการกลับมาและตอบคำถามนี้ แม้ว่าคำตอบอื่น ๆ จะได้ผล แต่อันนี้ง่ายและรวดเร็วกว่า
dkroy

11
เป็นที่น่าสังเกตว่ามีเซิร์ฟเวอร์เพียงไม่กี่แห่งที่ตั้งค่าเป็น GZIP ภาพของตนเพราะภาพมีการบีบอัดข้อมูลของตัวเองอยู่แล้ว มันต่อต้านการสูญเสียวงจร CPU ที่มีประโยชน์เพียงเล็กน้อย ดังนั้นในขณะนี้อาจมีปัญหากับเนื้อหาข้อความโดยเฉพาะกับรูปภาพไม่ใช่
phette23

3
มีวิธีใดบ้างที่เราจะสามารถเข้าถึงชื่อไฟล์ดั้งเดิมได้
mahes

@ phette23 นอกจากนี้ยังเป็นที่น่าสังเกตว่า Google PageSpeed ​​รายงานและดำเนินการตามค่าเริ่มต้น
Wernight

8
ควรตั้งค่าr.raw.decode_content = Trueก่อนshutil.copyfileobj(response.raw, out_file)เพราะby default, decode compressed responses (with GZIP or deflate)ดังนั้นคุณจะได้รับภาพศูนย์ไฟล์
Simin Jie

166

วิธีนี้เป็นวิธีแก้ปัญหาอย่างรวดเร็ว

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
คุณหมายถึงอะไร! f = open("/Users/apple/Desktop/sample.jpg", 'wb')คุณหมายถึงอะไรกับเส้นทางนี้! ฉันต้องการที่จะดาวน์โหลดภาพ
รอยยิ้ม

3
ที่เปิดไฟล์ descriptor ในพา ธ ที่ระบุซึ่งไฟล์รูปภาพสามารถเขียนได้
kiranbkrishna

@AndrewGlazkov ฉันคิดว่ามันจะมีมากขึ้น Pythonic ใช้if response.ok:
EndermanAPM

5
response.ok เป็นจริงสำหรับสถานะ 1xx, 2xx หรือ 3xx ใด ๆ แต่มีเพียง 200 คำตอบเท่านั้นที่มีการตอบสนองตามที่ @Martijn Pieters ที่กล่าวถึงในความคิดเห็นข้างต้น
annndrey

75

ฉันต้องการดาวน์โหลดภาพโดยใช้คำขอเดียวกัน ฉันลองคำตอบของ Martijn Pieters ก่อนและมันใช้ได้ดี แต่เมื่อฉันทำโปรไฟล์ในฟังก์ชั่นง่าย ๆ นี้ฉันพบว่ามันใช้ฟังก์ชั่นการโทรจำนวนมากเมื่อเทียบกับ urllib และ urllib2

จากนั้นฉันก็ลองวิธีที่ผู้เขียนร้องขอโมดูลนั้น

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

สิ่งนี้ยิ่งลดจำนวนการเรียกฟังก์ชั่นลงดังนั้นจึงทำให้แอปพลิเคชันของฉันเร็วขึ้น นี่คือรหัสของ profiler ของฉันและผลลัพธ์

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

ผลลัพธ์สำหรับ testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

และผลลัพธ์สำหรับ testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds

13
นี่เป็นเพราะคุณไม่ได้ระบุchunk_sizeพารามิเตอร์ที่เป็นค่าเริ่มต้นที่ 1 ดังนั้นiter_contentจะวนซ้ำในสตรีมผลลัพธ์ครั้งละ 1 ไบต์ โปรดดูเอกสารpython-requests.org/en/latest/api/...
CadentOrange

10
นอกจากนี้ยังเป็นการโหลดการตอบสนองทั้งหมดลงในหน่วยความจำซึ่งคุณอาจต้องการหลีกเลี่ยง ไม่มีการใช้PILที่นี่อย่างใดอย่างหนึ่งก็with open(image_name, 'wb') as outfile: outfile.write(r.content)เพียงพอแล้ว
Martijn Pieters

3
PILยังไม่ได้อยู่ในไลบรารีมาตรฐานซึ่งทำให้พกพาได้น้อยลง
jjj

2
@ ZhenyiZhang iter_contentช้าเพราะคุณchunk_sizeเล็กเกินไปถ้าคุณเพิ่มมันเป็น 100k มันจะเร็วขึ้นมาก
วัง

นี่คือคำตอบที่ดีที่สุด การอ่านไฟล์ลงในหน่วยความจำไม่ดีที่สุดเสมอไป แต่ "รูปภาพ" ที่ระบุโดย OP หมายความว่าไฟล์จะน้อยกว่า 4MB ดังนั้นจึงส่งผลกระทบเล็กน้อยต่อหน่วยความจำ
Chris Conlan

51

requestsนี้อาจจะง่ายกว่าการใช้ นี่เป็นครั้งเดียวที่ฉันจะไม่แนะนำให้ใช้requestsเพื่อทำสิ่ง HTTP

สองซับใช้urllib:

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

นอกจากนี้ยังมีโมดูล Python ที่ดีชื่อว่าwgetใช้งานง่าย พบที่นี่

นี่แสดงให้เห็นถึงความเรียบง่ายของการออกแบบ:

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

สนุก.

แก้ไข:คุณยังสามารถเพิ่มoutพารามิเตอร์เพื่อระบุเส้นทาง

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

ฉันใช้wgetโดยไม่ต้องยุ่งยากใด ๆ ขอบคุณที่ระบุประโยชน์ของการใช้urllib3
h3xh4wk

1
หมายเหตุว่าคำตอบนี้เป็นงูหลามงูหลาม 2 สำหรับ 3 urllib.request.urlretrieve("http://example.com", "file.ext")ที่คุณต้องทำ
ฮัสกี้

1
ขอบคุณ @Husky Updated
Blairg23

28

ข้อมูลโค้ดต่อไปนี้จะดาวน์โหลดไฟล์

ไฟล์ถูกบันทึกด้วยชื่อไฟล์ตามที่ระบุใน 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

มี 2 ​​วิธีหลัก:

  1. ใช้.content(ง่ายที่สุด / เป็นทางการ) (ดูคำตอบของ Zhenyi จาง ):

    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. ใช้.raw(ดูคำตอบของ Pieters Martijn ):

    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.

เวลาทั้งสองไม่แสดงความแตกต่างที่เห็นได้ชัดเจน


2
ฉันลองคำตอบมากมายและ1.คำตอบของคุณ(ใช้io.BytesIOและImage) เป็นคำตอบแรกที่ทำงานกับฉันใน Python 3.6 อย่าลืมfrom PIL import Image(และpip install Pillow)
colllin

ความแตกต่างระหว่าง. content และ. raw คืออะไร
foxiris

13

ง่ายเหมือนกับการนำเข้ารูปภาพและคำขอ

from PIL import Image
import requests

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

4

นี่คือคำตอบที่เป็นมิตรกับผู้ใช้มากกว่าที่ยังคงใช้การสตรีม

getImage()เพียงแค่กำหนดฟังก์ชั่นเหล่านี้และการโทร มันจะใช้ชื่อไฟล์เดียวกับ URL และเขียนไปยังไดเรกทอรีปัจจุบันโดยค่าเริ่มต้น แต่ทั้งสองสามารถเปลี่ยนแปลงได้

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

requestguts ของgetImage()จะขึ้นอยู่กับคำตอบที่นี่และความกล้าของgetImageFast()จะขึ้นอยู่กับคำตอบดังกล่าวข้างต้น


3

ฉันจะโพสต์คำตอบเนื่องจากฉันมีตัวแทนไม่เพียงพอที่จะแสดงความคิดเห็น แต่ด้วย wget ที่โพสต์โดย Blairg23 คุณสามารถระบุพารามิเตอร์ out สำหรับพา ธ

 wget.download(url, out=path)

2

นี่คือการตอบกลับครั้งแรกที่เกิดขึ้นสำหรับการค้นหาของ Google เกี่ยวกับวิธีการดาวน์โหลดไฟล์ไบนารีพร้อมคำขอ ในกรณีที่คุณต้องการดาวน์โหลดไฟล์โดยพลการพร้อมคำขอคุณสามารถใช้:

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
ดี! .close()มันมีแม้กระทั่งโดยปริยาย นี่คือคำตอบที่ดีที่สุดในปี 2019 ฉันเดา
Daniel W.

2

นี่คือวิธีที่ฉันทำ

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

คุณสามารถทำสิ่งนี้:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.