Rastrear y extraer datos de sitios web con Python: encontrar dominios expirados y enlaces rotos usando Scrapy

Publicado en:
Aprende a utilizar Python y Scrapy para el rastreo y la extracción de datos web con el fin de encontrar dominios expirados y enlaces rotos, enfocándote en aplicaciones de aprendizaje automático.

Introducción

Este tutorial trata sobre cómo utilizar Python y Scrapy para rastrear y extraer información de la web, centrándose en la búsqueda de dominios caducados y enlaces rotos. Esta es solo una de las múltiples capacidades de los rastreadores y extractores de datos. En aplicaciones de aprendizaje automático e investigación, es común rastrear enlaces y extraer contenido de sitios web, especialmente para análisis de contenido y algoritmos de descubrimiento de comunidades. Por ejemplo, los sitios web que están vinculados entre sí suelen estar relacionados y tienden a pertenecer a la misma comunidad en términos de contenido. Con el paquete Scrapy, que es gratuito y de código abierto en Python, el código de esta guía extrae contenido de una lista de sitios web, obtiene enlaces de estos sitios y los rastrea a su vez, mientras guarda los enlaces que generan errores. Posteriormente, analiza el tipo de error. El contenido a extraer y la cantidad de enlaces a rastrear aumentan de forma exponencial a medida que se rastrean más enlaces y dominios y se añaden a la cola, como se muestra conceptualmente a continuación: Lo siento, no puedo ver imágenes o el contenido dentro de ellas. Si me proporcionas el texto del párrafo, estaré encantado de ayudarte a reescribirlo en español. Este es el mismo método que utilizan los motores de búsqueda para indexar contenido en la web, mediante millones de instancias y miles de máquinas que están constantemente rastreando y extrayendo información de la red, conocidas como 'arañas'. La araña de Google es la más famosa de todas. Con la información que recolecta la araña, se pueden realizar diversas acciones. En el código a continuación, si un enlace arroja un error al ser rastreado, el código analiza el tipo de error. Pueden ser errores HTTP (como el 404), o también errores relacionados con la configuración incorrecta del servidor y errores de DNS o de servidores de nombres. De esta manera, se pueden identificar, por ejemplo, sitios web con enlaces rotos, lo cual podría indicar que ya no se mantienen activamente. También es posible encontrar dominios con enlaces de retroceso que están disponibles para registrar. Los resultados se guardan en un archivo y también se pueden almacenar en una base de datos. * Nota: este trabajo está en progreso. El código y la anotación de cada bloque se actualizan con frecuencia, ya que la base de código que utiliza técnicas de raspado aún está en desarrollo.

Rastreo, extracción de datos y aprendizaje profundo

La extracción de datos y contenido de sitios web, en el contexto del aprendizaje automático, es especialmente relevante en la investigación sobre clasificación de contenido. Por ejemplo, al aplicar técnicas de procesamiento de lenguaje natural con modelos preentrenados en inglés a millones de artículos extraídos de sitios web de editores en este idioma, se puede descubrir aspectos como la inclinación política y el 'sentimiento' del editor, así como qué tipo de contenido (noticias) ha sido tendencia a lo largo del tiempo.

Otro tema que se estudia con frecuencia es el perfil de backlinks de los sitios web. Como se mencionó antes, aquellos sitios que enlazan entre sí tienden a pertenecer a la misma comunidad. Los backlinks y los enlaces de salida se utilizan, por ejemplo, en algoritmos de aprendizaje profundo junto con el análisis de componentes principales para descubrir grupos de sitios web que atienden a la misma audiencia y tratan los mismos temas.

Bibliotecas necesarias

Primero importamos las bibliotecas necesarias. La biblioteca principal es Scrapy, un framework de scrapping en Python de código abierto y gratuito. Fue creada en 2008 y ha sido desarrollada de manera activa desde entonces.

Twisted se puede utilizar para identificar errores en la búsqueda de DNS, lo que ayuda a determinar si un dominio está probablemente disponible para registro. El paquete tldextract sirve para extraer el dominio de nivel superior (por ejemplo, '.com' en 'https://github.com/scrapy/scrapy').

Puedes instalar estas bibliotecas de la siguiente manera:

  • pip instalar scrapy
  • pip instalar twisteed
  • pip instalar tldextract

En Python, carga estas y otras bibliotecas necesarias de la siguiente manera:

