2010-05-18 9 views
7

Estoy intentando descargar más de 1 millón de páginas (URL que terminan con una ID de secuencia). Implementé un tipo de gestor de descargas multipropósito con una cantidad configurable de subprocesos de descarga y un subproceso de procesamiento. El descargador descarga archivos por lotes:¿Por qué el curl en Ruby es más lento que el curl de la línea de comandos?

curl = Curl::Easy.new 

batch_urls.each { |url_info| 
    curl.url = url_info[:url] 
    curl.perform 
    file = File.new(url_info[:file], "wb") 
    file << curl.body_str 
    file.close 
    # ... some other stuff 
} 

He intentado descargar 8000 páginas de muestra. Cuando uso el código anterior, obtengo 1000 en 2 minutos. Cuando escribo todas las URL en un archivo y hago en shell:

cat list | xargs curl 

Genero las 8000 páginas en dos minutos.

La cosa es, lo necesito para tenerlo en código ruby, porque hay otro código de monitoreo y procesamiento.

que he intentado:

  • Curl :: Multi - de alguna manera es más rápido, pero no alcanza el 50-90% de los archivos (no descargarlos y da ninguna razón/código)
  • con múltiples hilos Curl :: Fácil: alrededor de la misma velocidad que con un solo hilo

¿Por qué se reutiliza Curl :: Easy más lento que las llamadas curl de línea de comando posteriores y cómo puedo hacerlo más rápido? O lo que estoy haciendo mal?

Preferiría arreglar mi código de administrador de descargas que hacer la descarga para este caso de una manera diferente.

Antes de esto, estaba llamando a wget de línea de comandos que proporcioné con un archivo con una lista de URL. Howerver, no se manejaron todos los errores, tampoco fue posible especificar el archivo de salida para cada URL por separado cuando se usa la lista de URL.

Ahora me parece que la mejor manera sería usar varios hilos con la llamada del sistema al comando 'curl'. Pero ¿por qué cuando puedo usar Curl directamente en Ruby?

Código

para el gestor de descargas es aquí, si que podría ayudar: Download Manager (He jugado con los tiempos de espera, de no-ajustarlo a diferentes valores, no parecía ayuda)

Alguna pista apreciados.

+0

He tratado de crear sencilla llamada al sistema en lugar de rizo del rizo :: Fácil: resultado = sistema ("rizar", "s", "-o", ruta, url) y parece ser mucho más rápido. Estoy obteniendo alrededor de 300kb/s en lugar de 60kb/s con Curl :: Easy. Es extraño: la llamada al sistema con gran sobrecarga del sistema y sin conexión, la reutilización es mucho más rápida que las funciones de la biblioteca. El uso de la CPU es mucho mayor, obviamente, sin embargo, la velocidad es mucho mejor. De todos modos, esta no es una buena solución para mi problema, aunque funciona mejor. – Stiivi

+0

¿Ha intentado utilizar versiones más recientes de curb con la interfaz Curl :: Multi.download? – todd

+1

'lista de gatos | xargs curl' está pasando muchas URL a Curl en la línea de comandos, en lugar de una a la vez, por lo que Curl está recuperando muchas a la vez. Puedes hacerlo en Ruby con la suficiente facilidad, pero debes comparar manzanas con manzanas y usar HTTPClient o Typhoeus. –

Respuesta

5

Esto podría ser una tarea apropiado para Typhoeus

Algo como esto (no probado):

require 'typhoeus' 

def write_file(filename, data) 
    file = File.new(filename, "wb") 
    file.write(data) 
    file.close 
     # ... some other stuff 
end 

hydra = Typhoeus::Hydra.new(:max_concurrency => 20) 

batch_urls.each do |url_info| 
    req = Typhoeus::Request.new(url_info[:url]) 
    req.on_complete do |response| 
     write_file(url_info[:file], response.body) 
    end 
    hydra.queue req 
end 

hydra.run 

Ahora que lo pienso de ella, es posible obtener un problema de memoria debido a la enorme cantidad que de archivos. Una forma de prevenir eso sería nunca almacenar los datos en una variable, sino transmitirlos directamente al archivo. Puede usar em-http-request para eso.

