Diccionario palabras Español en texto. script

En esta entrada voy a describir los problemas y las estrategias que he seguido para realizar el script que extrae las palabras de la RAE.

Pasos a seguir

  • Análisis de la fuente de datos
    • Iterar por elementos
    • Extraer información
  • Descarga de la primera página
  • Desarrollo de la araña

Para realizar todo esto vamos a desarrollar un script python, una vez que descarguemos la información de la web utilizaremos XPath para extraer de forma cómoda los datos.

Análisis de la fuente de datos

Iterar por elementos

Si visitamos la web https://dle.rae.es/. Vemos que no hay forma de sacar un listado de todas las palabras que comienzan por ‘a’. Si seleccionamos del desplegable ‘Comienzan por’ y escribimos ‘a’, solo nos aparece un número limitado de palabras. Podemos ver el resultado en la siguiente url: https://dle.rae.es/a?m=31

Parece que nos saca solo 35 palabras. Si afinamos y buscamos… ‘aa’, ‘ab’, ‘ac’, etc. ¿Podremos sacar listado completos? Parece que sí aunque tenemos muchas palabras que comienzan por la letra ‘a’ así que para sacar un listado menor a 35 palabras debemos expandir las letras por las que comienzan, por ejemplo ‘abab’ https://dle.rae.es/abab?m=31

Podríamos pensar en hacer la búsqueda por fuerza bruta y hacer todas las peticiones posibles:

  • aaaaa
  • aaaab
  • aaaac
  • aaaad
  • zzzzz

¿Cuántas peticiones son? Tendríamos un montón de peticiones erróneas.

El juego de caracteres en español son 27, hay que sumar la ü y las vocales acentuadas, si suponemos que necesitamos 5 caracteres para asegurarnos que la RAE no nos devuelve más de 30 resultados tenemos 33 elevado a 5 posibilidades: 39.135.393. Esto son 40 millones de peticiones.

Necesitamos una forma más elegante de hacer la navegación. Vamos a comenzar con las letras sencillas, vamos a intentar extraer las palabras que comienzan por esa letra y en el caso de tener más de 30 resultados (no tenemos el listado completo por que la RAE lo corta), vamos a expandir el conjunto de palabras a buscar pegando a esa búsqueda todas las posibilidades. El pseudocódigo quedaría así:

comienzo_a_buscar = 'a', 'á', 'b', 'c', etc...

mientras queden elemento en: comienzo_a_buscar
    hacer petición a RAE
    si hay más de 30 elementos
        añadir a comienzo_a_buscar la combinación con alafabeto
    si no
        extraer información

Si vemos la ejecución de esto con la web de la RAE:

# comienzo_a_buscar al inicio
['a', 'á', 'b', 'c', 'd', 'e', 'é', 'f', 'g', 'h', 'i', 'í', 
'j', 'k', 'l', 'm', 'n', 'ñ', 'o', 'ó', 'p', 'q', 'r', 's', 
't', 'u', 'ú', 'ü', 'v', 'w', 'x', 'y', 'z']
# La 'a' tiene más de 30 resultados con lo que se expande
EXAPEND: a
https://dle.rae.es/aa?m=31
['aá', 'ab', 'ac', 'ad', 'ae', 'aé', 'af', 'ag', 'ah', 'ai', 
'aí', 'aj', 'ak', 'al', 'am', 'an', 'añ', 'ao', 'aó', 'ap', 
'aq', 'ar', 'as', 'at', 'au', 'aú', 'aü', 'av', 'aw', 'ax', 
'ay', 'az', 'á', 'b', 'c', 'd', 'e', 'é', 'f', 'g', 'h', 
'i', 'í', 'j', 'k', 'l', 'm', 'n', 'ñ', 'o', 'ó', 'p', 'q', 
'r', 's', 't', 'u', 'ú', 'ü', 'v', 'w', 'x', 'y', 'z']
# Seguimos buscando consumiendo 'aa', 'aá' y en 'ab' tenemos más de 30... la expandimos
EXAPEND: ab
https://dle.rae.es/aba?m=31
['abá', 'abb', 'abc', 'abd', 'abe', 'abé', 'abf', 'abg', 
'abh', 'abi', 'abí', 'abj', 'abk', 'abl', 'abm', 'abn', 
'abñ', 'abo', 'abó', 'abp', 'abq', 'abr', 'abs', 'abt', 
'abu', 'abú', 'abü', 'abv', 'abw', 'abx', 'aby', 'abz', 
'ac', 'ad', 'ae', 'aé', 'af', 'ag', 'ah', 'ai', 'aí', 'aj', 
'ak', 'al', 'am', 'an', 'añ', 'ao', 'aó', 'ap', 'aq', 'ar', 
'as', 'at', 'au', 'aú', 'aü', 'av', 'aw', 'ax', 'ay', 'az', 
'á', 'b', 'c', 'd', 'e', 'é', 'f', 'g', 'h', 'i', 'í', 'j', 
'k', 'l', 'm', 'n', 'ñ', 'o', 'ó', 'p', 'q', 'r', 's', 't', 
'u', 'ú', 'ü', 'v', 'w', 'x', 'y', 'z']