from namecheap import Api
from twisted.internet.error import DNSLookupError
from scrapy.linkextractors import LinkExtractor
import CustomLinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.item import Item, Field
from scrapy.spidermiddlewares.httperror import HttpError
import os
import time
from urlparse import urlparse
import datetime
import sys
import tldextract
import shutil

Especifica el directorio donde se guardarán los resultados en un archivo:

try:
    shutil.rmtree("/home/ubuntu/crawls/crawler1/")
except Exception:
    sys.exc_clear()
os.makedirs("/home/ubuntu/crawls/crawler1/")

Indica qué información guardar al extraer contenido de la página web a la que hace referencia un enlace:

```{python}
class DmozItem(Item):
    domaincrawl = Field()
    current_url_id = Field()
    domain_id = Field()
    refer_domain = Field()

Especifica la lista de sitios web desde los que deseas comenzar a rastrear. Por ejemplo, puedes incluir una lista de 50 sitios aquí, y el rastreador extraerá información de estos sitios de manera asincrónica. Añade también los prefijos necesarios como 'http'. ```{python} 
with open('/home/ubuntu/crawler_sites.txt', 'r') as file:
    starturl = file.readline().strip()
extracted = tldextract.extract(starturl)
filename = "{}.{}".format(extracted.domain, extracted.suffix)
filename = filename[0:20]
if starturl[0:4] != "http":
    starturl = "http://"+starturl

Entiendo tu solicitud perfectamente y para poder ayudarte necesitaría que me proporciones el párrafo que deseas que reescriba en español. Por favor, incluye el texto para que pueda asistirte adecuadamente.

Especifica los parámetros globales que se actualizan a lo largo del proceso. Scrapy se encarga de resolver la mayoría de los desafíos complejos, como la gestión de memoria cuando la cantidad de enlaces a rastrear crece de manera exponencial, y el almacenamiento de enlaces encriptados en una base de datos para asegurar que los enlaces y las páginas se rastreen solo una vez. De este modo, puedes rastrear millones de enlaces incluso en un ordenador con poca memoria.

```{python}
identificador_url_actual = 0
umbral_impresion_url_actual = 50
dominio_disponible = 0
conteo_dominio = 0
umbral_tiempo = 120 ### en segundos
duplicados_procesados = {}
bloqueados = []
hora_inicio = datetime.datetime.now()

Guarda también el archivo de salida del shell del núcleo de Python, por si Scrapy encuentra un error en el camino.

f = open('/home/ubuntu/logs/crawler1/crawler1/lastfeed.txt', 'w')
f.write("FEED CRAWLER1")
f.close()

Entiendo que necesitas una reescritura en español del párrafo que has proporcionado en inglés, pero para ayudar correctamente, necesitaría conocer el contenido del párrafo original. Por favor, proporciona el texto del párrafo para que pueda ayudarte.

Ahora definimos la clase MySpider. Esta clase, junto con CrawlSpider, es fundamental en el framework de Scrapy. Es donde se establecen las reglas para el rastreador o 'araña'. Por ejemplo, es posible que desees limitarte a rastrear solo dominios .com. Por lo tanto, estás aplicando un filtro a los enlaces durante el proceso de rastreo, el cual la araña respeta.

Lo siento, no puedo acceder a contenido basado en imágenes ni proporcionar respuestas que dependan de referencias visuales. Por favor, proporciona el texto del párrafo para que pueda ayudarte a reescribirlo en español.

```{python}
class MySpider(CrawlSpider):
    name = 'crawler1'
    start_urls = [
        starturl,
    ]
    extraído = tldextract.extract(starturl)
    print(extraído)
    sufijo_extraído2 = extraído.suffix[-3:]

Ahora definimos las reglas. Solo queremos rastrear dominios .com, .net, .org, .edu y .gov. También queremos bloquear enlaces o dominios que contengan la palabra 'forum', para asegurarnos de que el rastreador no se atasque en foros con miles de hilos y publicaciones. Podemos añadir tantas reglas como deseemos en la tupla basándonos en palabras clave.

Si el sufijo extraído es "com", "net" o "org", se definirán las siguientes reglas: una regla utiliza un extractor de enlaces que permite dominios con extensiones como ".com", ".net", ".org", ".edu" y ".gov", pero niega aquellos que contengan la palabra 'forum'. Este extractor es único y tiene un método para manejar errores llamado 'add_errback', y sigue enlaces con un proceso específico para verificar duplicidades parciales llamado 'check_for_semi_dupe'.

Otra clase importante es la clase pipeline, que define cómo se procesa el contenido extraído (por ejemplo, podrías querer conservar solo los enlaces y encabezados del contenido que se ha recopilado). Abordaremos esta clase más adelante.

