Python solicita la carga de archivos

123

Estoy realizando una tarea simple de cargar un archivo usando la biblioteca de solicitudes de Python. Busqué Stack Overflow y nadie parecía tener el mismo problema, es decir, que el servidor no recibe el archivo:

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)

Estoy completando el valor de la palabra clave 'upload_file' con mi nombre de archivo, porque si lo dejo en blanco, dice

Error - You must select a file to upload!

Y ahora tengo

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

Que aparece solo si el archivo está vacío. Así que estoy atascado en cuanto a cómo enviar mi archivo correctamente. Sé que el archivo funciona porque si voy a este sitio web y completo manualmente el formulario, devuelve una buena lista de objetos coincidentes, que es lo que busco. Realmente agradecería todas las pistas.

Algunos otros hilos relacionados (pero no responden a mi problema):

scichris
fuente

Respuestas:

210

Si upload_fileestá destinado a ser el archivo, use:

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

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

y requestsenviará un cuerpo POST de formulario de varias partes con el upload_filecampo establecido en el contenido del file.txtarchivo.

El nombre del archivo se incluirá en el encabezado de mime para el campo específico:

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

Tenga en cuenta el filename="file.txt"parámetro.

Puede usar una tupla para el filesvalor de mapeo, con entre 2 y 4 elementos, si necesita más control. El primer elemento es el nombre del archivo, seguido del contenido, un valor de encabezado de tipo de contenido opcional y una asignación opcional de encabezados adicionales:

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

Esto establece un nombre de archivo y un tipo de contenido alternativos, sin incluir los encabezados opcionales.

Si quiere que todo el cuerpo POST se tome de un archivo (sin otros campos especificados), entonces no use el filesparámetro, simplemente publique el archivo directamente como data. Es posible que también desee establecer un Content-Typeencabezado, ya que no se establecerá ninguno de lo contrario. Consulte Solicitudes de Python: datos POST de un archivo .

Martijn Pieters
fuente
Hola, ¿Cómo envío varios archivos con el mismo nombre? Como 'apego' por ejemplo.
William Wino
4
@William: se puede usar una secuencia de tuplas 2-valor también, lo que le permite reutilizar los nombres de los campos: files = [('attachment', open('attachment1.txt', 'rb')), ('attachment', open('attachment2.txt', 'rb'))]. Cada tupla es un par de clave y valor.
Martijn Pieters
2
También puede usar, files={'file':('nameoffile',open('namoffile','rb'),'Content-Type':'text/html','other header'),'file2':('nameoffile2',open('nameoffile2','rb'),'Content-Type':'application/xml','other header')}pero si se usa files = {}, entonces no se deben usar headers = {'Content-Type': 'blah blah'}. -> @ martijn-pieters: debido a que el tipo de contenido multipart / form-data debe incluir el valor de límite utilizado para delimitar las partes en el cuerpo de la publicación. No configurar el encabezado Content-Type garantiza que las solicitudes lo establezcan en el valor correcto.
zaki
1
@MartijnPieters ¿No se corre el riesgo de filtrar el archivo? ¿Lo requestscierra?
Matt Messersmith
4
@MattMessersmith: no, no está cerrado. Si desea cerrar el archivo, utilícelo with open(...) as fobj:y utilícelo fobjen el filesmapeo.
Martijn Pieters
35

(2018) la nueva biblioteca de solicitudes de Python ha simplificado este proceso, podemos usar la variable 'archivos' para indicar que queremos cargar un archivo codificado en varias partes

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

r = requests.post(url, files=files)
r.text
laico
fuente
3
¿La biblioteca de solicitudes cierra automáticamente el archivo?
Demetris
1
hola, ha pasado un tiempo desde que utilicé esta biblioteca. buena pregunta. ¿Podrías echarnos una mano a mí y a los demás escribiendo lsof | grep "nombre de archivo" y compartir sus resultados con nosotros? gracias :)
laycat
1
Con el uso de lsof, parece que el archivo permanece abierto, o al menos así es como interpreto los siguientes resultados. Antes, al ejecutar openno hay ningún registro en la lsoftabla sobre filename. Luego, después de que opense ejecuta, aparecen varios registros con readacceso. Después de ejecutar el requests.post, los registros siguen ahí indicando que el archivo no se cerró.
Demetris
23

Carga del cliente

Si desea cargar un solo archivo con la requestsbiblioteca de Python , las solicitudes lib admiten cargas de transmisión , lo que le permite enviar archivos grandes o transmisiones sin leer en la memoria .

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

Lado del servidor

Luego guarde el archivo en el server.py lateral de manera que guarde la secuencia en un archivo sin cargarlo en la memoria. A continuación se muestra un ejemplo con el uso de cargas de archivos 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

O use werkzeug Form Data Parsing como se menciona en una solución para el problema de " cargas de archivos grandes que consumen memoria " para evitar usar la memoria de manera ineficiente en la carga de archivos grandes (st archivo de 22 GiB en ~ 60 segundos. El uso de memoria es constante en aproximadamente 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
fuente
0

En Ubuntu puedes aplicar de esta manera,

para guardar el archivo en alguna ubicación (temporal) y luego abrirlo y enviarlo a la 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
fuente
cual es el valor de la datavariable?
am.rez
puede ser cualquier cosa como el nombre de usuario, acabo de mostrar cómo cargar archivos a las API REST
Harshit Trivedi