Al permitir el uso de plugins y ser modular, Apache Nutch tiene el beneficio de proveer interfaces extensibles a implementaciones personalizadas. Adicionalmente se posibilita el uso de plugins para el indexado, y actualmente existen implementaciones para Apache Solr, Elasticsearch y otros.
Apache Nutch puede correr en un nodo simple o de forma distribuida en un cluster Hadoop y está implementado en Java. Esto implica que se debe contar con Java instalado.
Para quienes no estén familiarizados con el término, un Web crawler, también conocido como spider, spiderbot o simplemente "robot", es un programa que se encarga de navegar páginas Web de forma automática para recolectar datos y enlaces a otras páginas. Típicamente se utilizan para descubrir e indexar enlaces a páginas Web. De esta forma son capaces de recolectar todos los enlaces en uno o múltiples sitios Web y descubrir la estructura y relación entre os mismos. Esta tarea es la que típicamente realizan los buscadores para descubrir el contenido que luego indexarán para ofrecer en los resultados de las búsquedas (claro está que para ofrecer un resultado, previamente se necesita conocerlo, conocer su existencia y contenido).
¿Para qué necesitaría o para qué puede ser útil un crawler? Supongamos que quiero obtener todos los productos en venta en Mercado Libre Argentina y guardar el título junto con el precio para buscar oportunidades de reselling. Ahí está, simplemente basta con poner un Web crawler apuntando a "mercadolibre.com.ar" para luego indexar los datos obtenidos con Elasticsearch. Sólo por citar un ejemplo donde no es posible implementar una solución manual ni es adecuado implementar una propia.
Veamos entonces cómo compilar Apache Nutch 2.x con MongoDB como datastore y Elasticsearch como indexador.
SPOILER ALERT: No es posible indexar en versiones posteriores a la 2.3.3 de Elasticsearch desde Apache Nutch 2.x. Sólo es posible hacerlo utilizando el plugin REST "indexer-elastic-rest" disponible en la última versión estable de Apache Nutch 1.x (ver alternativas al final del artículo).
Compilar Apache Nutch 2.x en Devuan
Abrir una sesión como root e instalar las dependencias necesarias para compilar Nutch:
root@devuan:/home/emi# apt-get install openjdk-8-jdk-headless ant mongodb
Es necesario crear una base de datos y usuario para Nutch en MongoDB.
Descargar y verificar el paquete (desde uno de los mirrors en la sección de descargas):
root@devuan:/home/emi# wget http://apache.dattatec.com/nutch/2.3.1/apache-nutch-2.3.1-src.tar.gz root@devuan:/home/emi# wget https://apache.org/dist/nutch/2.3.1/apache-nutch-2.3.1-src.tar.gz.sha512
Para verificar la integridad del paquete ejecutar:
root@devuan:/home/emi# sha512sum -c apache-nutch-2.3.1-src.tar.gz.sha512 apache-nutch-2.3.1-src.tar.gz: OK
Si es está todo bien, extraer el paquete:
root@devuan:/home/emi# tar xzf apache-nutch-2.3.1-src.tar.gz
La configuración de Nutch se encuentra dentro del directorio conf/
:
root@devuan:/home/emi# cd apache-nutch-2.3.1/conf/
El primer paso en la configuración es definir el acceso a la base MongoDB:
root@devuan:/home/emi/apache-nutch-2.3.1/conf# nano gora.properties
Dentro del archivo gora.properties
se debe definir el datastore MongoDB:
############################ # MongoDBStore properties # ############################ gora.datastore.default=org.apache.gora.mongodb.store.MongoStore gora.mongodb.override_hadoop_configuration=false gora.mongodb.mapping.file=/gora-mongodb-mapping.xml gora.mongodb.servers=localhost:27017 gora.mongodb.db=nutch gora.mongodb.login=nutch gora.mongodb.secret=12345
El archivo gora-mongodb-mapping.xml
define el mapeo de estructuras. No es necesario modificarlo.
root@devuan:/home/emi/apache-nutch-2.3.1/conf# ls -l gora-mongodb-mapping.xml -rw-rw-r-- 1 root root 3194 ene 10 2016 gora-mongodb-mapping.xml
En el archivo de configuración de Nutch (nutch-site.xml
), establecer MongoDB como datastore y Elasticsearch como indexer:
root@devuan:/home/emi/apache-nutch-2.3.1/conf# nano nutch-site.xml
La siguiente es una configuración básica que sirve para comenzar a trabajar con esta configuración:
<configuration> <property> <name>http.agent.name</name> <value>Spider de Linuxito</value> </property> <property> <name>storage.data.store.class</name> <value>org.apache.gora.mongodb.store.MongoStore</value> <description>Clase para almacenar datos en MongoDB</description> </property> <property> <name>plugin.includes</name> <value>protocol-httpclient|urlfilter-regex|parse-(text|tika|js)|index-(basic|anchor)|query-(basic|site|url)|response-(json|xml)|summary-basic|scoring-opic|urlnormalizer-(pass|regex|basic)|indexer-elastic</value> </property> <property> <name>db.ignore.internal.links</name> <value>false</value> </property> <property> <name>db.ignore.external.links</name> <value>false</value> </property> <property> <name>elastic.host</name> <value>localhost</value> </property> <property> <name>elastic.port</name> <value>9300</value> </property> <property> <name>elastic.cluster</name> <value>elasticsearch</value> </property> <property> <name>elastic.index</name> <value>nutchindex</value> </property> <property> <name>parser.character.encoding.default</name> <value>utf-8</value> </property> <property> <name>http.content.limit</name> <value>6553600</value> </property> <property> <name>elastic.max.bulk.docs</name> <value>250</value> <description>Maximum size of the bulk in number of documents.</description> </property> <property> <name>elastic.max.bulk.size</name> <value>2500500</value> <description>Maximum size of the bulk in bytes.</description> </property> </configuration>
Todas las propiedades de configuración se obtienen del archivo de ejemplo nutch-default.xml
.
Por supuesto, la configuración del sitio en el archivo nutch-site.xml
se puede modificar luego de compilar Nutch.
Es importante utilizar una configuración correcta en las propiedades db.ignore.internal.links
y db.ignore.external.links
, ya que si se ignoran enlaces internos y externos (ambas propiedades seteadas en true
), Nutch no recuperará más que las URLs en el archivo de semillas. Tener en cuenta que una URL perteneciente a otro subdominio se considera externa, ya que se trata de otro host.
El último paso en la configuración consiste en habilitar el módulo "gora-mongodb" en el archivo ivy/ivy.xml
:
root@devuan:/home/emi/apache-nutch-2.3.1/conf# cd .. root@devuan:/home/emi/apache-nutch-2.3.1# nano ivy/ivy.xml
Descomentar las siguientes líneas para habilitar MongoDB:
<!-- Uncomment this to use MongoDB as Gora backend. --> <!-- <dependency org="org.apache.gora" name="gora-mongodb" rev="0.6.1" conf="*->default" /> -->
Debe quedar así:
<!-- Uncomment this to use MongoDB as Gora backend. --> <dependency org="org.apache.gora" name="gora-mongodb" rev="0.6.1" conf="*->default" />
Finalmente es posible compilar Nutch ejecutando ant runtime
en el directorio base de los fuentes:
root@devuan:/home/emi/apache-nutch-2.3.1# ant clean root@devuan:/home/emi/apache-nutch-2.3.1# ant runtime
Esto llevará un cierto tiempo dependiendo de los recursos disponibles:
BUILD SUCCESSFUL Total time: 20 minutes 45 seconds
Los binarios compilados se encuentran dentro del directorio runtime/local/bin/
:
root@devuan:/home/emi/apache-nutch-2.3.1# cd runtime/local/ root@devuan:/home/emi/apache-nutch-2.3.1/runtime/local# ls -l total 28 drwxr-xr-x 2 root root 4096 oct 4 10:11 bin drwxr-xr-x 2 root root 4096 oct 4 10:11 conf drwxr-xr-x 3 root root 12288 oct 4 10:11 lib drwxr-xr-x 39 root root 4096 oct 4 10:11 plugins drwxr-xr-x 3 root root 4096 oct 4 10:11 test
Cambiar el ownership de la instalación al usuario con el que correrá Nutch y cerrar la sesión de root:
root@devuan:/home/emi# chown -R emi:emi apache-nutch-2.3.1* root@devuan:/home/emi# exit emi@devuan:~$ cd apache-nutch-2.3.1/runtime/local/
Primeros pasos con Apache Nutch 2.x
Nutch necesita una o más URLs donde a partir de las cuales comenzar a recuperar páginas. Se denominan semillas y necesitan apuntar a páginas válidas. Con sólo una URL basta para comenzar a recuperar páginas:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ mkdir seed emi@devuan:~/apache-nutch-2.3.1/runtime/local$ echo "https://www.mercadolibre.com.ar" > seed/urls.txt
Para limitar la búsqueda a un dominio específico (por ejemplo "mercadolibre.com.ar") incluyendo todos sus subdominios, utilizar la siguiente expresión regular:
# accept anything else #+. +mercadolibre.com.ar
Para comenzar a trabajar con Nutch, el primer paso consiste en inyectar las URLs semilla. Si al ejecutar bin/nutch
se obtiene el error "JAVA_HOME is not set", significa que se debe configurar dicha variable de entorno correctamente:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch inject seed/urls.txt Error: JAVA_HOME is not set.
Buscar el directorio base de la máquina virtual de Java (JVM):
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ find / -type d -name jvm 2>/dev/null /usr/lib/jvm /usr/lib/debug/usr/lib/jvm /usr/share/gdb/auto-load/usr/lib/jvm
En los sistemas Devuan y derivados se encuentra dentro de /usr/lib/jvm/
:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ ll /usr/lib/jvm/ total 20 drwxr-xr-x 4 root root 4096 sep 4 12:41 . drwxr-xr-x 84 root root 4096 oct 3 11:09 .. lrwxrwxrwx 1 root root 24 ene 6 2017 default-java -> java-1.8.0-openjdk-amd64 drwxr-xr-x 4 root root 4096 sep 4 12:05 java-1.5.0-gcj-6-amd64 lrwxrwxrwx 1 root root 20 nov 1 2017 java-1.8.0-openjdk-amd64 -> java-8-openjdk-amd64 -rw-r--r-- 1 root root 2600 ago 9 19:11 .java-1.8.0-openjdk-amd64.jinfo drwxr-xr-x 7 root root 4096 oct 3 11:09 java-8-openjdk-amd64
Es conveniente utilizar el enlace simbólico default-java
. Exportar la variable de entorno JAVA_HOME
apuntando al mismo:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ export JAVA_HOME="/usr/lib/jvm/default-java"
Inyectar en la base de Nutch las URLs a recuperar:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch inject seed/urls.txt InjectorJob: starting at 2018-10-05 09:59:44 InjectorJob: Injecting urlDir: seed/urls.txt InjectorJob: Using class org.apache.gora.mongodb.store.MongoStore as the Gora storage class. InjectorJob: total number of urls rejected by filters: 0 InjectorJob: total number of urls injected after normalization and filtering: 1 Injector: finished at 2018-10-05 09:59:59, elapsed: 00:00:15
Crawling con Nutch
El proceso de crawling con Nutch se divide en 4 tareas: 1- generar urls a recuperar (bin/nutch generate
); 2- recuperar páginas a partir de las URLs en la base (bin/nutch fetch
); 3- parsear las páginas descargadas para obtener nuevas URLs a generar en el paso 1 (bin/nutch parse
); y 4- actualizar la base de datos (bin/nutch updatedb
). Este proceso se repite hasta el infinito, o hasta que no se descubran nuevas URLs (en este caso significaría que hemos descargado todo el sitio Web).