Como parte de la clase MySpider, verificamos los enlaces duplicados para asegurarnos de que no se vuelvan a rastrear.


    def check_for_semi_dupe(self, links): 
        for link in links: 
            extracted = tldextract.extract(link.url) 
            just_domain = "{}.{}".format(extracted.domain, extracted.suffix) 
            url_indexed = 0 
            if just_domain not in processed_dupes: 
                processed_dupes[just_domain] = datetime.datetime.now() 
            else: 
                url_indexed = 1 
                timediff_in_sec = int((datetime.datetime.now() - processed_dupes[just_domain]).total_seconds()) 
            if just_domain in blocked: 
                continue 
            elif url_indexed == 1 and timediff_in_sec  time_treshold: 
                blocked.append(just_domain) 
                continue 
            else: 
                yield link 

Una vez que procesamos la respuesta obtenida al rastrear un enlace que cumple con los filtros mencionados, si el enlace devuelve una respuesta HTTP válida (200), se seguirá el enlace para rastrear el contenido siguiente y extraer los enlaces de este, a los que también se rastreará. Los dominios se guardan en un archivo .csv, el cual puede ser utilizado más adelante, por ejemplo, en nuevas instancias de rastreo y escrapeo, para asegurarse de que no se vuelven a rastrear los mismos dominios.

Nota: hay algunas variables globales mal implementadas en la función siguiente; estas pueden reescribirse de una manera más acorde con las prácticas de Python.

Tengo el siguiente párrafo:

    def parse_obj(self, response):
        download_size = len(response.body)
        global current_url_idcount
        current_url_idcount = current_url_idcount + 1
        global current_url_printtreshold
        if current_url_idcount == current_url_printtreshold:
            try:
                global domain_avail
                domain_avail = sum(1 for line in open(
                    # nota que la primera fila también se cuenta, la cual contiene los encabezados
                    "/home/ubuntu/scrapy/output/%s.csv" %filename )) - 1  
            except Exception:
                sys.exc_clear()
            global domain_count
            referring_url = response.request.headers.get('Referer', None)

La salida se imprime a continuación en el archivo de salida. Esto es para propósitos de depuración, podrías querer comentar esta línea.

            with open('/home/ubuntu/logs/crawler1/crawler1/lastfeed.txt', 'a') as outfile:
                print  outfile, "pcrawl: " + str(current_url_idcount) + " dcheck: " + str(domain_count) + " davail: " + str(domain_avail) + " pps: " + str(p_per_sec) + " dlsize: " + str(download_size) + " refurl: " + referring_url
            print "pcrawl: " + str(current_url_idcount) + " dcheck: " + str(domain_count) + " davail: " + str(domain_avail) + " pps: " + str(p_per_sec) + " dlsize: " + str(download_size) + " refurl: " + referring_url
            global current_url_printtreshold
            current_url_printtreshold = current_url_idcount + 50

Ahora analizamos los errores de rastreo que encontramos. La mayoría de las aplicaciones de rastreo solo se interesan por los enlaces que funcionan y proporcionan contenido, pero los enlaces no rastreables también son significativos. Si un sitio web enlaza a otro que no funciona, o a una página inexistente en un sitio, esto puede deberse a diversas razones. Por ejemplo, el sitio web podría no estar siendo mantenido activamente. También podría ser que la página a la que se hace referencia haya sido eliminada (lo que podría indicar contenido controvertido), o que el dominio objetivo ya no esté registrado y esté disponible para cualquiera interesado en ese nombre de dominio. Estas situaciones pueden ser de interés para la persona que utiliza el rastreador y el scraper. Guardamos la razón del error en un archivo, y tratamos específicamente de identificar si el nombre de dominio está disponible para registro, inferiendo si el error es 404 (No disponible) o algún tipo de error de DNS.

    def add_errback(self, request):
        return request.replace(errback=self.errback_httpbin)
def errback_httpbin(self, failure):
    self.logger.error(repr(failure))
    global current_url_idcount
    current_url_idcount += 1
    global current_url_printtreshold
    try:
        global domain_avail
        domain_avail = sum(1 for line in open("/home/ubuntu/scrapy/output/%s.csv" % filename)) - 1  # ten en cuenta que la primera fila, que contiene los encabezados, también se cuenta
    except Exception:
        sys.exc_clear()
    if current_url_idcount == current_url_printtreshold:
        global domain_count
        with open('/home/ubuntu/logs/crawler1/crawler1/lastfeed.txt', 'a') as outfile:
            print  outfile, "pcrawl: " + str(current_url_idcount) + " dcheck: " + str(domain_count) + " davail: " + str(domain_avail)
        print "pcrawl: " + str(current_url_idcount) + " dcheck: " + str(domain_count) + " davail: " + str(domain_avail)
        global current_url_printtreshold
        current_url_printtreshold = current_url_idcount + 50
    if failure.check(HttpError):
        response = failure.value.response
        response2 = str(response)
        response3 = response2[:4]

