import gradio as gr import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry from bs4 import BeautifulSoup from urllib.parse import urljoin, urlparse import os def build_session(): session = requests.Session() retries = Retry( total=2, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], ) adapter = HTTPAdapter(max_retries=retries) session.mount("https://", adapter) session.mount("http://", adapter) return session def extract_images(url): session = build_session() try: headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", } # Timeout separado: (conexión, lectura). Antes era un único valor de 50s, # lo que dejaba la UI colgada mucho tiempo si el sitio nunca respondía. response = session.get(url, headers=headers, timeout=(10, 20)) response.raise_for_status() soup = BeautifulSoup(response.text, "html.parser") base_url = f"{urlparse(url).scheme}://{urlparse(url).netloc}" images = [] seen = set() # Buscar imágenes en for img in soup.find_all("img"): src = img.get("src") or img.get("data-src") or img.get("data-original") if not src: continue # Convertir URLs relativas a absolutas img_url = urljoin(base_url, src) # Filtrar duplicados y URLs inválidas if img_url in seen or not img_url.startswith(("http://", "https://")): continue seen.add(img_url) # Obtener nombre del archivo parsed = urlparse(img_url) filename = os.path.basename(parsed.path) or "imagen.jpg" if not any(filename.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"]): filename += ".jpg" images.append({ "image": img_url, "caption": filename }) # Buscar imágenes de fondo en
o elementos con style for tag in soup.find_all(style=True): style = tag["style"] if "url(" in style: url_start = style.find("url(") + 4 url_end = style.find(")", url_start) bg_url = style[url_start:url_end].strip('"\'') bg_url = urljoin(base_url, bg_url) if bg_url not in seen and bg_url.startswith(("http://", "https://")): seen.add(bg_url) images.append({"image": bg_url, "caption": "background.jpg"}) if not images: return [], "No se encontraron imágenes en esta página." return [img["image"] for img in images], f"Se encontraron {len(images)} imágenes." except requests.exceptions.Timeout: return [], ( "El sitio no respondió a tiempo tras reintentar. Suele pasar cuando " "el dominio está filtrado a nivel de red/DNS (algunos hosts de archivos " "terminan en listas de bloqueo por abuso) o el servidor está caído/lento. " "Prueba el mismo enlace en el navegador desde esta misma red para confirmar." ) except requests.exceptions.ConnectionError as e: # requests envuelve un ReadTimeout que agotó los reintentos como # ConnectionError (no como Timeout) — es un detalle de la librería, # no un tipo de fallo distinto al de arriba. if "ReadTimeoutError" in str(e) or "Read timed out" in str(e): return [], ( "El sitio acepta la conexión pero nunca termina de responder, " "ni siquiera tras reintentar. Es el patrón de un bloqueo de " "red/firewall o de un mecanismo anti-bots que corta la respuesta " "a mitad de camino, no el de un timeout demasiado corto." ) return [], f"No se pudo conectar con el sitio: {str(e)}" except requests.exceptions.RequestException as e: return [], f"Error al acceder a la URL: {str(e)}" except Exception as e: return [], f"Error inesperado: {str(e)}" finally: session.close() # Interfaz de Gradio with gr.Blocks(title="Extractor de Imágenes Web") as demo: gr.Markdown(""" # 🖼️ Extractor de Imágenes Pega el enlace de cualquier página web y extrae todas las imágenes disponibles. """) with gr.Row(): url_input = gr.Textbox( label="URL de la página", placeholder="https://ejemplo.com", scale=4 ) submit_btn = gr.Button("🔍 Extraer imágenes", scale=1, variant="primary") status_text = gr.Textbox(label="Estado", interactive=False) gallery = gr.Gallery( label="Imágenes encontradas", show_label=True, columns=4, rows=4, height="auto", object_fit="contain", allow_preview=True ) # Información de descarga gr.Markdown(""" ### 💡 Para descargar: 1. Haz **clic en cualquier imagen** para previsualizarla en tamaño completo. 2. En la vista previa, haz **clic derecho → Guardar imagen como...**. 3. También puedes hacer clic derecho directamente sobre cualquier miniatura. """) submit_btn.click( fn=extract_images, inputs=url_input, outputs=[gallery, status_text] ) # Ejemplo gr.Examples( examples=["https://es.wikipedia.org/wiki/Gato"], inputs=url_input, label="Ejemplo" ) if __name__ == "__main__": demo.launch()