Arqueología de software: Spider Bot Language
En un artículo anterior vimos como podemos descargarnos un sitio Web para navegarlo offline con Wget. Creando ese artículo recordé un software que solía utilizar hace ya muchos años atrás [> 10], estoy hablando de BlackWidow.
En este artículo vamos a ver si con BlackWidow aún es posible descargar sitios Webs para navegarlos offline. Pero, en realidad BlackWidow es solo es una excusa para poder hablar de un particular y muy poco conocido lenguaje de programación: Spider Bot Language (SBL).
Tabla de contenidos
Descargar sitios web para navegarlos offline
Esto era una práctica muy común a principio del siglo (el siglo XXI está envejeciendo rápidamente).
Una de las herramientas más utilizadas para descargar sitios web enteros en Windows era BlackWidow.
En la actualidad BlackWidow sigue vigente, aunque no sabemos si aún lo siguen desarrollando activamente; lo cierto es que se puede descargar y comprar [$39,5 dolaritos] desde la página Web de la empresa padre y madre de esa criatura.
BlackWidow incorpora un browser embebido a través del cual vas poder:
- Abrir un sitio web para navegarlo y descargarlo.
- Ver el código fuente de la página.
- Sacar una captura de pantalla de la página.
- Crear una imagen miniatura del sitio.
- Modificar la velocidad de descarga para evitar ser "detectados".
- Ver el sitio web como una estructura de directorios.
- Exportar los datos descargados.
Cómo utilizar BlackWidow para descargar un sitio Web
Vamos a usar el trial que podes descargar desde su página oficial: http://softbytelabs.com/wp/blackwidow/.
Una vez instalado, vamos a proceder a experimentar con nuestro propio sitio: https://tecnolocuras.com.
La UI tiene sin duda un look and feel Windows XP. No está mal. Lo que si notamos es que el browser integrado es una versión embebida de Internet Explorer, ya obsoleto para renderizar correctamente el sitio, como lo atestigua la siguiente captura:
Presionando el botón [Filters] se abre la pantalla desde donde podemos gestionar nuestros filtros. Los filtros se aplican a nivel URL y no de contenidos, esto último nos habilitaría a utilizar, por ejemplo: xpath o css selectors, pero no es el caso ¿Quizás un sintoma de que el software no está del todo acompasado con las tendencias actuales? Los filtros soportan Wildcard y expresiones regulares.
Para comenzar a descargar el sitio Web que ingresamos en el browser embebido, simplemente presionamos el botón [Scanner]. Dependiendo el tamaño del sitio demorara más o menos tiempo en finalizar la descarga. En el caso de https://tecnolocuras.com, como somos nuevitos y casi no tenemos artículos (al menos al momento de escribir esto en Mayo 2021), descargó todo el sitio en menos de 30 segundos.
El sitio descargado lo muestra como un directorio, respetando la jerarquía establecida en las URL.
BlackWidow nos permite exportar todas las URL del sitio Web descargado en los siguiente formatos:
- Texto plano (formato txt).
- Texto plano delimitado por tab.
- Exportar las imágenes en archivos html.
- Links HTML.
- XML.
Podes guardar el proyecto presionando [Save]. El proyecto lo guarda en un formato propietario con la extensión .bw6. Este archivo contiene el detalle de las URL escaneadas, el nivel de profundidad y otras opciones más, como se puede apreciar a continuación:
[BlackWidow v6.00 filters]
URL = https://tecnolocuras.com/
[ ] Expert mode
[x] Scan everything
[ ] Scan whole site
Local depth: 0
[ ] Scan external links
[ ] Only verify external links
External depth: 0
Default index page: index.html
Browser user agent: Mozilla/4.0 (compatible; MSIE 7.0; BlackWidow v6 - http://SoftByteLabs.com)
Startup referrer:
[ ] Slow down by 10:60 seconds
6 threads
[end]
El sitio descargado lo podes encontrar en tu directorio de descargas y es completamente funcional y navegable.
La utilidad Link Errors es muy útil para detectar, por ejemplo, URL que retornen 404.
El lenguaje de programación Spider Bot Language
El objetivo de mostrarte BlackWidow era experimentar con el lenguaje de programación orientado a objeto llamado Spider Bot Language (SBL) (aparentemente anunciado en el año 2007) .
Viendo la página oficial (http://softbytelabs.com/wp/brownrecluse/) vemos que el lenguaje viene incorporada en un producto diferente a BlackWidow. Este producto se llama BrownRecluse.
Ni lentos ni perezosos procedemos con la descarga de BrownRecluse. Antes de probarlo, y a juzgar por la descripción que del producto hacen en la página oficial, estamos ante un lenguaje interpretado con sintaxis similar a Pascal, VB Script y JS. [¡Linda mezcla eh!]
Este lenguaje es utilizado para hacer Web scraping antes de que el scraping fuera popular y aparecieran 20 mil plataformas que te ofrecen hacerlo. Últimamente a estos productos le han integrado Inteligencia Artificial, ya sea porque está de moda o porque efectivamente "aporta valor" para faciltiar la tarea del scrapeador.
Ni bien abrimos BrownRecluse nos damos cuenta que estamos frente a un entorno de desarrollo (IDE).
El estilo se nos hace parecido a VBScript Editor (del cual ya hablaremos en otra ocasión).
Con una UI orientada a paneles, permite agregar, mover y redimensionar las ventanas, lo que facilita personalizar la experiencia de uso del IDE.
Una grata sorpresa fue ver que el IDE tiene soporte para (un muy rustico) autocompletado, como podemos apreciar en el siguiente GIF:
Según lo que encontramos en la documentación del lenguaje (disponible en el venerable formato chm), nos dicen que:
|El lenguaje fue diseñado para programas de araña de Internet. Esto significa que no necesita preocuparse por hacer funciones para acceder a Internet, extraer páginas web o archivos, etc. El lenguaje ya incluye todo lo que necesitas. Por ejemplo, si deseas extraer una página web y mostrar su código fuente en la ventana de salida, sólo necesitas 4 sentencias. Una para crear un objeto URL, otra para asignar la URL real a extraer, otra para obtener los datos, y finalmente, otra para mostrar los datos.|
Y nos muestran el siguiente código:
Link = New(URL);
Link.Location = 'http://SoftByteLabs.com';
Link.Get;
Output(Link.Data);
El lenguaje dice ser orientado a objetos. Por defecto incorpora una serie de objetos prestablecidos cuyo propósito es facilitar la tarea de la manipulación del contenido descargado de una página Web. Uno de estos objetos es el Submit.
El objeto Submit permite manejar el envío de datos de formularios. Es muy útil cuando se envía información a través de POST, como cuando se hace clic en un botón de envío de un formulario de una página web.
Vamos a probar lo siguiente: enviar una consulta al formulario de tecnolocuras.com.
Primero creamos la instancia de los objetos URL y Submit respectivamente:
Link = New(URL);
Form = New(Submit);
Luego procedemos a establecer la URL, obtener el contenido de la misma, seleccionar el primer formulario y mostrar el dato en pantalla:
Link.Get('https://tencolocuras.com'); Form.Data = Link.Data; Form.SelectFirst; Output(Form.FormData);
El output del script es el HTML del formulario de esta página:
<input id="search_text" type="text" class="search_text_with_icon textbox" name="q" maxLength=512 size=50 tabindex="1" onchange="searchTextOnChange('search_text', 'search_icon', 'search_clear');" onkeypress="this.onchange();" onpaste="this.onchange();" oninput="this.onchange();"> <input id="search_icon" align="center" type="submit" class="search_icon" value=" " tabindex="2"> <div id="search_clear" class="search_clear" tabindex="3" onclick="resetSearchText('search_text');"> <div class="search_clear_line"></div> </div>
Ahora, el script completo:
Link = New(URL);
Form = New(Submit);
//Hacemos un get de la URL y mostramos su contenido
Link.Get('https://tecnolocuras.com');
Form.Data = Link.Data;
Display("Form data:" + Form.Data);
//Obtenermos el primer formulario
Form.SelectFirst;
//Seteamos el valor del elemento input "q"
Form.SetValue("q","Cmder");
/*Mostramos el valor de algunas propiedades del objeto Submit*/
Display("Nombre del form:" + Form.FormName);
Display("Valor del input: " + Form.GetValue("q"));
Display("Post data: " + Form.PostData);
Display("Form action:" + Form.Action);
Display("Total de forms:" + Form.FormsCount);
Display("Método del form:" + Form.Method);
//Hace el POST del form con el dato seteado
Link.Post(Form.PostData);
En el siguiente ejemplo vamos a ver como podemos obtener el título de una página. Para eso utilizamos el objeto Parser que permite dividir una página web en "partes" predefinidas (aka: tags), por ejemplo: el título, el head, script, body, entre otros. Esto nos ahorra tener que crear expresiones regulares para obtener el contenido de los tags más comunes, como ser, el título de una página Web.
También SBL nos permite definir que User Agent queremos utilizar. Para eso, simplemente hay que asignar el String
del User Agent a la variable especial UserAgent.
Link = New(URL);
Page = New(Parser);
//Samsung Galaxy S8
UserAgent = 'Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Mobile Safari/537.36';
Link.Get('https://tecnolocuras.com');
Page.Parse(Link.Data);
Display(Page.Title);
Este otro script nos permite listar todos los links que hay en un sitio Web:
Output.Clear;
WebPage = "https://tecnolocuras.com";
Link = New(URL);
if not Link.Get(WebPage) then begin
//La propiedad ErrorText contiene la descripcipon del error.
Display('Error', Link.ErrorText, IconStop);
//Detiene la ejecución del script
Terminate;
end;
if Link.ServerCode >= 400 then begin
Display('Error', Link.ServerText, IconStop);
Terminate;
end;
Page = New(Parser);
Page.Parse(Link.Data);
TagsList = Page.Tags('a', 'href');
//Itera sobre todo los tags que conincide con el críterio ingresado
for each TagsList as Tag do
//FixUp convierte links relativos a absolutos
Output(Link.Fixup(Tag));
//Ordena la salida en orden ascendente
Output.Sort;
SBL también soporta funciones. Veamos un ejemplo un poco más avanzado que los anteriores, en donde se definen funciones que son asignadas a los callbacks que ofrece el objeto Scanner:
Output.Clear;
WebSite = "https://tecnolocuras.com";
if WebSite = nothing then Terminate;
DecodeURL(WebSite, [BaseHost], [HostName]);
Scan = New(Scanner);
function CheckLink();
begin
DecodeURL(@Scan.Location, [Host], [HostName]);
result = (Host = @BaseHost);
end;
function CheckMime();
begin
Output(@Scan.Location);
result = (@Scan.MimeType ~= 'text/html');
end;
function CheckData();
begin
if @Scan.MimeType ~= 'text/html' then begin
Page = New(Parser);
Page.Parse(@Scan.Data);
HrefTags = Page.Tags('a','href');
for each HrefTags as Tag do
@Scan.AddLink(Tag);
ImgTags = Page.Tags('img','src');
for each ImgTags as Tag do
@Scan.AddLink(Tag);
end;
end;
Scan.Location = WebSite;
Scan.OnValidateLink = CheckLink;
Scan.OnBeforeDownload = CheckMime;
Scan.OnAfterDownload = CheckData;
Scan.Start;
Elegante solución.
En el script anterior asignamos la función que verifica el tipo del link al callback OnValidateLink
del objeto Scanner. Antes de comenzar la descarga verificamos el MimeType de los vínculos y, por último, asignamos la función CheckData
al callback OnAfterDownload
.
El @
que vemos en el código es para hacer referencia a las variables globales que se encuentran fuera del alcance de las funciones.
Las funciones puede ser asignadas a una variable.
Las variables declaradas dentro de una función puede ser accedidas y seteadas su valor desde fuera de la función a través del .
como se muestra en el siguiente ejemplo:
function Persona(e, n, g);
begin
edad = e;
nombre = n;
genero = g;
end;
Persona(66, "Fermat", "M");
Display("Nombre: " + Persona.nombre);
Display("Edad: " + Persona.edad);
Display("Genero: " + Persona.genero);
Persona.nombre = "Evariste";
Persona.edad = "21";
Display("Nombre: " + Persona.nombre);
Display("Edad: " + Persona.edad);
Las funciones de SBL permite una pseudo "orientación a objetos" o, al menos, una primitiva forma de encapsulación.
Por ultimo mencionaremos una de las características más sobresalientes, sino la mejor, del lenguaje Spider Bot Language, estamos hablando del soporte de hilos (Threads). Sí señoras y señores: ¡SBL soporta threads! algo que a muchos lenguajes de programación le gustaría tener [esto es una indirecta para vos Autoit]
BusyCount = 0;
using 4 threads do begin
SomethingToDo = 3; // not shared among threads.
repeat
if SomethingToDo then begin
@BusyCount++; // increment our busy flag as we are now busy.
// Do something here...
Output(SomethingToDo);
SomethingToDo--;
@BusyCount--; // decrement our busy flag as we are no longer busy.
end else
Pause(100); // nothing to do, so lets pause for 100 ms.
// If BusyCount is zero, no other threads have something to do,
// and if this thread has nothing to do as well, we can exit.
until (@BusyCount = 0) and (IsFalse SomethingToDo);
end;
SBL es un grato descubrimiento pero me temo que llegó tarde a mi arsenal de herramientas para hacer scraping. En la actualidad, prácticamente todo lenguaje de programación tiene una o más librería que permiten hacer lo mismo que SBL con la ventaja que, muchas de ellas, son gratis, multiplataforma y con una gran comunidad detrás, algo que SBL parece no haber tenido nunca.
Por otro lado, la ejecución de los scripts SBL parace tienen una fuerte dependencia con el IDE, al menos nosotros no encontramos la manera de ejecutarlo de forma independiente a BrownRecluse, lo que impediría, por ejemplo, hacer algún mecanismo automatizado que no requiera de la UI para ser ejecutado.
Independientemente del comentario anterior, SBL es un lenguaje muy interesante, destinado a ser utilizado para un objetivo concreto; podríamos decir que es un DSL.
Hace poco tiempo recibí un correo de Federico Tomassetti, especializado en el desarrollo de lenguajes específicos de dominio, en donde hace la siguiente reflexión:
"I have been reflecting on the concept of Language Lever. What is the Language Lever? Is the ability to get more out of our skills by using better Languages. How can this be translated in practice? In three ways: 1) By adopting tools that makes us use a language better. For example, better editors that guide us when we use the language, pointing out mistakes and providing suggestions 2) By migrating to languages more suitable for us. And while doing so bringing the knowledge we have with us. We can achieve that through language migrations 3) By creating languages more suitable for us. For most organization building better "generic" languages is out of reach, but they could obtain great ROI by building languages that are better for the specific things they do. Those are Domain Specific Languages."
Yo creo que SBL cubre (o cubrió en su momento) las características que menciona Tomasseti.
A modo de resumen
Este artículo inaugura una serie que podríamos llamar "Arqueología de Software", cuyo objetivo es hacer revisiones de herramientas "antiguas" (es decir, de hace 10 años para atrás), para estudiar su vigencia, las personas involucradas, si siguen estando activas y, porque no, revalorizarlas, redescubrirlas o utilizaras como fuente de inspiración para nuevos proyectos ["Sobre hombros de gigantes"].
¿Conocías SBL? ¿Tenes algún caso parecido de un lenguaje de programación raro o poco conocido que hayas utilizado? Nos gustaría conocer tu experiencia. Déjanos tus comentarios 👇
¡Nos vemos! Peace. ✌
/ Súmate al boletín. No es gran cosa, pero es gratis 👇 /