Lukeetah commited on
Commit
6f77dbb
·
verified ·
1 Parent(s): a35aa97

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -109
app.py CHANGED
@@ -1,120 +1,120 @@
1
- import os
2
  import requests
3
- from flask import Flask, render_template, request, flash, redirect, url_for
4
  from bs4 import BeautifulSoup
5
- import pandas as pd
6
  import time
7
-
8
- # --- Inicialización de la Aplicación Flask ---
9
- app = Flask(__name__)
10
- # Se necesita una clave secreta para mostrar mensajes (flashing)
11
- app.secret_key = 'supersecretkey'
12
-
13
- # --- Lógica de Web Scraping ---
14
- def scrape_lobby_data():
 
 
 
 
 
 
 
 
15
  """
16
- Función que realiza el web scraping en el sitio de Ley de Lobby,
17
- extrae los datos de audiencias y los guarda en archivos TXT y CSV.
18
  """
19
- # URL del sitio a scrapear para el año 2025
20
- url = "https://www.leylobby.gob.cl/instituciones/AO001/audiencias/2025"
21
 
22
- # --- Consideración Ética ---
23
- # Es una buena práctica identificarse. Algunos sitios web pueden bloquear
24
- # solicitudes sin un User-Agent reconocible.
25
- headers = {
26
- 'User-Agent': 'EthicalScraper/1.0 (contacto@ejemplo.com) - Script para proyecto educativo'
27
- }
 
 
 
28
 
29
  try:
30
- # Realizar la solicitud GET para obtener el contenido de la página
31
- print(f"Obteniendo datos desde: {url}")
32
  response = requests.get(url, headers=headers, timeout=15)
33
- # Genera un error si la solicitud no fue exitosa (ej. error 404, 500)
34
- response.raise_for_status()
35
-
36
- # --- Parseo del HTML con BeautifulSoup ---
37
  soup = BeautifulSoup(response.content, 'html.parser')
38
 
39
- # Encontrar la tabla que contiene los datos de las audiencias
40
- # Se debe inspeccionar el HTML del sitio para encontrar los selectores correctos.
41
- table = soup.find('table', class_='table-striped')
42
-
43
- if not table:
44
- print("No se encontró la tabla de datos. El sitio puede haber cambiado su estructura.")
45
- return False, "No se pudo encontrar la tabla de datos en la página."
46
-
47
- # --- Extracción de Datos ---
48
- # Extraer los encabezados de la tabla
49
- headers_list = [th.get_text(strip=True) for th in table.find('thead').find_all('th')]
50
-
51
- # Extraer las filas de datos de la tabla
52
- data_rows = []
53
- for row in table.find('tbody').find_all('tr'):
54
- columns = [td.get_text(strip=True) for td in row.find_all('td')]
55
- data_rows.append(columns)
56
-
57
- if not data_rows:
58
- return False, "La tabla fue encontrada, pero no contenía datos de audiencias."
59
-
60
- # --- Guardado de Archivos ---
61
- # Crear un DataFrame de pandas con los datos extraídos
62
- df = pd.DataFrame(data_rows, columns=headers_list)
63
-
64
- # 1. Guardar en formato CSV
65
- df.to_csv('audiencias_lobby.csv', index=False, encoding='utf-8-sig')
66
- print("Datos guardados exitosamente en 'audiencias_lobby.csv'")
67
-
68
- # 2. Guardar en formato TXT (formato de texto plano separado por comas)
69
- df.to_csv('audiencias_lobby.txt', index=False, sep='\t')
70
- print("Datos guardados exitosamente en 'audiencias_lobby.txt'")
71
-
72
- # --- Pausa Ética ---
73
- # Si fueras a hacer más solicitudes, es bueno esperar un momento.
74
- time.sleep(1)
75
-
76
- return True, f"¡Éxito! Se extrajeron {len(data_rows)} registros y se guardaron en 'audiencias_lobby.csv' y 'audiencias_lobby.txt'."
77
-
78
- except requests.exceptions.RequestException as e:
79
- # Capturar errores de red (ej. sin conexión, DNS no encontrado)
80
- error_message = f"Error de red al intentar acceder a la URL: {e}"
81
- print(error_message)
82
- return False, error_message
83
- except Exception as e:
84
- # Capturar cualquier otro error inesperado
85
- error_message = f"Ocurrió un error inesperado: {e}"
86
- print(error_message)
87
- return False, error_message
88
-
89
- # --- Rutas de la Aplicación Web ---
90
-
91
- @app.route('/')
92
- def index():
93
- """
94
- Renderiza la página principal (index.html).
95
- """
96
- return render_template('index.html')
97
-
98
- @app.route('/scrape', methods=['POST'])
99
- def run_scraper():
100
- """
101
- Esta ruta se activa cuando se hace clic en el botón del formulario.
102
- Llama a la función de scraping y muestra un mensaje al usuario.
103
- """
104
- print("Iniciando proceso de scraping...")
105
- success, message = scrape_lobby_data()
106
-
107
- # Muestra un mensaje de éxito o error en la página
108
- if success:
109
- flash(message, 'success')
110
- else:
111
- flash(message, 'error')
112
-
113
- # Redirige al usuario de vuelta a la página principal
114
- return redirect(url_for('index'))
115
-
116
- # --- Punto de Entrada Principal ---
117
- if __name__ == '__main__':
118
- # Inicia el servidor de desarrollo de Flask
119
- # El debug=True permite ver los errores en el navegador y recarga el servidor automáticamente
120
- app.run(debug=True)
 
1
+ import gradio as gr
2
  import requests
 
3
  from bs4 import BeautifulSoup
 