Este proceso se puede realizar manualmente, aunque Nutch provee un script Bash que lo hace de manera automática (bin/crawl
). Sin embargo este script está pensado para indexar en Apache Solr, con lo cual el paso de indexado no será realizado.
Para ejecutar el proceso de forma manual, seguir los siguientes pasos:
1- Generar las URLs:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch generate -topN 100 GeneratorJob: starting at 2018-10-05 10:01:25 GeneratorJob: Selecting best-scoring urls due for fetch. GeneratorJob: starting GeneratorJob: filtering: true GeneratorJob: normalizing: true GeneratorJob: topN: 100 GeneratorJob: finished at 2018-10-05 10:01:29, time elapsed: 00:00:04 GeneratorJob: generated batch id: 1538744485-497126277 containing 1 URLs
2- Descargar las páginas a partir de las URLs en la base:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch fetch -all FetcherJob: starting at 2018-10-05 10:27:51 FetcherJob: fetching all FetcherJob: threads: 10 FetcherJob: parsing: false FetcherJob: resuming: false FetcherJob : timelimit set for : -1 Using queue mode : byHost Fetcher: threads: 10 QueueFeeder finished: total 1 records. Hit by time limit :0 fetching https://www.mercadolibre.com.ar/ (queue crawl delay=5000ms) [...]
3- A continuación, parsear las páginas recuperadas en busca de nuevas URLs:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch parse -all ParserJob: starting at 2018-10-05 10:29:25 ParserJob: resuming: false ParserJob: forced reparse: false ParserJob: parsing all Parsing https://www.mercadolibre.com.ar/ [...]
4- Finalmente, actualizar la base de datos:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch updatedb -all DbUpdaterJob: starting at 2018-10-05 11:05:53 DbUpdaterJob: updatinging all DbUpdaterJob: finished at 2018-10-05 11:05:58, time elapsed: 00:00:04
Volver al paso 1.
Sin embargo, para la primera vez es más simple utilizar el script Bash:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/crawl seed/urls.txt inicio 3 No SOLRURL specified. Skipping indexing. Injecting seed URLs /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch inject seed/urls.txt -crawlId inicio InjectorJob: starting at 2018-10-08 10:16:23 InjectorJob: Injecting urlDir: seed/urls.txt InjectorJob: Using class org.apache.gora.mongodb.store.MongoStore as the Gora storage class. InjectorJob: total number of urls rejected by filters: 0 InjectorJob: total number of urls injected after normalization and filtering: 1 Injector: finished at 2018-10-08 10:16:26, elapsed: 00:00:03 lun oct 8 10:16:26 -03 2018 : Iteration 1 of 3 Generating batchId Generating a new fetchlist /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch generate -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true -topN 50000 -noNorm -noFilter -adddays 0 -crawlId inicio -batchId 1539004586-18854 GeneratorJob: starting at 2018-10-08 10:16:28 GeneratorJob: Selecting best-scoring urls due for fetch. GeneratorJob: starting GeneratorJob: filtering: false GeneratorJob: normalizing: false GeneratorJob: topN: 50000 GeneratorJob: finished at 2018-10-08 10:16:32, time elapsed: 00:00:04 GeneratorJob: generated batch id: 1539004586-18854 containing 1 URLs Fetching : /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch fetch -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true -D fetcher.timelimit.mins=180 1539004586-18854 -crawlId inicio -threads 50 FetcherJob: starting at 2018-10-08 10:16:34 FetcherJob: batchId: 1539004586-18854 FetcherJob: threads: 50 FetcherJob: parsing: false FetcherJob: resuming: false FetcherJob : timelimit set for : 1539015394398 Using queue mode : byHost Fetcher: threads: 50 QueueFeeder finished: total 0 records. Hit by time limit :0 -finishing thread FetcherThread0, activeThreads=0 Fetcher: throughput threshold: -1 Fetcher: throughput threshold sequence: 5 -finishing thread FetcherThread2, activeThreads=47 -finishing thread FetcherThread3, activeThreads=46 -finishing thread FetcherThread4, activeThreads=45 -finishing thread FetcherThread5, activeThreads=44 -finishing thread FetcherThread6, activeThreads=43 -finishing thread FetcherThread7, activeThreads=42 -finishing thread FetcherThread8, activeThreads=41 -finishing thread FetcherThread9, activeThreads=40 -finishing thread FetcherThread10, activeThreads=39 -finishing thread FetcherThread11, activeThreads=38 -finishing thread FetcherThread12, activeThreads=37 -finishing thread FetcherThread13, activeThreads=36 -finishing thread FetcherThread14, activeThreads=35 -finishing thread FetcherThread15, activeThreads=34 -finishing thread FetcherThread16, activeThreads=33 -finishing thread FetcherThread17, activeThreads=32 -finishing thread FetcherThread18, activeThreads=31 -finishing thread FetcherThread19, activeThreads=30 -finishing thread FetcherThread20, activeThreads=29 -finishing thread FetcherThread21, activeThreads=28 -finishing thread FetcherThread22, activeThreads=27 -finishing thread FetcherThread23, activeThreads=26 -finishing thread FetcherThread24, activeThreads=25 -finishing thread FetcherThread25, activeThreads=24 -finishing thread FetcherThread26, activeThreads=23 -finishing thread FetcherThread27, activeThreads=22 -finishing thread FetcherThread28, activeThreads=21 -finishing thread FetcherThread29, activeThreads=20 -finishing thread FetcherThread30, activeThreads=19 -finishing thread FetcherThread31, activeThreads=18 -finishing thread FetcherThread32, activeThreads=17 -finishing thread FetcherThread33, activeThreads=16 -finishing thread FetcherThread34, activeThreads=15 -finishing thread FetcherThread35, activeThreads=14 -finishing thread FetcherThread36, activeThreads=13 -finishing thread FetcherThread37, activeThreads=12 -finishing thread FetcherThread38, activeThreads=11 -finishing thread FetcherThread39, activeThreads=10 -finishing thread FetcherThread40, activeThreads=9 -finishing thread FetcherThread41, activeThreads=8 -finishing thread FetcherThread42, activeThreads=7 -finishing thread FetcherThread43, activeThreads=6 -finishing thread FetcherThread44, activeThreads=5 -finishing thread FetcherThread45, activeThreads=4 -finishing thread FetcherThread46, activeThreads=3 -finishing thread FetcherThread47, activeThreads=2 -finishing thread FetcherThread1, activeThreads=1 -finishing thread FetcherThread48, activeThreads=0 -finishing thread FetcherThread49, activeThreads=0 0/0 spinwaiting/active, 0 pages, 0 errors, 0.0 0 pages/s, 0 0 kb/s, 0 URLs in 0 queues -activeThreads=0 Using queue mode : byHost Fetcher: threads: 50 QueueFeeder finished: total 1 records. Hit by time limit :0 fetching https://ofertas.mercadolibre.com.ar/ofertas-de-la-semana (queue crawl delay=5000ms) Fetcher: throughput threshold: -1 Fetcher: throughput threshold sequence: 5 -finishing thread FetcherThread23, activeThreads=48 -finishing thread FetcherThread22, activeThreads=47 -finishing thread FetcherThread21, activeThreads=46 -finishing thread FetcherThread20, activeThreads=45 -finishing thread FetcherThread19, activeThreads=44 -finishing thread FetcherThread18, activeThreads=43 -finishing thread FetcherThread17, activeThreads=42 -finishing thread FetcherThread16, activeThreads=41 -finishing thread FetcherThread15, activeThreads=40 -finishing thread FetcherThread14, activeThreads=39 -finishing thread FetcherThread13, activeThreads=38 -finishing thread FetcherThread12, activeThreads=37 -finishing thread FetcherThread11, activeThreads=36 -finishing thread FetcherThread10, activeThreads=35 -finishing thread FetcherThread9, activeThreads=34 -finishing thread FetcherThread8, activeThreads=33 -finishing thread FetcherThread7, activeThreads=32 -finishing thread FetcherThread6, activeThreads=31 -finishing thread FetcherThread5, activeThreads=30 -finishing thread FetcherThread4, activeThreads=29 -finishing thread FetcherThread3, activeThreads=28 -finishing thread FetcherThread2, activeThreads=27 -finishing thread FetcherThread1, activeThreads=26 -finishing thread FetcherThread30, activeThreads=25 -finishing thread FetcherThread47, activeThreads=24 -finishing thread FetcherThread46, activeThreads=23 -finishing thread FetcherThread45, activeThreads=22 -finishing thread FetcherThread44, activeThreads=21 -finishing thread FetcherThread43, activeThreads=20 -finishing thread FetcherThread42, activeThreads=19 -finishing thread FetcherThread41, activeThreads=18 -finishing thread FetcherThread40, activeThreads=17 -finishing thread FetcherThread39, activeThreads=16 -finishing thread FetcherThread38, activeThreads=15 -finishing thread FetcherThread37, activeThreads=14 -finishing thread FetcherThread36, activeThreads=13 -finishing thread FetcherThread35, activeThreads=12 -finishing thread FetcherThread34, activeThreads=11 -finishing thread FetcherThread33, activeThreads=10 -finishing thread FetcherThread32, activeThreads=9 -finishing thread FetcherThread31, activeThreads=8 -finishing thread FetcherThread48, activeThreads=7 -finishing thread FetcherThread49, activeThreads=7 -finishing thread FetcherThread29, activeThreads=6 -finishing thread FetcherThread28, activeThreads=5 -finishing thread FetcherThread27, activeThreads=4 -finishing thread FetcherThread26, activeThreads=3 -finishing thread FetcherThread25, activeThreads=2 -finishing thread FetcherThread24, activeThreads=1 -finishing thread FetcherThread0, activeThreads=0 0/0 spinwaiting/active, 1 pages, 0 errors, 0.2 0 pages/s, 492 492 kb/s, 0 URLs in 0 queues -activeThreads=0 FetcherJob: finished at 2018-10-08 10:16:49, time elapsed: 00:00:15 Parsing : /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch parse -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true -D mapred.skip.attempts.to.start.skipping=2 -D mapred.skip.map.max.skip.records=1 1539004586-18854 -crawlId inicio ParserJob: starting at 2018-10-08 10:16:51 ParserJob: resuming: false ParserJob: forced reparse: false ParserJob: batchId: 1539004586-18854 Parsing https://ofertas.mercadolibre.com.ar/ofertas-de-la-semana ParserJob: success ParserJob: finished at 2018-10-08 10:16:56, time elapsed: 00:00:05 CrawlDB update for inicio /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch updatedb -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true 1539004586-18854 -crawlId inicio DbUpdaterJob: starting at 2018-10-08 10:16:58 DbUpdaterJob: batchId: 1539004586-18854 DbUpdaterJob: finished at 2018-10-08 10:17:03, time elapsed: 00:00:04 Skipping indexing tasks: no SOLR url provided. lun oct 8 10:17:03 -03 2018 : Iteration 2 of 3 Generating batchId Generating a new fetchlist /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch generate -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true -topN 50000 -noNorm -noFilter -adddays 0 -crawlId inicio -batchId 1539004623-21385 GeneratorJob: starting at 2018-10-08 10:17:04 GeneratorJob: Selecting best-scoring urls due for fetch. GeneratorJob: starting GeneratorJob: filtering: false GeneratorJob: normalizing: false GeneratorJob: topN: 50000 GeneratorJob: finished at 2018-10-08 10:17:09, time elapsed: 00:00:04 GeneratorJob: generated batch id: 1539004623-21385 containing 99 URLs Fetching : /home/emi/apache-nutch-2.3.1/runtime/local/bin/nutch fetch -D mapred.reduce.tasks=2 -D mapred.child.java.opts=-Xmx1000m -D mapred.reduce.tasks.speculative.execution=false -D mapred.map.tasks.speculative.execution=false -D mapred.compress.map.output=true -D fetcher.timelimit.mins=180 1539004623-21385 -crawlId inicio -threads 50 FetcherJob: starting at 2018-10-08 10:17:11 FetcherJob: batchId: 1539004623-21385 FetcherJob: threads: 50 FetcherJob: parsing: false FetcherJob: resuming: false FetcherJob : timelimit set for : 1539015431036 Using queue mode : byHost Fetcher: threads: 50 QueueFeeder finished: total 15 records. Hit by time limit :0 fetching https://www.mercadolibre.com.ar/registration (queue crawl delay=5000ms) Fetcher: throughput threshold: -1 Fetcher: throughput threshold sequence: 5 fetching https://home.mercadolibre.com.ar/categories (queue crawl delay=5000ms) fetching https://instrumentos.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://camaras-digitales.mercadolibre.com.ar/camaras-accesorios/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://electronica.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://hogar.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://myaccount.mercadolibre.com.ar/purchases/list (queue crawl delay=5000ms) fetching https://telefonia.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 6 pages, 0 errors, 1.2 1 pages/s, 1312 1312 kb/s, 7 URLs in 1 queues 50/50 spinwaiting/active, 7 pages, 0 errors, 0.7 0 pages/s, 755 198 kb/s, 7 URLs in 1 queues fetching https://www.mercadolibre.com.ar/tiendas-oficiales (queue crawl delay=5000ms) 49/50 spinwaiting/active, 7 pages, 0 errors, 0.5 0 pages/s, 503 0 kb/s, 6 URLs in 1 queues 49/50 spinwaiting/active, 7 pages, 0 errors, 0.4 0 pages/s, 377 0 kb/s, 6 URLs in 1 queues 50/50 spinwaiting/active, 8 pages, 0 errors, 0.3 0 pages/s, 550 1242 kb/s, 6 URLs in 1 queues fetching https://www.mercadolibre.com.ar/gz/app (queue crawl delay=5000ms) 50/50 spinwaiting/active, 9 pages, 0 errors, 0.3 0 pages/s, 470 68 kb/s, 5 URLs in 1 queues fetching https://www.mercadolibre.com.ar/gz/home/navigation (queue crawl delay=5000ms) 49/50 spinwaiting/active, 9 pages, 0 errors, 0.3 0 pages/s, 403 0 kb/s, 4 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 1 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004668731 now = 1539004671093 0. https://www.mercadolibre.com.ar/mercadopuntos/descuentosexclusivos 1. https://www.mercadolibre.com.ar/gz/cart 2. https://www.mercadolibre.com.ar/ayuda 3. https://www.mercadolibre.com.ar/ 50/50 spinwaiting/active, 10 pages, 0 errors, 0.3 0 pages/s, 363 85 kb/s, 4 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 0 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004678058 now = 1539004676094 0. https://www.mercadolibre.com.ar/mercadopuntos/descuentosexclusivos 1. https://www.mercadolibre.com.ar/gz/cart 2. https://www.mercadolibre.com.ar/ayuda 3. https://www.mercadolibre.com.ar/ fetching https://www.mercadolibre.com.ar/mercadopuntos/descuentosexclusivos (queue crawl delay=5000ms) 50/50 spinwaiting/active, 11 pages, 0 errors, 0.2 0 pages/s, 323 0 kb/s, 3 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 0 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004684008 now = 1539004681094 0. https://www.mercadolibre.com.ar/gz/cart 1. https://www.mercadolibre.com.ar/ayuda 2. https://www.mercadolibre.com.ar/ fetching https://www.mercadolibre.com.ar/gz/cart (queue crawl delay=5000ms) 50/50 spinwaiting/active, 12 pages, 0 errors, 0.2 0 pages/s, 290 0 kb/s, 2 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 0 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004690271 now = 1539004686095 0. https://www.mercadolibre.com.ar/ayuda 1. https://www.mercadolibre.com.ar/ fetching https://www.mercadolibre.com.ar/ayuda (queue crawl delay=5000ms) 49/50 spinwaiting/active, 12 pages, 0 errors, 0.2 0 pages/s, 264 0 kb/s, 1 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 1 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004690271 now = 1539004691096 0. https://www.mercadolibre.com.ar/ 50/50 spinwaiting/active, 13 pages, 0 errors, 0.2 0 pages/s, 253 132 kb/s, 1 URLs in 1 queues * queue: https://www.mercadolibre.com.ar maxThreads = 1 inProgress = 0 crawlDelay = 5000 minCrawlDelay = 0 nextFetchTime = 1539004696563 now = 1539004696097 0. https://www.mercadolibre.com.ar/ fetching https://www.mercadolibre.com.ar/ (queue crawl delay=5000ms) -finishing thread FetcherThread29, activeThreads=49 -finishing thread FetcherThread31, activeThreads=48 -finishing thread FetcherThread41, activeThreads=47 -finishing thread FetcherThread34, activeThreads=46 -finishing thread FetcherThread37, activeThreads=45 -finishing thread FetcherThread40, activeThreads=44 -finishing thread FetcherThread38, activeThreads=43 -finishing thread FetcherThread21, activeThreads=42 -finishing thread FetcherThread25, activeThreads=41 -finishing thread FetcherThread20, activeThreads=40 -finishing thread FetcherThread33, activeThreads=39 -finishing thread FetcherThread27, activeThreads=38 -finishing thread FetcherThread26, activeThreads=37 -finishing thread FetcherThread30, activeThreads=36 -finishing thread FetcherThread35, activeThreads=35 -finishing thread FetcherThread22, activeThreads=34 -finishing thread FetcherThread36, activeThreads=33 -finishing thread FetcherThread19, activeThreads=32 -finishing thread FetcherThread18, activeThreads=31 -finishing thread FetcherThread17, activeThreads=30 -finishing thread FetcherThread39, activeThreads=29 -finishing thread FetcherThread44, activeThreads=28 -finishing thread FetcherThread24, activeThreads=27 -finishing thread FetcherThread28, activeThreads=26 -finishing thread FetcherThread23, activeThreads=25 -finishing thread FetcherThread42, activeThreads=24 -finishing thread FetcherThread32, activeThreads=23 -finishing thread FetcherThread15, activeThreads=22 -finishing thread FetcherThread16, activeThreads=21 -finishing thread FetcherThread0, activeThreads=20 -finishing thread FetcherThread2, activeThreads=19 -finishing thread FetcherThread7, activeThreads=18 -finishing thread FetcherThread4, activeThreads=17 -finishing thread FetcherThread6, activeThreads=16 -finishing thread FetcherThread8, activeThreads=15 -finishing thread FetcherThread13, activeThreads=14 -finishing thread FetcherThread49, activeThreads=13 -finishing thread FetcherThread11, activeThreads=12 -finishing thread FetcherThread12, activeThreads=11 -finishing thread FetcherThread10, activeThreads=10 -finishing thread FetcherThread5, activeThreads=9 -finishing thread FetcherThread3, activeThreads=8 -finishing thread FetcherThread14, activeThreads=7 -finishing thread FetcherThread9, activeThreads=6 -finishing thread FetcherThread48, activeThreads=5 -finishing thread FetcherThread43, activeThreads=4 -finishing thread FetcherThread47, activeThreads=3 -finishing thread FetcherThread1, activeThreads=2 -finishing thread FetcherThread46, activeThreads=1 -finishing thread FetcherThread45, activeThreads=0 0/0 spinwaiting/active, 14 pages, 0 errors, 0.2 0 pages/s, 271 482 kb/s, 0 URLs in 0 queues -activeThreads=0 Using queue mode : byHost Fetcher: threads: 50 fetching https://deportes.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) Fetcher: throughput threshold: -1 Fetcher: throughput threshold sequence: 5 fetching https://ropa.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://listado.mercadolibre.com.ar/_Tienda_hp-store_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://computacion.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) QueueFeeder finished: total 84 records. Hit by time limit :0 fetching https://vender.mercadolibre.com.ar/ (queue crawl delay=5000ms) fetching https://juegos-juguetes.mercadolibre.com.ar/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://www.mercadolibre.com/jms/mla/lgz/login?platform_id=ml&go=https%3A%2F%2Fofertas.mercadolibre.com.ar%2Fofertas-de-la-semana&loginType=explicit (queue crawl delay=5000ms) fetching https://www.mercadopago.com.ar/tarjeta-mercadopago?utm_medium=referral&utm_source=mercadolibre.com.ar&utm_campaign=cobranded_acquisition_banner_exhibitor (queue crawl delay=5000ms) fetching https://vehiculos.mercadolibre.com.ar/accesorios/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://analytics.mercadolibre.com.ar/ (queue crawl delay=5000ms) 49/50 spinwaiting/active, 7 pages, 0 errors, 1.4 1 pages/s, 1287 1287 kb/s, 74 URLs in 2 queues fetching https://listado.mercadolibre.com.ar/_Tienda_garden-life_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 50/50 spinwaiting/active, 9 pages, 0 errors, 0.9 0 pages/s, 885 482 kb/s, 73 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/corrientes/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 9 pages, 0 errors, 0.6 0 pages/s, 590 0 kb/s, 72 URLs in 1 queues 50/50 spinwaiting/active, 10 pages, 0 errors, 0.5 0 pages/s, 494 208 kb/s, 72 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_casa-del-audio_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 50/50 spinwaiting/active, 11 pages, 0 errors, 0.4 0 pages/s, 437 209 kb/s, 71 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_babynet_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 11 pages, 0 errors, 0.4 0 pages/s, 364 0 kb/s, 70 URLs in 1 queues 50/50 spinwaiting/active, 12 pages, 0 errors, 0.3 0 pages/s, 344 224 kb/s, 70 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_PriceRange_10000-0_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://listado.mercadolibre.com.ar/industrias-oficinas/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 12 pages, 0 errors, 0.3 0 pages/s, 301 0 kb/s, 68 URLs in 1 queues 50/50 spinwaiting/active, 13 pages, 0 errors, 0.3 0 pages/s, 292 221 kb/s, 68 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_prestigio_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 13 pages, 0 errors, 0.3 0 pages/s, 263 0 kb/s, 67 URLs in 1 queues 49/50 spinwaiting/active, 13 pages, 0 errors, 0.2 0 pages/s, 239 0 kb/s, 67 URLs in 1 queues 50/50 spinwaiting/active, 14 pages, 0 errors, 0.2 0 pages/s, 236 207 kb/s, 67 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_PriceRange_3000-10000_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) fetching https://listado.mercadolibre.com.ar/_Tienda_fravega_Deal_ofertas-de-la-semana_af_to (queue crawl delay=5000ms) 50/50 spinwaiting/active, 15 pages, 0 errors, 0.2 0 pages/s, 236 234 kb/s, 65 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/chaco/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 50/50 spinwaiting/active, 16 pages, 0 errors, 0.2 0 pages/s, 234 213 kb/s, 64 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_adidas-performance_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 16 pages, 0 errors, 0.2 0 pages/s, 219 0 kb/s, 63 URLs in 1 queues 50/50 spinwaiting/active, 17 pages, 0 errors, 0.2 0 pages/s, 218 211 kb/s, 63 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/la-rioja/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 50/50 spinwaiting/active, 18 pages, 0 errors, 0.2 0 pages/s, 218 213 kb/s, 62 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana_Discount_20-100 (queue crawl delay=5000ms) 49/50 spinwaiting/active, 18 pages, 0 errors, 0.2 0 pages/s, 206 0 kb/s, 61 URLs in 1 queues 50/50 spinwaiting/active, 19 pages, 0 errors, 0.2 0 pages/s, 221 488 kb/s, 61 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_adidas-originals_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 19 pages, 0 errors, 0.2 0 pages/s, 210 0 kb/s, 60 URLs in 1 queues 50/50 spinwaiting/active, 20 pages, 0 errors, 0.2 0 pages/s, 210 212 kb/s, 60 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_reflejar_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 20 pages, 0 errors, 0.2 0 pages/s, 200 0 kb/s, 59 URLs in 1 queues 50/50 spinwaiting/active, 21 pages, 0 errors, 0.2 0 pages/s, 201 206 kb/s, 59 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana_Installments_NoInterest (queue crawl delay=5000ms) fetching https://listado.mercadolibre.com.ar/neuquen/_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 50/50 spinwaiting/active, 22 pages, 0 errors, 0.2 0 pages/s, 201 213 kb/s, 57 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana_Discount_40-100 (queue crawl delay=5000ms) 49/50 spinwaiting/active, 22 pages, 0 errors, 0.2 0 pages/s, 193 0 kb/s, 56 URLs in 1 queues 50/50 spinwaiting/active, 23 pages, 0 errors, 0.2 0 pages/s, 197 291 kb/s, 56 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Tienda_educando_Deal_ofertas-de-la-semana (queue crawl delay=5000ms) 49/50 spinwaiting/active, 23 pages, 0 errors, 0.2 0 pages/s, 189 0 kb/s, 55 URLs in 1 queues 50/50 spinwaiting/active, 24 pages, 0 errors, 0.2 0 pages/s, 190 207 kb/s, 55 URLs in 1 queues fetching https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana_Discount_30-100 (queue crawl delay=5000ms) [...]
Este script requiere pasar como parámetros la ruta al archivo de semillas, un ID para la tarea, y la cantidad de rondas.
Para este ejemplo utilicé tres rondas, y luego de algo más de dos horas y media, recuperó 24.312 páginas:
emi@devuan:~/nutch$ mongo MongoDB shell version: 3.2.11 connecting to: test > show dbs admin 0.000GB local 0.000GB nutch 0.120GB > use nutch switched to db nutch > show collections inicio_webpage > db.inicio_webpage.count() 24312 > db.stats() { "db" : "nutch", "collections" : 1, "objects" : 24312, "avgObjSize" : 9015.767028627839, "dataSize" : 219191328, "storageSize" : 128016384, "numExtents" : 0, "indexes" : 1, "indexSize" : 1294336, "ok" : 1 }
Tal vez para comenzar sea más adecuado utilizar una o a lo sumo dos pasadas (1 o 2 como último parámetro al script).
Enviando una consulta a la base de datos Mongo es posible obtener todas las URLs recuperadas a un archivo de texto plano:
emi@devuan:~$ mongo localhost/nutch --eval "DBQuery.shellBatchSize=50; db.inicio_webpage.find({}, {'_id': 0, 'baseUrl': 1})" > cmdout.mongo emi@devuan:~$ head cmdout.mongo MongoDB shell version: 3.2.11 connecting to: localhost/nutch { "baseUrl" : "https://ofertas.mercadolibre.com.ar/ofertas-de-la-semana" } { } { "baseUrl" : "https://camaras-digitales.mercadolibre.com.ar/camaras-accesorios/_Deal_ofertas-de-la-semana" } { "baseUrl" : "https://computacion.mercadolibre.com.ar/_Deal_ofertas-de-la-semana" } { "baseUrl" : "https://deportes.mercadolibre.com.ar/_Deal_ofertas-de-la-semana" } { "baseUrl" : "https://juegos-juguetes.mercadolibre.com.ar/_Deal_ofertas-de-la-semana" } { "baseUrl" : "https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana" } { "baseUrl" : "https://listado.mercadolibre.com.ar/_Deal_ofertas-de-la-semana_Discount_10-100" }
Indexando con Elasticsearch
Ya sea habiendo realizado el proceso de crawling de forma manual, o utilizando el script bin/crawl
, será necesario indexar toda la información obtenida (contenido, metadatos, links, anchor text, etc.) en Elasticsearch.
Para ello simplemente ejecutar:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ bin/nutch index elasticsearch -all IndexingJob: starting Active IndexWriters : ElasticIndexWriter elastic.cluster : elastic prefix cluster elastic.host : hostname elastic.port : port (default 9300) elastic.index : elastic index command elastic.max.bulk.docs : elastic bulk index doc counts. (default 250) elastic.max.bulk.size : elastic bulk index length. (default 2500500 ~2.5MB) IndexingJob: done.
Sin embargo, en el log se encuentran los siguientes errores:
emi@devuan:~/apache-nutch-2.3.1/runtime/local$ tail logs/hadoop.log elastic.port : port (default 9300) elastic.index : elastic index command elastic.max.bulk.docs : elastic bulk index doc counts. (default 250) elastic.max.bulk.size : elastic bulk index length. (default 2500500 ~2.5MB) 2018-10-09 11:11:56,286 INFO elasticsearch.plugins - [Orator] loaded [], sites [] 2018-10-09 11:11:56,405 INFO client.transport - [Orator] failed to get node info for [#transport#-1][devuan][inet[localhost/127.0.0.1:9300]], disconnecting... org.elasticsearch.transport.NodeDisconnectedException: [][inet[localhost/127.0.0.1:9300]][cluster:monitor/nodes/info] disconnected 2018-10-09 11:11:56,405 INFO indexer.IndexingJob - IndexingJob: done.
Aquí es donde empiezan los problemas...
Existe un plugin desarrollado por un tercero que es compatible con Elasticsearch 5. Sin embargo estoy utilizando la versión 6.4, así que no me sirve. Por ende la mejor alternativa consiste en utilizar el plugin que utiliza la interfaz REST (puerto 9200
) en lugar del protocolo binario (puerto 9300
). Sin embargo, este plugin sólo está disponible para Apache Nutch 1.x. (no es posible compilarlo para la versión 2.x porque depende de la base de código de 1.x).
Las alternativas que quedan para indexar en Elasticsearch desde Apache Nutch son:
- Utilizar una versión anterior a Elasticsearch 2.3.3 con Apache Nutch 2.x.
- Utilizar el plugin REST "indexer-elastic-rest" con apache Nutch 1.x.
- Conectar directamente MongoDB con Elasticsearch utilizando
mongo-connector
.
"Tengo hasta ahí".

En próximos artículos veremos cómo sigue la cuestión.
Referencias
- Apache Nutch - Wikipedia
- Apache Lucene - Wikipedia
- Apache Hadoop
- Scraping the Web with Nutch for Elasticsearch
- Apache Nutch Wiki
- Nutch 2.X Tutorial - Nutch Wiki
- The Basics: Working with Nutch 2.x
- Web Crawling with Apache Nutch - Semantic Scholar
- Nutch Command Line Options of bin/nutch
- MongoDB CRUD Operations - Query Documents
- Configure the mongo Shell
- Project Fields to Return from Query
- Should I use Nutch 1x or 2x with Elasticsearch
- How to get Nutch 2.3.1 to work with ElasticSearch 5.3.2?
- ElasticSearch 5 Indexer for Apache Nutch 2.3 - GitHub
- apache/nutch: Apache Nutch - GitHub
- Nutch Wiki - AboutPlugins
- Indexing MongoDB with ElasticSearch
- Introducing Mongo Connector