Ahora ya tenemos la parte de cómo navegar por toda la información.

Extraer información

Si analizamos el HTML de la web con resultados

Podemos ver cómo hay un div con el identificador «resultado» y dentro de este una serie de elementos con la clase «n1». Dentro de la información de cada resultado voy a coger la información del título y voy a quitar la cadena «Ir a la entrada».

La razón para hacer esto es que no puedo coger el contenido ya que en distintos momentos la web muestra contenidos distintos:

Esos subtítulos hacen que el contenido no se pueda extraer de forma sencilla

Otro elemento a tener en cuenta es que existen palabra en femenino y masculino. Para tener todas en el listado si encontramos una comilla dentro de la palabra la combinamos

Descargando la primera página

Si desarrollamos un pequeño scrip python para hacer la descarga de la página vemos como el servidor de la REA no devuelve nada…

from urllib.request import Request, urlopen

req = Request("https://dle.rae.es/azuz?m=31")
webpage = urlopen(req)

# Da error de acceso prohibido:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/urllib/request.py", line 223, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.6/urllib/request.py", line 532, in open
    response = meth(req, response)
  File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib/python3.6/urllib/request.py", line 570, in error
    return self._call_chain(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.6/urllib/request.py", line 650, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden

¿Qué es lo que ocurre? De alguna manera el servidor de la RAE está detectando que no somos un navegador normal. Nosotros podemos imitar la petición de un navegador copiando sus cabeceras… Vamos a decir que somos un Firefox en Windows

from urllib.request import Request, urlopen

UA="Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"

req = Request("https://dle.rae.es/azuz?m=31", headers={'User-Agent': UA)
webpage = urlopen(req)

print(webpage.readlines())

Vemos cómo ahora mismo podemos acceder a la página

Desarrollo de la araña

Aquí podéis encontrar el código completo de la araña web ya desarrollada.

Hay un elemento «raro» dentro de este código y es que cuando hacemos una búsqueda de términos relacionados con ficheros web el servidor se comporta de forma extraña. El listado de términos a evitar se ha sacado a base de ejecutar el script varias veces y detectar qué términos son. Por eso el código siguiente:

if(palabra_start_with in ['app', 'docs', 'js']):
        continue

El script entero es el siguiente

#!/usr/bin/env python3

# Desarrollado por Jorge Dueñas Lerín

from urllib.parse   import quote
from urllib.request import Request, urlopen
from lxml import etree
import time


UA="Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
url="https://dle.rae.es/{}?m=31"

to_remove_from_title='Ir a la entrada '
"""
Usamos title por que el contenido en determinadas situaciones cambia:
<a data-cat="FETCH" data-acc="LISTA EMPIEZA POR" data-eti="abollado" title="Ir a la entrada abollado, abollada" href="/abollado">abollado<sup>1</sup>, da</a>
"""
skip = len(to_remove_from_title)

letras = ['a', 'á', 'b', 'c', 'd', 'e', 'é', 'f', 'g', 'h', 'i', 'í', 'j', 'k', 'l', 'm',
             'n', 'ñ', 'o', 'ó', 'p', 'q', 'r', 's', 't', 'u', 'ú', 'ü', 'v', 'w', 'x', 'y', 'z']

#Para comprobar la necesidad de title
#letras = ['abonado']

start_withs = letras.copy()

ftodas = open("palabras_todas.txt", "w")

while len(start_withs) != 0:
    palabra_start_with = start_withs.pop(0)

    if(palabra_start_with in ['app', 'docs', 'js']):
        continue

    req = Request(url.format(quote(palabra_start_with)), headers={'User-Agent': UA})
    print (req.full_url)
    print (start_withs)
    webpage = urlopen(req)
    htmlparser = etree.HTMLParser()
    tree = etree.parse(webpage, htmlparser)
    res = tree.xpath('//*[@id="resultados"]/*/div[@class="n1"]/a/@title')

    # Se repiten palabras. Cuando por ejemplo aba tiene más de 30 y se exapande
    # abaa, abab, etc... las primeras palabras no aparecen: aba
    for pal in res:
        pal_clean = pal[skip:]
        pal_clean = pal_clean.split(", ")
        for p in pal_clean:
            print(p)
            ftodas.write(p+'\n')

    if(len(res)>30):
        print("!" * 80)
        print("EXAPEND: " + palabra_start_with)
        expand = [palabra_start_with + l for l in letras]
        start_withs = expand + start_withs


ftodas.close()
exit()

Conclusión

Hemos visto cómo podemos programar arañas que imitan un navegador y cómo podemos navegar por la web. Ya casi tenemos todo listo para hacer google2 🙂

11 respuestas a “Diccionario palabras Español en texto. script”

  1. Gran trabajo! Gracias por compartirlo!

  2. Doctor:
    deberia de dejar el archivo de texto generado por aqui.
    para que no tengamos que consultar masivamente en la RAE todos los interesados.
    Sin embargo, le felicito por su codigo.

  3. Lo corri por 5 min y llego hasta la palabra administratorio… esto es masivo!

  4. Gran trabajo pero una duda me llama la atención.

    ¿Sería posible no incluir las declinaciones verbales en el listado sino simplemente los infinitivos?

  5. Buenas,
    Excelente script, me ha salvado el problema de las palabras para crear un crucigrama.
    Si alguien necesita sacar las definiciones, adjunto unas lineas para obtenerlas sin ejemplos ni exponentes (usados como enlaces a otras palabras)
    # Sin ejemplos
    for bad in tree.xpath(‘//*[@id=»resultados»]/*/p[@class=»j»]//span[@class=»h»]’): bad.getparent().remove(bad)

    #Sin exponentes
    for bad in tree.xpath(‘//*[@id=»resultados»]/*/p[@class=»j»]/*/sup’): bad.getparent().remove(bad)

    #Lista de definiciones de la palabra en un string
    defins = ».join(tree.xpath((‘//*[@id=»resultados»]/*/p[@class=»j»]//text()’)))

    # Separar cada definicion por la numerologia inicial y eliminar esta
    for defi in re.split(r’\d+\.\s’, defins)[1:]: definicion = «».join(defi).strip()

  6. Hola!
    Me interesa saber si se puede extraer cada palabra con al menos un significado… ¿lo has intentado? Estoy pensando en hacer una sopa de letras en python como proyecto de aprendizaje y seria genial nutrirse de un diccionario.

  7. Lo tengo en tareas pendientes… a ver si saco un hueco y lo hago en los próximos días.

  8. Buenas Jorge!

    Me parece un gran post y me he inspirado en el para hacer lo mismo con scrappy.

    Además también he añadido la recolección de definiciones, lo dejo por aquí:
    https://github.com/Alex44lel/RAE-Scraper

    1. jajajaja, buen trabajo.
      No sabía si etiquetar el comentario como spam… XP

  9. Muchas gracias por tu estupendo trabajo, me ha ayudado para agregar palabras al listado que tiene mi corrector ortográfico para médicos y otros listados.

Deja una respuesta