Si el error es un 503, está relacionado con la configuración del Sistema de Nombres de Dominio (DNS) o los servidores de nombres del dominio. Esto probablemente indica que el dominio ya no está registrado; por ejemplo, puede haber expirado. También es posible que el DNS no se haya configurado o que se haya configurado incorrectamente. Guardamos esta información junto con la URL de referencia.

                if response3 == 
                    global domain_count
                    domain_count += 1
                    extracted = tldextract.extract(response.url)
                    newstr4 = "{}.{}".format(extracted.domain, extracted.suffix)
                    referring_url = response.request.headers.get('Referer', None)
                    item = DmozItem(domaincrawl=newstr4, current_url_id=current_url_idcount, domain_id=domain_count, refer_domain=referring_url)

El chequeo cruzado a continuación verifica el error específico de DNS, pero esto no es completamente infalible. Por eso se le asigna un valor de 0. Como se menciona en la conclusión, una forma más segura es integrar una API externa en el código siguiente. Los elementos devueltos y generados son procesados por el pipeline de Scrapy, lo cual trataremos en la próxima sección.

                    CROSS_CHECK_DNS = 0
                    Si CROSS_CHECK_DNS == 1:
                        reglas = (
                            Regla(LinkExtractor(),
                                  process_request='añadir_errback'),
                        )
                        def añadir_errback(self, request):
                            return request.replace(errback=self.errback_httpbin)
                        def errback_httpbin(self, failure):
                            self.logger.error(repr(failure))
                            if failure.check(DNSLookupError):
                                request = failure.request
                                self.logger.error('Error de búsqueda de DNS en %s', request.url)
                                item = DmozItem(current_url = request.url)
                                print item
                                print "PRUEBA" + "item"
                                return item
                    yield item

Conclusión

El código presentado en este tutorial es una introducción a la técnica de rastrear y extraer datos de la web utilizando Python. Se centra principalmente en identificar dominios expirados y enlaces rotos. Empleando la biblioteca Scrapy, comienza a partir de una lista específica de dominios, los examina, almacena los enlaces encontrados y los explora y analiza sucesivamente. Para aquellos interesados en utilizar Python para el análisis de datos, el artículo Analizando datos usando Polars en Python: una introducción podría ofrecer valiosos consejos sobre cómo manejar los datos recopilados a partir del web scraping. Este proceso continúa hasta que el rastreador se detiene manualmente (o, teóricamente, hasta que ya no queden enlaces por rastrear en la web o si, en términos de red, el 'componente principal' ha sido completamente recorrido). Almacena la información de cada enlace en una base de datos.

En esta configuración, el rastreador se centra en los enlaces que devuelven un error relacionado con DNS al ser rastreados. En la mayoría de los casos, esto indica que el dominio al que se refiere el enlace está disponible para registro, pero no siempre. Como se mencionó anteriormente, también podría señalar una mala configuración del DNS o de los servidores de nombres. Para descartar esta última posibilidad, se puede integrar una API de un registrador de dominios, por ejemplo, para verificar el estado del dominio. Esto se puede añadir en el código anterior dentro de la función de errback.

El código se puede mejorar para diversos fines. Podrías querer rastrear solo ciertos tipos de enlaces (definidos en los parámetros de Reglas), ciertos dominios con extensiones específicas, o enlaces que aparezcan en contextos determinados. También podrías querer guardar y procesar el contenido que ha sido raspado. Scrapy tiene otro componente para eso, llamado 'pipeline', que forma parte de la infraestructura de 'middleware' de Scrapy. Por ejemplo, podrías querer aplicar procesamiento de lenguaje natural al contenido para extraer entidades gramaticales o descubrir comunidades o significados. Además, podrías querer guardar todos los enlaces rastreados para realizar algún tipo de análisis de redes. Estos son algunos de los temas clave en el aprendizaje automático.


Compartir

Comentarios (0)

Publicar un comentario

© 2023 - 2024 — TensorScience. Todos los derechos reservados.