Python żąda przesłania pliku

123

Wykonuję proste zadanie załadowania pliku za pomocą biblioteki żądań Pythona. Przeszukałem Stack Overflow i wydawało się, że nikt nie ma tego samego problemu, a mianowicie, że plik nie został odebrany przez serwer:

import requests
url='http://nesssi.cacr.caltech.edu/cgi-bin/getmulticonedb_release2.cgi/post'
files={'files': open('file.txt','rb')}
values={'upload_file' : 'file.txt' , 'DB':'photcat' , 'OUT':'csv' , 'SHORT':'short'}
r=requests.post(url,files=files,data=values)

Wypełniam wartość słowa kluczowego „upload_file” moją nazwą pliku, ponieważ jeśli zostawię to pole puste, zostanie wyświetlone

Error - You must select a file to upload!

A teraz rozumiem

File  file.txt  of size    bytes is  uploaded successfully!
Query service results:  There were 0 lines.

Który pojawia się tylko wtedy, gdy plik jest pusty. Więc utknąłem, jak pomyślnie wysłać mój plik. Wiem, że plik działa, ponieważ jeśli wejdę na tę stronę i ręcznie wypełnię formularz, zwraca ładną listę dopasowanych obiektów, o co mi chodzi. Naprawdę doceniłbym wszystkie wskazówki.

Kilka innych wątków powiązanych (ale nie odpowiadających na mój problem):

scichris
źródło

Odpowiedzi:

210

Jeśli upload_filema być plikiem, użyj:

files = {'upload_file': open('file.txt','rb')}
values = {'DB': 'photcat', 'OUT': 'csv', 'SHORT': 'short'}

r = requests.post(url, files=files, data=values)

i requestswyśle ​​wieloczęściową upload_filetreść formularza POST z polem ustawionym na zawartość file.txtpliku.

Nazwa pliku zostanie uwzględniona w nagłówku MIME dla określonego pola:

>>> import requests
>>> open('file.txt', 'wb')  # create an empty demo file
<_io.BufferedWriter name='file.txt'>
>>> files = {'upload_file': open('file.txt', 'rb')}
>>> print(requests.Request('POST', 'http://example.com', files=files).prepare().body.decode('ascii'))
--c226ce13d09842658ffbd31e0563c6bd
Content-Disposition: form-data; name="upload_file"; filename="file.txt"


--c226ce13d09842658ffbd31e0563c6bd--

Zwróć uwagę na filename="file.txt"parametr.

filesJeśli potrzebujesz większej kontroli, możesz użyć krotki jako wartości mapowania, zawierającej od 2 do 4 elementów. Pierwszym elementem jest nazwa pliku, po której następuje zawartość i opcjonalna wartość nagłówka typu treści oraz opcjonalne mapowanie dodatkowych nagłówków:

files = {'upload_file': ('foobar.txt', open('file.txt','rb'), 'text/x-spam')}

Ustawia to alternatywną nazwę pliku i typ zawartości, pomijając opcjonalne nagłówki.

Jeśli masz na myśli, że całe ciało POST ma zostać pobrane z pliku (bez określonych innych pól), nie używaj tego filesparametru, po prostu opublikuj plik bezpośrednio jako data. Możesz wtedy również ustawić Content-Typenagłówek, ponieważ żaden nie zostanie ustawiony inaczej. Zobacz Żądania Pythona - dane POST z pliku .

Martijn Pieters
źródło
Cześć, jak wysłać wiele plików o tej samej nazwie? Na przykład „przywiązanie”.
William Wino
4
@William: można użyć sekwencję krotek 2 wartości też, co pozwala nazwy pól ponownego użytku: files = [('attachment', open('attachment1.txt', 'rb')), ('attachment', open('attachment2.txt', 'rb'))]. Każda krotka to para klucza i wartości.
Martijn Pieters
2
Możesz także użyć, files={'file':('nameoffile',open('namoffile','rb'),'Content-Type':'text/html','other header'),'file2':('nameoffile2',open('nameoffile2','rb'),'Content-Type':'application/xml','other header')}ale jeśli używane jest files = {}, to headers = {'Content-Type': 'blah blah'} nie może być używane! -> @ martijn-pieters: ponieważ multipart / form-data Content-Type musi zawierać wartość graniczną używaną do oddzielenia części w treści wiadomości. Brak ustawienia nagłówka Content-Type gwarantuje, że żądania ustawią go na poprawną wartość.
zaki
1
@MartijnPieters Czy to nie ryzykuje wycieku pliku? Czy requeststo zamyka?
Matt Messersmith,
4
@MattMessersmith: nie, nie jest zamknięty. Jeśli chcesz zamknąć plik, użyj with open(...) as fobj:i użyj fobjw filesmapowaniu.
Martijn Pieters
35

