He escrito un descargador de subprocesos múltiples pausable usando solicitudes y subprocesos, sin embargo, las descargas simplemente no pueden completarse después de reanudar, en pocas palabras, debido a condiciones especiales de la red, las conexiones a menudo pueden morir durante las descargas que requieren actualizar las conexiones.
Puede ver el código aquí en mi pregunta anterior:
Observé que las descargas pueden ir más allá del 100% después de reanudar y no se detendrán (al menos no he visto que se detengan), los índices mmap se saldrán de los límites y muchos mensajes de error...
Finalmente me di cuenta de que esto se debe a que el fantasma de la solicitud anterior hace que el servidor envíe por error datos adicionales de la última conexión que no se descargaron.
Esta es mi solución:
s = requests.session() r = s.get( url, headers={'connection': 'close', 'range': 'bytes={0}-{1}'.format(start, end)}, stream=True) r.close() s.close() del r del sEn mis pruebas, descubrí que las solicitudes tienen dos atributos llamados sesión, un título, una minúscula, la minúscula es una función y el otro es un constructor de clase, ambos crean un objeto request.sessions.Session, ¿hay alguna diferencia entre ellos?
¿Y cómo puedo configurar keep-alive en False?
El método encontrado aquí ya no funcionará:
In [39]: s = requests.session() ...: s.config['keep_alive'] = False --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-39-497f569a91ba> in <module> 1 s = requests.session() ----> 2 s.config['keep_alive'] = False AttributeError: 'Session' object has no attribute 'config'Este método de aquí no arroja errores:
s = requests.session() s.keep_alive = FalsePero dudo seriamente que tenga algún efecto, solo agrega un nuevo booleano al objeto y no creo que el objeto lo maneje.
He visto un método de cierre en solicitudes.modelos.Respuesta, ¿tiene algún efecto en este caso o simplemente puedo cerrar la sesión?
Y, por último, con este método, ¿está garantizado que el servidor nunca enviará bytes adicionales de conexiones inactivas anteriores?
En general, con python, cuando hay algún tipo de 'controlador' que se supone que se cierra después del uso, envolver el uso with puede limitar el alcance de la cosa a un pequeño bloque de código.
ResponseData = None with requests.get( Url, headers=Headers) as ResponseObject: ResponseData = ResponseObject.text.encode('utf-8') ResponseData = ResponseData.decode("utf-8") #code down here does not have any idea what "ReponseObject" is. #For some reason python is able to kill it more reilably after `with`No estoy seguro de si esta es una respuesta canónica, pero podría serle útil. El fragmento funciona para mí, pero evito crear una sesión por completo. Este truco me ha funcionado para innumerables otras cosas que se suponía que debían cerrarse, pero no lo hicieron.
EDITAR: Seguimiento de la sesión: supongo que puedes duplicar with ver si funciona.
with requests.session() as s: with s.get(....) as r: #try stuff hereSupongo que su problema no está relacionado con el servidor. Probablemente los servidores se están comportando correctamente y el problema son los hilos.
Teniendo en cuenta el código de la pregunta relacionada, si está actualizado, cuando PAUSE se establece en verdadero, lo que sucede durante el 50% del tiempo cuando el primer argumento argv se establece en 1 , se crean docenas de subprocesos cada segundo (en realidad num_connections subprocesos , la (pressed - lastpressed).total_seconds() > 0.5 y self.paused = not self.paused hace que un nuevo lote comience cada segundo). En Linux, verificaría esto con tp -H -p $pid o watch ps -T -p $pid o watch ls /proc/$pid/task/ ; probablemente esté usando Windows y existen formas de Windows para verificar esto.
Cada lote de conexiones es correcto cuando se considera de forma aislada, el rango de conexión se establece correctamente en los encabezados. Olfateándote verás que están bien. El problema surge cuando llegan nuevos lotes de hilos haciendo el mismo trabajo. Obtiene muchos hilos que descargan rangos similares en diferentes lotes que le brindan los mismos datos. Dado que su lógica de escritura es relativa, no absoluta, si dos subprocesos le dan el mismo fragmento 123, su self.position += len(chunk) aumentará para ambos fragmentos similares, lo que puede ser una razón por la que obtiene su más del 100%.
Para probar si sucede lo que dije, solo intente descargar un archivo cada vez mayor y verifique si su archivo que se está guardando no sufre este aumento doble:
0000000000 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 ................ 0000000010 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 07 ................ 0000000020 00 00 00 08 00 00 00 09 00 00 00 0a 00 00 00 0b ................ 0000000030 00 00 00 0c 00 00 00 0d 00 00 00 0e 00 00 00 0f ................O simule un servidor de rango de archivos usted mismo haciendo algo similar a esto:
#!/usr/bin/env python3 from http.server import BaseHTTPRequestHandler, HTTPServer import time hostname = "localhost" serverport = 8081 filesizemegabytes = 8#.25 filesizebytes = int(filesizemegabytes*1024*1024) filesizebytestr = str(filesizebytes) class Server(BaseHTTPRequestHandler): def do_GET(self): self.do(True) def do_HEAD(self): self.do(False) def do(self,writebody=True): rangestr = self.headers.get('range') if type(rangestr) is str and rangestr.startswith('bytes='): self.send_response(206) rangestr = rangestr[6:] rangeint = tuple(int(i) for i in rangestr.split('-')) self.send_header('Content-Range', 'bytes '+rangestr+'/'+filesizebytestr) else: self.send_response(200) rangeint = (0,filesizebytes) self.send_header('Content-type', 'application/octet-stream') self.send_header('Accept-Ranges', 'bytes') self.send_header('Content-Length', rangeint[1]-rangeint[0]) self.end_headers() if writebody: for i in range(rangeint[0],rangeint[1]): self.wfile.write(i.to_bytes(4, byteorder='big')) if __name__ == '__main__': serverinstance = HTTPServer((hostname, serverport), server) print("Server started http://%s:%s" % (hostname, serverport)) try: serverinstance.serve_forever() except KeyboardInterrupt: pass serverinstance.server_close() No necesita subprocesos múltiples para descargas múltiples. Los subprocesos "verdes" son suficientes ya que no necesita más de una CPU, solo necesita esperar a IO. En lugar de multithread + requests , una solución más adecuada sería asyncio + aiohttp ( aiohttp una vez requests no están muy bien diseñadas para async , aunque encontrará algunas adaptaciones en la naturaleza).
Por último, keep-alives son útiles cuando planea volver a conectarse, lo que parece ser su caso. ¿Son sus IP de origen y de origen: puertos iguales? Está tratando de forzar el close de las conexiones, pero una vez que se da cuenta de que el problema no son los servidores, vuelva a analizar su situación y vea si no es mejor keep-alive .