EventMachine.run { 
    http = EventMachine::HttpRequest.new('http://www.website.com/').get 
    http.stream { |chunk| print chunk } 
    # ... 
} 
+0

aseado, parece funcionar muy bien! :-) Trataré de probarlo durante la noche (no para poner uno de los sitios del gobierno) para al menos 50-100k archivos para ver cuánto tiempo lleva y para detectar posibles problemas. Necesita ser ejecutado periódicamente más tarde. Gracias por tu consejo. – Stiivi

+0

+1 Para transmitir al archivo – Basic

0

Primero déjeme decir que no sé casi nada sobre Ruby.

Lo que sí sé es que Ruby es un lenguaje interpretado; no es sorprendente que sea más lento que el código altamente optimizado que se ha compilado para una plataforma específica. Cada operación de archivo probablemente tendrá controles a su alrededor que curl no tiene. Las "otras cosas" ralentizarán aún más las cosas.

¿Ha intentado perfilar su código para ver dónde se está gastando la mayor parte del tiempo?

+0

Los métodos de clase Ruby Curl :: Easy son "envoltorios" alrededor de las funciones de libcurl C, todo el código de descarga está escrito en C. La sobrecarga adicional de llamadas e intérpretes es insignificante en este caso. También tenga en cuenta que la ejecución de la línea de comandos Curl fue más rápida e incluye la creación de procesos y otras cosas (que es rápido, pero creo que es más lento que la llamada a la función de intérprete Rubu). – Stiivi

+0

@Stiivi, obviamente alguna suposición que ha hecho está mal en alguna parte. Probablemente esta parte: "la sobrecarga adicional de llamadas e intérpretes es insignificante". Algo no es insignificante Probablemente tomando las cadenas C y convirtiéndolas en cadenas de Ruby. – tster

0

Stiivi,

alguna posibilidad de que Net::HTTP sería suficiente para sencilla descarga de páginas HTML?

+0

Solo es HTTP. – Stiivi

3

Por lo tanto, si no establece un controlador on_body que el bordillo almacenará temporalmente la descarga. Si está descargando archivos, debe usar un controlador on_body. Si quieres descargar varios archivos usando Ruby Curl, prueba la interfaz Curl :: Multi.download.

require 'rubygems' 
require 'curb' 

urls_to_download = [ 
    'http://www.google.com/', 
    'http://www.yahoo.com/', 
    'http://www.cnn.com/', 
    'http://www.espn.com/' 
] 
path_to_files = [ 
    'google.com.html', 
    'yahoo.com.html', 
    'cnn.com.html', 
    'espn.com.html' 
] 

Curl::Multi.download(urls_to_download, {:follow_location => true}, {}, path_to_files) {|c,p|} 

Si solo quiere descargar un solo archivo.

Curl::Easy.download('http://www.yahoo.com/') 

Aquí es un buen recurso: http://gist.github.com/405779

0

no ha especificado una versión de Ruby, pero las discusiones en 1.8.x son hilos de espacio de usuario, no programadas por el sistema operativo, por lo que todo el intérprete de Ruby solo use una CPU/núcleo. Además de eso, hay un bloqueo de intérprete global, y probablemente también otros bloqueos, lo que interfiere con la concurrencia. Como está tratando de maximizar el rendimiento de la red, probablemente esté subutilizando las CPU.

Genera tantos procesos como la memoria de la máquina y limita la dependencia de los hilos.

1

Se han realizado pruebas comparativas que han comparado el bordillo con otros métodos como HTTPClient. El ganador, en casi todas las categorías, fue HTTPClient. Además, ha habido algunos escenarios documentados donde el bordillo NO funciona en escenarios de multi-threading.

Como tú, he tenido tu experiencia. Ejecuté comandos del sistema de curl en más de 20 hilos simultáneos y fue 10 veces más rápido que correr el bordillo en más de 20 hilos simultáneos. No importa, lo que probé, este siempre fue el caso.

He cambiado desde entonces a HTTPClient, y la diferencia es enorme. Ahora funciona tan rápido como 20 comandos simultáneos del sistema curl, y utiliza menos CPU también.

+0

, agregar un enlace para HTTPClient? – jwfearn

Cuestiones relacionadas