4
  import time
5
+ import random
6
+ from urllib.parse import urljoin
7
+
8
+ # --- Técnicas Anti-Scraping ---
9
+ USER_AGENTS = [
10
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
11
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
12
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
13
+ ]
14
+
15
+ def get_random_user_agent():
16
+ """ Devuelve un User-Agent al azar. """
17
+ return random.choice(USER_AGENTS)
18
+
19
+ # --- Función Principal de Scraping ---
20
+ def scrape_website(url, max_links_str):
21
  """
22
+ Scrapea la URL, entra en cada link de detalle, extrae el contenido de las tablas
23
+ y devuelve un archivo de texto para descargar.
24
  """
25
+ if not url.startswith('http'):
26
+ url = 'https://' + url
27
 
28
+ # Convertir el número máximo de links a entero, con un valor por defecto
29
+ try:
30
+ max_links = int(max_links_str)
31
+ except (ValueError, TypeError):
32
+ max_links = 10 # Valor por defecto si la entrada no es válida
33
+
34
+ links_to_visit = set()
35
+ all_content = f"Resultados del scraping para: {url}\n"
36
+ all_content += "========================================\n\n"
37
 
38
  try:
39
+ # 1. Petición a la URL principal
40
+ headers = {'User-Agent': get_random_user_agent()}
41
  response = requests.get(url, headers=headers, timeout=15)
42
+ response.raise_for_status()
 
 
 
43
  soup = BeautifulSoup(response.content, 'html.parser')
44
 
45
+ # 2. Encontrar todos los links que parecen ser de audiencias
46
+ # Se busca un patrón específico para ser más preciso
47
+ for a_tag in soup.find_all('a', href=True):
48
+ link = a_tag['href']
49
+ # Usamos urljoin para construir correctamente la URL absoluta
50
+ full_link = urljoin(url, link)
51
+ # Filtramos para quedarnos solo con los links de audiencias del mismo sitio
52
+ if url in full_link and '/audiencias/' in full_link:
53
+ links_to_visit.add(full_link)
54
+
55
+ all_content += f"Se encontraron {len(links_to_visit)} links de audiencias para visitar.\n"
56
+ all_content += f"Procesando los primeros {min(len(links_to_visit), max_links)} links...\n\n"
57
+
58
+ # 3. Visitar cada link y extraer el contenido de la tabla
59
+ for i, link in enumerate(list(links_to_visit)[:max_links]):
60
+ try:
61
+ time.sleep(random.uniform(1, 2.5)) # Pausa respetuosa
62
+ headers = {'User-Agent': get_random_user_agent()}
63
+ detail_response = requests.get(link, headers=headers, timeout=10)
64
+ detail_response.raise_for_status()
65
+ detail_soup = BeautifulSoup(detail_response.content, 'html.parser')
66
+
67
+ title = detail_soup.find('title').get_text(strip=True) if detail_soup.find('title') else "Sin título"
68
+ all_content += f"--- Contenido de: {link} ---\n"
69
+ all_content += f"Título: {title}\n\n"
70
+
71
+ # Buscar la tabla de detalles (inspeccionando la página, vemos que tiene la clase 'table') [4, 5]
72
+ table = detail_soup.find('table', class_='table')
73
+ if table:
74
+ # Extraer todas las filas de la tabla [1]
75
+ rows = table.find_all('tr')
76
+ for row in rows:
77
+ # Extraer las celdas de cabecera (th) y datos (td)
78
+ cols = row.find_all(['th', 'td'])
79
+ # Limpiar y unir el texto de las celdas
80
+ cleaned_cols = [ele.text.strip() for ele in cols]
81
+ all_content += " | ".join(cleaned_cols) + "\n"
82
+ else:
83
+ all_content += "No se encontró una tabla de detalles en esta página.\n"
84
+
85
+ all_content += "\n----------------------------------------\n\n"
86
+
87
+ except requests.RequestException as e:
88
+ all_content += f"Error al visitar {link}: {e}\n\n"
89
+
90
+ except requests.RequestException as e:
91
+ return f"Error al acceder a la URL principal: {e}", None # Devuelve dos valores
92
+
93
+ # 4. Crear el archivo de texto y devolverlo
94
+ # Gradio maneja la creación del archivo temporal automáticamente [7, 8]
95
+ file_path = "resultados_scraping.txt"
96
+ with open(file_path, "w", encoding="utf-8") as f:
97
+ f.write(all_content)
98
+
99
+ # Devolvemos un mensaje de éxito y la ruta del archivo para la descarga
100
+ return f"¡Proceso completado! Se procesaron {min(len(links_to_visit), max_links)} links. Descarga el archivo para ver los resultados.", file_path
101
+
102
+
103
+ # --- Interfaz con Gradio ---
104
+ iface = gr.Interface(
105
+ fn=scrape_website,
106
+ inputs=[
107
+ gr.Textbox(lines=1, placeholder="Ingresa una URL (ej. leylobby.gob.cl/...)"),
108
+ gr.Textbox(value="10", label="Número máximo de links a visitar")
109
+ ],
110
+ outputs=[
111
+ gr.Textbox(label="Estado del Proceso"),
112
+ gr.File(label="Descargar Resultados (.txt)") # Componente de descarga de archivo [7, 9]
113
+ ],
114
+ title="🤖 Web Scraper Pro v2",
115
+ description="Ingresa una URL para extraer el contenido de los links de detalle. El resultado se genera en un archivo .txt descargable. ¡Ideal para análisis de datos!",
116
+ allow_flagging="never"
117
+ )
118
+
119
+ # ¡Lanzamos la app!
120
+ iface.launch()