AlfonzMx:

Extraer todos los enlaces de una página web es una tarea común entre los raspadores web, es útil para construir raspadores avanzados que rastrean cada página de un determinado sitio web para extraer datos, también se puede utilizar para el proceso de diagnóstico de SEO o incluso la fase de recopilación de información para la penetración. probadores. En este tutorial, aprenderá cómo puede crear una herramienta de extracción de enlaces en Python desde cero utilizando solo requests and BeautifulSoup libraries.

Instalemos las dependencias:

pip3 install requests bs4 colorama

Abra un nuevo archivo de Python y siga, importemos los módulos que necesitamos:

import requests
from urllib.parse import urlparse, urljoin
from bs4 import BeautifulSoup
import colorama

Vamos a usar colorama solo para usar diferentes colores al imprimir, para distinguir entre enlaces internos y externos:

# init the colorama module
colorama.init()
GREEN = colorama.Fore.GREEN
GRAY = colorama.Fore.LIGHTBLACK_EX
RESET = colorama.Fore.RESET

Necesitaremos dos variables globales, una para todos los enlaces internos del sitio web y la otra para todos los enlaces externos:

# initialize the set of links (unique links)
internal_urls = set()
external_urls = set()
  • Los enlaces internos son URL que enlazan con otras páginas del mismo sitio web.
  • Los enlaces externos son URL que enlazan con otros sitios web.

Dado que no todos los enlaces en las etiquetas de anclaje ( unos tags) son válidos (He experimentado con esto), algunos son enlaces a partes de la página web, algunos son javascript, así que vamos a escribir una función para validar las direcciones URL:

def is_valid(url):
    """
    Checks whether `url` is a valid URL.
    """
    parsed = urlparse(url)
    return bool(parsed.netloc) and bool(parsed.scheme)

Esto asegurará que exista un esquema adecuado (protocolo, por ejemplo, http o https ) y un nombre de dominio en la URL.

Ahora construyamos una función para devolver todas las URL válidas de una página web:

def get_all_website_links(url):
    """
    Returns all URLs that is found on `url` in which it belongs to the same website
    """
    # all URLs of `url`
    urls = set()
    # domain name of the URL without the protocol
    domain_name = urlparse(url).netloc
    soup = BeautifulSoup(requests.get(url).content, "html.parser")

Primero, inicialicé la variable de conjunto de URL , he usado conjuntos de Python aquí porque no queremos enlaces redundantes.

En segundo lugar, extraje el nombre de dominio de la URL, lo necesitaremos para verificar si el enlace que tomamos es externo o interno.

En tercer lugar, descargué el contenido HTML de la página web y lo envolví con un soupobjeto para facilitar el análisis de HTML.

Consigamos todas las etiquetas HTML a (etiquetas de anclaje que contienen todos los enlaces de la página web):

    for a_tag in soup.findAll("a"):
        href = a_tag.attrs.get("href")
        if href == "" or href is None:
            # href empty tag
            continue

Entonces obtenemos el atributo href y verificamos si hay algo allí. De lo contrario, simplemente continuamos con el siguiente enlace.

Como no todos los enlaces son absolutos, tendremos que unir las URL relativas con su nombre de dominio (por ejemplo, cuando href es "/ search" y url es "google.com" , el resultado será "google.com/search" ):

        # join the URL if it's relative (not absolute link)
        href = urljoin(url, href)

Ahora necesitamos eliminar los parámetros HTTP GET de las URL, ya que esto causará redundancia en el conjunto, el siguiente código maneja eso:

        parsed_href = urlparse(href)
        # remove URL GET parameters, URL fragments, etc.
        href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path

Terminemos la función:

        if not is_valid(href):
            # not a valid URL
            continue
        if href in internal_urls:
            # already in the set
            continue
        if domain_name not in href:
            # external link
            if href not in external_urls:
                print(f"{GRAY}[!] External link: {href}{RESET}")
                external_urls.add(href)
            continue
        print(f"{GREEN}[*] Internal link: {href}{RESET}")
        urls.add(href)
        internal_urls.add(href)
    return urls

Todo lo que hicimos aquí fue verificar:

  • Si la URL no es válida, continúe con el siguiente enlace.
  • Si la URL ya está en internal_urls , tampoco la necesitamos.
  • Si la URL es un enlace externo, imprímalo en color gris y agréguelo a nuestro conjunto global external_urls y continúe con el siguiente enlace.

Finalmente, después de todas las comprobaciones, la URL será un enlace interno, lo imprimimos y lo agregamos a nuestras urls y conjuntos internal_urls .

La función anterior solo tomará los enlaces de una página específica, ¿qué pasa si queremos extraer todos los enlaces de todo el sitio web? Hagámoslo:

# number of urls visited so far will be stored here
total_urls_visited = 0

def crawl(url, max_urls=50):
    """
    Crawls a web page and extracts all links.
    You'll find all links in `external_urls` and `internal_urls` global set variables.
    params:
        max_urls (int): number of max urls to crawl, default is 30.
    """
    global total_urls_visited
    total_urls_visited += 1
    links = get_all_website_links(url)
    for link in links:
        if total_urls_visited > max_urls:
            break
        crawl(link, max_urls=max_urls)

Esta función rastrea el sitio web, lo que significa que obtiene todos los enlaces de la primera página y luego se llama a sí mismo de forma recursiva para seguir todos los enlaces extraídos anteriormente. Sin embargo, esto puede causar algunos problemas, el programa se atascará en sitios web grandes (que tienen muchos enlaces) como google.com, como resultado, agregué un parámetro max_urls para salir cuando alcancemos una cierta cantidad de URL marcadas .

Muy bien, probemos esto, asegúrese de usarlo en un sitio web para el que esté autorizado; de lo contrario, no soy responsable de ningún daño que cause.

if __name__ == "__main__":
    crawl("https://www.thepythoncode.com")
    print("[+] Total External links:", len(external_urls))
    print("[+] Total Internal links:", len(internal_urls))
    print("[+] Total:", len(external_urls) + len(internal_urls))

Estoy probando en este sitio web. Sin embargo, le recomiendo encarecidamente que no lo haga, ya que esto generará muchas solicitudes y saturará el servidor web y puede bloquear su dirección IP.

Aquí hay una parte del resultado: 

Resultado de la ejecución del extractor de enlaces

Impresionante, ¿verdad? Espero que este tutorial sea un beneficio para que te inspire a construir tales herramientas usando Python.

Hay algunos sitios web que cargan la mayor parte de su contenido usando Javascript, como resultado, necesitamos usar la biblioteca request_html en su lugar, lo que nos permite ejecutar Javascript usando Chromium , ya escribí un script para eso agregando solo unas pocas líneas (como request_html es bastante similar a las solicitudes ), compruébalo aquí .

Solicitar el mismo sitio web muchas veces en un corto período de tiempo puede hacer que el sitio web bloquee su dirección IP, en ese caso, debe usar un servidor proxy para tales fines.

Si está interesado en capturar imágenes, consulte este tutorial: Cómo descargar todas las imágenes de una página web en Python , o si desea extraer tablas HTML, consulte este tutorial .

Edité el código un poco, por lo que podrá guardar las URL de salida en un archivo, verifique el código completo .

Happy Scraping ♥