(2018) nowa biblioteka żądań Pythona uprościła ten proces, możemy użyć zmiennej `` files '', aby zasygnalizować, że chcemy przesłać plik zakodowany w wielu częściach

url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}

r = requests.post(url, files=files)
r.text
laycat
źródło
3
Czy biblioteka żądań automatycznie zamyka plik?
Demetris
1
cześć, minęło trochę czasu, odkąd korzystałem z tej biblioteki. Fajne pytanie. czy możesz mi i innym pomóc, wpisując lsof | grep „nazwa pliku” i udostępnić nam swoje wyniki? dzięki :)
laycat
1
Przy użyciu lsofwydaje się, że plik pozostaje otwarty, a przynajmniej tak interpretuję poniższe wyniki. Przed uruchomieniem openw lsoftabeli nie ma rekordu o filename. Następnie po wykonaniu operacji openpojawia się wiele rekordów z readdostępem. Po wykonaniu instrukcji requests.postnadal istnieją rekordy wskazujące, że plik nie został zamknięty.
Demetris,
23

Przesyłanie przez klienta

Jeśli chcesz przesłać pojedynczy plik z requestsbiblioteką Python , request lib obsługuje przesyłanie strumieniowe , które pozwala na wysyłanie dużych plików lub strumieni bez wczytywania do pamięci .

with open('massive-body', 'rb') as f:
    requests.post('http://some.url/streamed', data=f)

Po stronie serwera

Następnie przechowuj plik na server.pyboku tak, aby zapisywać strumień do pliku bez ładowania do pamięci. Poniżej znajduje się przykład korzystania z przesyłania plików Flask .

@app.route("/upload", methods=['POST'])
def upload_file():
    from werkzeug.datastructures import FileStorage
    FileStorage(request.stream).save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return 'OK', 200

Lub użyj analizy danych formularza werkzeug, jak wspomniano w poprawce dotyczącej problemu „ wysyłania dużych plików pochłaniających pamięć ”, aby uniknąć nieefektywnego wykorzystania pamięci przy wysyłaniu dużych plików (plik st 22 GiB w ~ 60 sekund. Zużycie pamięci jest stałe na poziomie około 13 MiB.).

@app.route("/upload", methods=['POST'])
def upload_file():
    def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
        import tempfile
        tmpfile = tempfile.NamedTemporaryFile('wb+', prefix='flaskapp', suffix='.nc')
        app.logger.info("start receiving file ... filename => " + str(tmpfile.name))
        return tmpfile

    import werkzeug, flask
    stream, form, files = werkzeug.formparser.parse_form_data(flask.request.environ, stream_factory=custom_stream_factory)
    for fil in files.values():
        app.logger.info(" ".join(["saved form name", fil.name, "submitted as", fil.filename, "to temporary file", fil.stream.name]))
        # Do whatever with stored file at `fil.stream.name`
    return 'OK', 200
gihanchanuka
źródło
0

W Ubuntu możesz aplikować w ten sposób,

aby zapisać plik w jakiejś lokalizacji (tymczasowo), a następnie otworzyć i wysłać go do API

      path = default_storage.save('static/tmp/' + f1.name, ContentFile(f1.read()))
      path12 = os.path.join(os.getcwd(), "static/tmp/" + f1.name)
      data={} #can be anything u want to pass along with File
      file1 = open(path12, 'rb')
      header = {"Content-Disposition": "attachment; filename=" + f1.name, "Authorization": "JWT " + token}
       res= requests.post(url,data,header)
Harshit Trivedi
źródło
jaka jest wartość datazmiennej?
am.rez
może to być dowolna nazwa użytkownika, właśnie pokazałem, jak przesyłać pliki do REST apis
Harshit Trivedi