Descargar favicons de múltiples webs utilizando Python
Los favicons son pequeños iconos asociados a un sitio web que normalmente aparecen en la pestaña del navegador, en los marcadores, en el historial o junto a la dirección de una página.
Aunque aparentemente se trata de un elemento muy sencillo, un favicon forma parte de la identidad visual de una web y puede resultar especialmente útil cuando estamos desarrollando herramientas relacionadas con navegadores, gestores de sitios web, dashboards, aplicaciones de escritorio o sistemas de monitorización.
En este artículo vamos a crear una pequeña aplicación de escritorio utilizando Python capaz de consultar una lista de sitios web, descargar sus favicons, mostrarlos visualmente en una interfaz gráfica y registrar cuánto tiempo ha tardado en obtenerse cada uno.
Además, incorporaremos varias funcionalidades interesantes:
- Descarga automática de favicons.
- Visualización de los iconos mediante una interfaz gráfica.
- Barra de progreso.
- Medición del tiempo de descarga de cada favicon.
- Posibilidad de pausar el proceso.
- Posibilidad de reanudarlo.
- Posibilidad de detener el scraping.
- Conversión de los iconos a escala de grises.
- Exportación de los tiempos de descarga a JSON.
- Guardado de los favicons en archivos PNG.
¿Qué es exactamente un favicon?
Un favicon es una pequeña imagen que identifica visualmente a un sitio web.
Tradicionalmente se utilizaba un archivo llamado favicon.ico, aunque actualmente los sitios modernos pueden utilizar diferentes formatos y tamaños, incluyendo PNG, SVG e incluso diferentes variantes adaptadas a distintos dispositivos.
Un sitio web puede declarar su favicon mediante una etiqueta HTML como esta:
<link rel="icon" href="/favicon.ico">
Sin embargo, localizar correctamente el favicon de cualquier página puede ser algo más complejo de lo que parece. El icono puede estar en diferentes rutas, utilizar otro formato o incluso ser generado mediante mecanismos específicos del sitio.
Para simplificar nuestro ejemplo utilizaremos un servicio externo que nos proporcionará el favicon asociado a un dominio.
¿Qué vamos a construir?
La aplicación tendrá una interfaz gráfica desde la que podremos iniciar un proceso de descarga sobre una lista de sitios web.
Mientras el programa trabaja podremos visualizar:
- El progreso general del proceso.
- Los favicons descargados.
- La URL correspondiente a cada favicon.
- El tiempo empleado en obtener cada imagen.
Además, el usuario podrá controlar el proceso mediante los botones de iniciar, pausar, reanudar y detener.
El resultado será una pequeña herramienta de escritorio que combina varias tecnologías de Python en un único proyecto.
Tecnologías utilizadas
Para desarrollar la aplicación utilizaremos varias librerías:
- Python: lenguaje principal del proyecto.
- Requests: realización de peticiones HTTP.
- Tkinter: creación de la interfaz gráfica.
- Pillow: carga, manipulación y conversión de imágenes.
- Threading: ejecución del proceso de descarga en segundo plano.
- JSON: almacenamiento de los tiempos de descarga.
- Time: medición del tiempo empleado en cada petición.
Instalación de dependencias
La mayoría de los componentes utilizados forman parte de Python o de su biblioteca estándar. Sin embargo, necesitaremos instalar algunas dependencias externas.
Podemos hacerlo mediante pip:
pip install requests pillow
Tkinter normalmente viene incluido en las instalaciones de Python para Windows. En algunas distribuciones Linux puede ser necesario instalarlo mediante el gestor de paquetes del sistema.
Por ejemplo, en Ubuntu:
sudo apt install python3-tk
La lista de sitios web
El primer paso consiste en definir los sitios que queremos procesar.
En nuestro ejemplo utilizaremos una lista de dominios:
websites = [
"https://www.google.com",
"https://www.reddit.com",
"https://www.yahoo.com",
"https://www.amazon.com",
"https://www.bbc.com",
"https://www.cnn.com",
"https://www.python.org",
"https://www.github.com",
"https://www.linkedin.com",
"https://www.youtube.com",
"https://www.wikipedia.org",
"https://www.apple.com",
"https://www.microsoft.com",
"https://www.spotify.com",
"https://www.slack.com",
"https://www.salesforce.com"
]
Naturalmente, esta lista puede modificarse y ampliarse con cualquier dominio que queramos procesar.
En una aplicación más avanzada podríamos incluso permitir que el usuario introdujese las URLs directamente desde la interfaz.
Obtener el favicon mediante una petición HTTP
Una de las partes fundamentales del programa es la función encargada de obtener la imagen.
Utilizaremos Requests para realizar una petición HTTP:
def fetch_thumbnail(url):
try:
response = requests.get(
f"https://www.google.com/s2/favicons?domain={url}",
timeout=5
)
response.raise_for_status()
img = Image.open(BytesIO(response.content))
img.thumbnail((150, 150))
except requests.exceptions.RequestException:
img = default_favicon
return img
Aquí están ocurriendo varias cosas.
1. Realizamos la petición HTTP
requests.get() realiza una petición HTTP al servicio utilizado para obtener el favicon.
El parámetro timeout=5 evita que una petición pueda quedarse bloqueada indefinidamente.
Esto es especialmente importante cuando estamos procesando muchos dominios. Si un servidor no responde correctamente, no queremos que nuestra aplicación quede esperando eternamente.
2. Comprobamos la respuesta
La llamada:
response.raise_for_status()
genera una excepción si el servidor devuelve un código HTTP de error.
Por ejemplo, códigos como 404 o 500 pueden provocar que la petición sea considerada fallida.
3. Convertimos los bytes en una imagen
La respuesta HTTP contiene los datos binarios de la imagen.
Utilizamos BytesIO para tratar esos datos como si fueran un archivo:
img = Image.open(BytesIO(response.content))
Posteriormente, Pillow se encarga de interpretar la imagen.
4. Redimensionamos el favicon
Para evitar que imágenes demasiado grandes ocupen demasiado espacio en la interfaz utilizamos:
img.thumbnail((150, 150))
Esta operación mantiene las proporciones originales de la imagen.
¿Qué ocurre si no podemos descargar el favicon?
En una aplicación que procesa múltiples sitios debemos asumir que algunas peticiones pueden fallar.
Para evitar que un único error detenga todo el programa utilizamos una imagen predeterminada:
default_favicon = Image.new(
'RGB',
(50, 50),
color=(73, 109, 137)
)
Si la petición falla, utilizaremos esta imagen como sustituto.
Esto permite que el proceso continúe con el resto de dominios.
Medición del tiempo de descarga
Otra característica interesante del programa es que podemos medir cuánto tarda cada petición.
Para ello utilizamos el módulo time:
start_time = time.time() img = fetch_thumbnail(url) end_time = time.time() elapsed_time = end_time - start_time
La diferencia entre ambos valores nos proporciona una aproximación del tiempo empleado en realizar la operación.
Posteriormente podemos almacenar esta información:
download_times.append({
"url": url,
"time": elapsed_time
})
El resultado será una estructura de datos que podremos utilizar posteriormente para generar estadísticas o exportarlas a un archivo.
¿Por qué utilizamos threading?
Aquí aparece una cuestión importante cuando desarrollamos interfaces gráficas.
Las aplicaciones creadas con Tkinter utilizan un bucle principal encargado de mantener la interfaz funcionando y procesar los eventos del usuario.
Si realizamos todas las peticiones HTTP directamente dentro de ese hilo principal, la ventana puede quedarse congelada mientras esperamos las respuestas de los servidores.
Por ejemplo:
for website in websites:
img = fetch_thumbnail(website)
Si tenemos decenas de sitios y algunos tardan varios segundos en responder, la interfaz dejaría de responder durante el proceso.
Para evitarlo ejecutamos el proceso en un hilo independiente:
threading.Thread(
target=run_scraping,
args=(
progress,
label_progress,
icon_inner_frame,
time_table,
icon_canvas
)
).start()
De esta manera el proceso de descarga se ejecuta en segundo plano mientras la interfaz continúa respondiendo.
Controlar el proceso mediante Event
Para implementar los botones de pausa, reanudación y detención utilizaremos threading.Event.
pause_event = threading.Event() stop_event = threading.Event() pause_event.set()
Un Event puede entenderse como una señal que puede estar activada o desactivada.
Pausar
pause_event.clear()
Al ejecutar clear(), el evento queda desactivado.
Reanudar
pause_event.set()
Al volver a activarlo, el proceso puede continuar.
Esperar mientras está pausado
Dentro del proceso principal tenemos:
pause_event.wait()
Esta llamada bloquea el hilo hasta que el evento vuelva a estar activado.
Detener
Para detener el proceso utilizamos otro evento:
stop_event.set()
Posteriormente el bucle comprueba su estado:
if stop_event.is_set():
break
De esta forma podemos abandonar el proceso sin necesidad de cerrar la aplicación.
Mostrar los favicons en Tkinter
Una vez obtenida la imagen necesitamos convertirla a un formato que Tkinter pueda mostrar:
img = ImageTk.PhotoImage(img)
Después podemos crear un Label que contenga la imagen:
label = Label(
icon_inner_frame,
image=img,
cursor="hand2",
relief="solid",
borderwidth=1
)
Hay un detalle importante cuando trabajamos con imágenes en Tkinter: debemos conservar una referencia al objeto PhotoImage.
label.image = img
Si no mantenemos esta referencia, el recolector de basura de Python puede eliminar el objeto y la imagen podría desaparecer de la interfaz.
Organizar los iconos mediante Grid
Los favicons se colocan en una cuadrícula utilizando el gestor de geometría grid.
row = index // 12
column = index % 12
label.grid(
row=row,
column=column,
padx=10,
pady=10,
sticky="nsew"
)
De esta forma podemos distribuir los iconos en diferentes filas y columnas.
El número de columnas puede modificarse fácilmente dependiendo del tamaño de la ventana.
Barra de progreso
Para proporcionar información visual al usuario utilizamos un Progressbar de Tkinter:
progress = ttk.Progressbar(
central_frame,
orient="horizontal",
length=500,
mode="determinate"
)
Durante el proceso actualizamos su valor:
progress['value'] = (
(index + 1) * 100 / len(websites)
)
Y mostramos también información textual:
label_progress.config(
text=f"Progreso general: {index + 1}/{len(websites)}"
)
Esto permite conocer tanto el porcentaje aproximado como el número exacto de sitios procesados.
Mostrar los tiempos de descarga
Para visualizar los resultados utilizaremos un Treeview:
columns = ("URL", "Tiempo")
time_table = ttk.Treeview(
central_frame,
columns=columns,
show="headings"
)
time_table.heading("URL", text="URL")
time_table.heading("Tiempo", text="Tiempo de descarga")
Cada vez que obtenemos un favicon añadimos una nueva fila:
time_table.insert(
"",
"end",
values=(url, f"{elapsed_time:.2f} segundos")
)
Así podemos comprobar qué dominios han respondido más rápido y cuáles han tardado más.
Exportar los resultados a JSON
Los datos recopilados pueden almacenarse en un archivo JSON para analizarlos posteriormente.
def save_to_json():
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("Archivos JSON", "*.json")]
)
if file_path:
with open(file_path, "w", encoding="utf-8") as file:
json.dump(
download_times,
file,
indent=4,
ensure_ascii=False
)
El resultado tendrá una estructura similar a:
[
{
"url": "https://www.google.com",
"time": 0.42
},
{
"url": "https://www.python.org",
"time": 0.31
}
]
Este formato resulta especialmente interesante porque posteriormente podemos procesarlo desde Python, JavaScript, PHP u otros lenguajes.
Convertir los favicons a blanco y negro
Otra funcionalidad incluida en el programa es la posibilidad de convertir los iconos descargados a escala de grises.
def convert_icons_to_bw():
for label in icon_inner_frame.winfo_children():
img = label.image
img = ImageTk.getimage(img).convert("L")
img = ImageTk.PhotoImage(img)
label.config(image=img)
label.image = img
El modo "L" de Pillow representa una imagen en escala de grises.
Esto puede utilizarse, por ejemplo, para crear una visualización monocromática de todos los sitios recopilados.
Guardar los favicons como archivos PNG
También podemos guardar físicamente las imágenes descargadas.
def download_icons():
for website in websites:
img = fetch_thumbnail(website)
img = img.convert("RGB")
domain_name = (
website.split("//")[-1].split("/")[0]
)
img.save(f"{domain_name}_favicon.png")
Por ejemplo, para:
https://www.python.org
obtendremos un archivo similar a:
www.python.org_favicon.png
Interfaz gráfica completa
La aplicación utiliza Tkinter para construir una interfaz sencilla desde la que controlar todo el proceso.
Disponemos de botones para:
- Iniciar scraping: comienza la descarga.
- Pausar scraping: suspende temporalmente el proceso.
- Reanudar scraping: continúa desde el punto en el que se pausó.
- Detener scraping: finaliza el proceso.
- Guardar JSON: exporta los tiempos registrados.
- Convertir iconos a B/N: transforma las imágenes a escala de grises.
- Descargar iconos: guarda los favicons como archivos.
Código fuente completo
A continuación podemos encontrar el código completo de la aplicación:
import requests
import time
import json
from tkinter import (
Tk,
Button,
Label,
Scrollbar,
filedialog,
Canvas,
Frame
)
from tkinter import ttk
from PIL import Image, ImageTk
from io import BytesIO
import threading
# ============================================================
# CONFIGURACIÓN
# ============================================================
websites = [
"https://www.google.com",
"https://www.reddit.com",
"https://www.yahoo.com",
"https://www.amazon.com",
"https://www.bbc.com",
"https://www.cnn.com",
"https://www.msn.com",
"https://stackoverflow.com",
"https://medium.com",
"https://python.org",
"https://github.com",
"https://linkedin.com",
"https://twitter.com",
"https://facebook.com",
"https://instagram.com",
"https://quora.com",
"https://tumblr.com",
"https://reuters.com",
"https://nytimes.com",
"https://nasa.gov",
"https://apple.com",
"https://microsoft.com",
"https://netflix.com",
"https://spotify.com",
"https://soundcloud.com",
"https://pinterest.com",
"https://twitch.tv",
"https://snapchat.com",
"https://tiktok.com",
"https://wired.com",
"https://forbes.com",
"https://theguardian.com",
"https://nike.com",
"https://espn.com",
"https://dailymotion.com",
"https://flickr.com",
"https://tripadvisor.com",
"https://imdb.com",
"https://rottentomatoes.com",
"https://weather.com",
"https://bing.com",
"https://etsy.com",
"https://cnbc.com",
"https://npr.org",
"https://youtube.com",
"https://wikipedia.org",
"https://slack.com",
"https://salesforce.com"
]
# Imagen utilizada cuando no podemos obtener el favicon
default_favicon = Image.new(
"RGB",
(50, 50),
color=(73, 109, 137)
)
# Resultados de las descargas
download_times = []
# Eventos utilizados para controlar el hilo
pause_event = threading.Event()
stop_event = threading.Event()
# El proceso comienza sin estar pausado
pause_event.set()
# ============================================================
# DESCARGA DEL FAVICON
# ============================================================
def fetch_thumbnail(url):
try:
response = requests.get(
f"https://www.google.com/s2/favicons?domain={url}",
timeout=5
)
response.raise_for_status()
image = Image.open(
BytesIO(response.content)
).convert("RGBA")
image.thumbnail((150, 150))
return image
except Exception as error:
print(f"Error obteniendo favicon de {url}: {error}")
return default_favicon.copy()
# ============================================================
# ACTUALIZAR SCROLL
# ============================================================
def update_canvas_scroll_region():
icon_canvas.update_idletasks()
icon_canvas.configure(
scrollregion=icon_canvas.bbox("all")
)
# ============================================================
# AÑADIR FAVICON A LA INTERFAZ
# ============================================================
def add_thumbnail(url, index):
start_time = time.time()
image = fetch_thumbnail(url)
elapsed_time = time.time() - start_time
photo = ImageTk.PhotoImage(image)
label = Label(
icon_inner_frame,
image=photo,
cursor="hand2",
relief="solid",
borderwidth=1
)
# Mantener una referencia para evitar que
# Python elimine la imagen de memoria.
label.image = photo
columns = 8
row = index // columns
column = index % columns
label.grid(
row=row,
column=column,
padx=10,
pady=10
)
download_times.append({
"url": url,
"time": elapsed_time
})
time_table.insert(
"",
"end",
values=(
url,
f"{elapsed_time:.2f} segundos"
)
)
percentage = (
(index + 1) * 100 / len(websites)
)
progress["value"] = percentage
label_progress.config(
text=f"Progreso general: {index + 1}/{len(websites)}"
)
update_canvas_scroll_region()
# ============================================================
# PROCESO PRINCIPAL
# ============================================================
def run_scraping():
# Reiniciar estado de parada
stop_event.clear()
for index, website in enumerate(websites):
if stop_event.is_set():
break
# Esperar si el proceso está pausado
pause_event.wait()
if stop_event.is_set():
break
add_thumbnail(
website,
index
)
if not stop_event.is_set():
progress["value"] = 100
label_progress.config(
text="¡Íconos descargados con éxito! 🎉"
)
# ============================================================
# INICIAR
# ============================================================
def start_scraping():
if not pause_event.is_set():
pause_event.set()
thread = threading.Thread(
target=run_scraping,
daemon=True
)
thread.start()
# ============================================================
# PAUSAR
# ============================================================
def pause_scraping():
pause_event.clear()
label_progress.config(
text="Scraping pausado"
)
# ============================================================
# REANUDAR
# ============================================================
def resume_scraping():
pause_event.set()
label_progress.config(
text="Scraping reanudado"
)
# ============================================================
# DETENER
# ============================================================
def stop_scraping():
stop_event.set()
pause_event.set()
label_progress.config(
text="Scraping detenido"
)
# ============================================================
# GUARDAR JSON
# ============================================================
def save_to_json():
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[
("Archivos JSON", "*.json")
]
)
if not file_path:
return
with open(
file_path,
"w",
encoding="utf-8"
) as file:
json.dump(
download_times,
file,
indent=4,
ensure_ascii=False
)
# ============================================================
# CONVERTIR ICONOS A BLANCO Y NEGRO
# ============================================================
def convert_icons_to_bw():
for label in icon_inner_frame.winfo_children():
image = ImageTk.getimage(
label.image
)
image = image.convert("L")
photo = ImageTk.PhotoImage(
image
)
label.configure(
image=photo
)
label.image = photo
# ============================================================
# GUARDAR FAVICONS
# ============================================================
def download_icons():
folder = filedialog.askdirectory()
if not folder:
return
for website in websites:
image = fetch_thumbnail(website)
image = image.convert("RGBA")
domain = (
website
.split("//")[-1]
.split("/")[0]
)
filename = f"{domain}_favicon.png"
image.save(
f"{folder}/{filename}"
)
label_progress.config(
text="Favicons guardados correctamente."
)
# ============================================================
# INTERFAZ
# ============================================================
root = Tk()
root.title(
"Scraper de Favicons y Tiempos"
)
root.state("zoomed")
central_frame = ttk.Frame(root)
central_frame.pack(
padx=20,
pady=20,
fill="both",
expand=True
)
central_frame.grid_rowconfigure(
4,
weight=1
)
central_frame.grid_columnconfigure(
0,
weight=1
)
# ============================================================
# BOTONES
# ============================================================
button_frame = ttk.Frame(
central_frame
)
button_frame.grid(
row=0,
column=0,
pady=20
)
start_button = Button(
button_frame,
text="Iniciar scraping",
command=start_scraping
)
start_button.grid(
row=0,
column=0,
padx=10
)
pause_button = Button(
button_frame,
text="Pausar scraping",
command=pause_scraping
)
pause_button.grid(
row=0,
column=1,
padx=10
)
resume_button = Button(
button_frame,
text="Reanudar scraping",
command=resume_scraping
)
resume_button.grid(
row=0,
column=2,
padx=10
)
stop_button = Button(
button_frame,
text="Detener scraping",
command=stop_scraping
)
stop_button.grid(
row=0,
column=3,
padx=10
)
save_button = Button(
button_frame,
text="Guardar JSON",
command=save_to_json
)
save_button.grid(
row=0,
column=4,
padx=10
)
convert_button = Button(
button_frame,
text="Convertir iconos a B/N",
command=convert_icons_to_bw
)
convert_button.grid(
row=0,
column=5,
padx=10
)
download_button = Button(
button_frame,
text="Descargar iconos",
command=download_icons
)
download_button.grid(
row=0,
column=6,
padx=10
)
# ============================================================
# PROGRESO
# ============================================================
progress = ttk.Progressbar(
central_frame,
orient="horizontal",
length=500,
mode="determinate"
)
progress.grid(
row=1,
column=0,
pady=10
)
label_progress = Label(
central_frame,
text="Progreso general: 0/0"
)
label_progress.grid(
row=2,
column=0,
pady=10
)
# ============================================================
# TABLA
# ============================================================
columns = (
"URL",
"Tiempo"
)
time_table = ttk.Treeview(
central_frame,
columns=columns,
show="headings",
height=12
)
time_table.heading(
"URL",
text="URL"
)
time_table.heading(
"Tiempo",
text="Tiempo de descarga"
)
time_table.column(
"URL",
width=500
)
time_table.column(
"Tiempo",
width=200
)
time_table.grid(
row=3,
column=0,
padx=20,
pady=20
)
# ============================================================
# CANVAS
# ============================================================
icon_canvas = Canvas(
central_frame
)
icon_canvas.grid(
row=4,
column=0,
padx=20,
pady=20,
sticky="nsew"
)
scrollbar = Scrollbar(
central_frame,
orient="vertical",
command=icon_canvas.yview
)
scrollbar.grid(
row=4,
column=1,
sticky="ns"
)
icon_canvas.configure(
yscrollcommand=scrollbar.set
)
icon_frame = Frame(
icon_canvas
)
icon_canvas.create_window(
(0, 0),
window=icon_frame,
anchor="nw"
)
icon_inner_frame = Frame(
icon_frame
)
icon_inner_frame.grid(
row=0,
column=0
)
# ============================================================
# INICIO
# ============================================================
root.mainloop()
Algunas consideraciones importantes
Aunque este proyecto es perfectamente válido como herramienta educativa, conviene tener en cuenta que estamos realizando peticiones HTTP contra servicios externos.
Cuando trabajemos con grandes cantidades de dominios debemos evitar generar una cantidad excesiva de peticiones en un periodo de tiempo muy corto. Las políticas de cada servicio pueden ser diferentes y algunos proveedores pueden limitar o bloquear determinadas solicitudes.
También debemos tener en cuenta que un favicon no tiene por qué existir siempre. Un dominio puede no disponer de uno, puede utilizar un formato que nuestra herramienta no pueda interpretar o puede bloquear determinadas peticiones.
Una limitación importante del código original
Hay un detalle técnico especialmente importante cuando trabajamos con Tkinter y threading.
Tkinter no está diseñado para que sus widgets sean manipulados libremente desde cualquier hilo.
En una implementación sencilla podemos encontrarnos con código que funciona correctamente en determinadas máquinas pero presenta comportamientos inesperados en otras.
Una arquitectura más robusta consiste en realizar las peticiones HTTP en hilos de trabajo y enviar posteriormente los resultados al hilo principal mediante root.after(), una cola (queue.Queue) o un sistema similar.
Por ejemplo:
root.after(
0,
actualizar_interfaz,
resultado
)
De esta forma, la descarga puede ejecutarse en segundo plano mientras todas las modificaciones de la interfaz se realizan desde el hilo principal de Tkinter.
¿Cómo podríamos mejorar todavía más la aplicación?
Este proyecto puede utilizarse como punto de partida para desarrollar una herramienta mucho más completa.
Introducir URLs desde la interfaz
Actualmente la lista de sitios está definida directamente en el código. Una evolución natural sería incorporar un campo de texto donde el usuario pueda introducir nuevas URLs.
Incluso podríamos permitir importar una lista desde un archivo:
websites.txt
y procesar automáticamente todos los dominios contenidos en él.
Descargas concurrentes
El código actual procesa los dominios secuencialmente.
Una versión más avanzada podría utilizar un ThreadPoolExecutor para descargar varios favicons simultáneamente:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=8) as executor:
resultados = executor.map(
fetch_thumbnail,
websites
)
Esto puede reducir considerablemente el tiempo total de procesamiento cuando trabajamos con una lista grande.
Eso sí: aumentar la concurrencia también aumenta el número de peticiones simultáneas, por lo que debemos utilizarla con criterio.
Detectar el favicon directamente desde HTML
Otra mejora interesante sería dejar de depender de un servicio externo y analizar directamente el HTML de cada página.
Podríamos buscar etiquetas como:
<link rel="icon" href="..."> <link rel="shortcut icon" href="..."> <link rel="apple-touch-icon" href="...">
Esto permitiría construir un sistema de descubrimiento de favicons mucho más completo.
Guardar información adicional
El JSON podría ampliarse para almacenar más información:
{
"url": "https://example.com",
"status": 200,
"time": 0.42,
"format": "PNG",
"width": 64,
"height": 64
}
De esta manera convertiríamos el programa en una pequeña herramienta de análisis de recursos web.
Conclusión
Este proyecto demuestra cómo Python puede utilizarse para construir una herramienta de escritorio que combina peticiones HTTP, procesamiento de imágenes, concurrencia, almacenamiento de datos y una interfaz gráfica.
Aparentemente solo estamos descargando unos pequeños iconos, pero el proyecto nos permite trabajar con conceptos bastante importantes del desarrollo de software:
- Peticiones HTTP.
- Gestión de errores.
- Timeouts.
- Procesamiento de imágenes.
- Interfaces gráficas.
- Hilos y concurrencia.
- Eventos de sincronización.
- Persistencia de información mediante JSON.
- Actualización de interfaces durante procesos largos.
Y precisamente ahí está lo interesante del proyecto: una herramienta aparentemente sencilla puede convertirse en un excelente ejercicio para aprender cómo interactúan diferentes componentes de Python en una aplicación real.
Además, a partir de esta base podemos evolucionarla hacia un gestor de recursos web mucho más completo, capaz de analizar dominios, descubrir favicons automáticamente, medir tiempos de respuesta, generar estadísticas y exportar toda la información recopilada.
Nota: utiliza este tipo de herramientas de forma responsable y respeta las condiciones de uso, límites de frecuencia y políticas de los servicios a los que realices peticiones.
Visitas: 4
Automation & Data Specialist | Web Development | Data Processing & Integration
