jcalbornoz commited on
Commit
98092ee
·
verified ·
1 Parent(s): 5dc688e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -88
app.py CHANGED
@@ -13,11 +13,9 @@ try:
13
  subprocess.run(["playwright", "install", "chromium"], check=True)
14
  except: pass
15
 
16
- # --- GENERADOR DE URLS (CORREGIDO SEGÚN TUS EJEMPLOS) ---
17
- def construir_urls(zona, ciudad, tipo, hab, ban, park, antiguedad):
18
- # 1. FINCA RAÍZ
19
- # Estructura usuario: https://www.fincaraiz.com.co/arriendo/3-o-mas-habitaciones/2-o-mas-banos/1-parqueadero/de-1-a-8-anios?&searchstring=fontibon-bogota
20
-
21
  mapa_ant = {
22
  "Menos de 1 año": "de-0-a-1-anos",
23
  "1 a 8 años": "de-1-a-8-anios",
@@ -27,149 +25,149 @@ def construir_urls(zona, ciudad, tipo, hab, ban, park, antiguedad):
27
  }
28
  slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anios")
29
 
30
- # Lógica singular/plural para parqueaderos
31
  slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
32
 
33
- # Searchstring limpio
34
  query_geo = f"{zona.lower().replace(' ', '-')}-{ciudad.lower().replace(' ', '-')}"
35
 
36
- # URL SIN LA ZONA EN EL PATH, SOLO EN SEARCHSTRING
37
  url_fr = f"https://www.fincaraiz.com.co/arriendo/{int(hab)}-o-mas-habitaciones/{int(ban)}-o-mas-banos/{slug_park}/{slug_ant}?&searchstring={query_geo}"
38
 
39
- # 2. METROCUADRADO (Esta funcionaba bien, solo ajustamos el scraper)
40
  c_slug = ciudad.lower().replace(" ", "-")
41
  url_mc = f"https://www.metrocuadrado.com/{tipo.lower()}-casa-oficina/arriendo/{c_slug}/{int(ban)}-banos-{int(hab)}-habitaciones/?search=form"
42
 
43
  return url_fr, url_mc
44
 
45
- # --- MOTOR DE EXTRACCIÓN ---
46
- def motor_tramitia_final(zona, ciudad, area, tipo, hab, ban, park, antiguedad):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  resultados = []
48
- log = []
49
 
50
- url_fr, url_mc = construir_urls(zona, ciudad, tipo, hab, ban, park, antiguedad)
51
- log.append(f"🔗 FR: {url_fr}")
52
- log.append(f"🔗 MC: {url_mc}")
 
 
53
 
54
  with sync_playwright() as p:
55
  browser = p.chromium.launch(headless=True)
56
- context = browser.new_context(
57
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
58
- )
59
 
60
- # --- 1. FINCA RAÍZ ---
61
  try:
62
  page = context.new_page()
63
  page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
 
64
 
65
- # Scroll para cargar
66
- for _ in range(3):
67
- page.mouse.wheel(0, 1000)
68
- time.sleep(1.5)
69
-
70
- # Buscamos en el wrapper de datos que nos diste antes
71
- # Si ese falla, tenemos un backup genérico
72
  wrappers = page.query_selector_all("div.lc-dataWrapper, article")
73
-
74
- for wrapper in wrappers[:6]:
75
  try:
76
- texto = wrapper.inner_text()
77
- if "$" not in texto: continue
78
-
79
- precios = [int(s) for s in texto.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
80
  if not precios: continue
81
 
82
- # JS para encontrar el link padre
83
- href = wrapper.evaluate("el => el.closest('a') ? el.closest('a').href : ''")
84
-
85
  resultados.append({
86
  "Portal": "Finca Raiz",
87
  "Precio": max(precios),
88
  "Precio_M2": max(precios) / area,
89
- "Descripcion": texto.split('\n')[0][:50] + "...",
90
  "URL": href if href else url_fr
91
  })
92
  except: continue
93
  page.close()
94
  except Exception as e:
95
- log.append(f"⚠️ Error FR: {str(e)}")
96
 
97
- # --- 2. METROCUADRADO (Selector corregido: property-card__detail) ---
98
  try:
99
  page = context.new_page()
100
  page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
 
101
 
102
- for _ in range(4):
103
- page.mouse.wheel(0, 800)
104
- time.sleep(1)
105
-
106
- # AQUI ESTA EL CAMBIO: Usamos tu clase exacta
107
- detalles = page.query_selector_all("div.property-card__detail")
108
-
109
- for det in detalles[:6]:
110
  try:
111
- texto = det.inner_text()
112
-
113
- # Extraer precio del texto del detalle
114
- precios = [int(s) for s in texto.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
115
  if not precios: continue
116
 
117
- # IMPORTANTE: El link NO suele estar dentro de 'property-card__detail',
118
- # suele estar en el contenedor padre o abuelo. Usamos JS para subir de nivel.
119
- href = det.evaluate("""el => {
120
- // Intentamos subir hasta encontrar la tarjeta completa y su link
121
- let card = el.closest('li') || el.closest('div.card') || el.closest('div[class*="card"]');
122
- if (card) {
123
- let link = card.querySelector('a');
124
- return link ? link.href : '';
125
- }
126
- return '';
127
  }""")
128
 
129
- # Si no encuentra link por JS, intentamos buscar el <a> más cercano hacia arriba
130
- if not href:
131
- href = det.evaluate("el => el.closest('a') ? el.closest('a').href : ''")
132
-
133
  resultados.append({
134
  "Portal": "Metrocuadrado",
135
  "Precio": max(precios),
136
  "Precio_M2": max(precios) / area,
137
- "Descripcion": texto.split('\n')[0][:50] + "...", # El detalle suele tener la info clave
138
  "URL": href if href else url_mc
139
  })
140
  except: continue
141
  page.close()
142
  except Exception as e:
143
- log.append(f"⚠️ Error MC: {str(e)}")
144
 
145
  browser.close()
146
 
147
  if not resultados:
148
- return f"⚠️ No se encontraron datos.\nLOG:\n" + "\n".join(log), None, None
149
 
150
  df = pd.DataFrame(resultados)
151
 
152
- # PDF
153
- pdf_path = f"Reporte_{int(time.time())}.pdf"
154
- pdf = FPDF()
155
- pdf.add_page()
156
- pdf.set_font("Arial", 'B', 14)
157
- pdf.cell(0, 10, f"REPORTE: {zona.upper()}", ln=True)
158
- pdf.ln(5)
159
 
160
- for _, r in df.iterrows():
161
- pdf.set_font("Arial", 'B', 10)
162
- pdf.cell(0, 8, f"{r['Portal']} - ${r['Precio']:,.0f}", ln=True)
163
- pdf.set_font("Arial", '', 8)
164
- pdf.multi_cell(0, 5, f"Link: {r['URL']}\nDesc: {r['Descripcion']}\n---")
165
-
166
- pdf.output(pdf_path)
167
-
168
- return f"✅ {len(df)} inmuebles encontrados.", df, pdf_path
169
 
170
  # --- INTERFAZ ---
171
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
172
- gr.Markdown("## 🤖 TramitIA Pro: Configuración Exacta")
173
 
174
  with gr.Row():
175
  with gr.Column():
@@ -187,13 +185,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
187
  ["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"],
188
  label="Antigüedad", value="1 a 8 años"
189
  )
190
- btn = gr.Button("BUSCAR", variant="primary")
191
 
192
  with gr.Column():
193
- msg = gr.Textbox(label="Log URL", lines=3)
 
194
  out_df = gr.Dataframe(label="Resultados")
195
- out_pdf = gr.File(label="Reporte")
196
 
197
- btn.click(motor_tramitia_final, [z, c, a, t, h, b, p, e], [msg, out_df, out_pdf])
198
 
199
  demo.launch()
 
13
  subprocess.run(["playwright", "install", "chromium"], check=True)
14
  except: pass
15
 
16
+ # --- 1. GENERADOR DE URLS (Tu lógica exacta) ---
17
+ def construir_urls_final(zona, ciudad, tipo, hab, ban, park, antiguedad):
18
+ # Mapeo Antigüedad
 
 
19
  mapa_ant = {
20
  "Menos de 1 año": "de-0-a-1-anos",
21
  "1 a 8 años": "de-1-a-8-anios",
 
25
  }
26
  slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anios")
27
 
28
+ # Lógica singular/plural
29
  slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
30
 
31
+ # Searchstring
32
  query_geo = f"{zona.lower().replace(' ', '-')}-{ciudad.lower().replace(' ', '-')}"
33
 
34
+ # URL FINCA RAÍZ
35
  url_fr = f"https://www.fincaraiz.com.co/arriendo/{int(hab)}-o-mas-habitaciones/{int(ban)}-o-mas-banos/{slug_park}/{slug_ant}?&searchstring={query_geo}"
36
 
37
+ # URL METROCUADRADO
38
  c_slug = ciudad.lower().replace(" ", "-")
39
  url_mc = f"https://www.metrocuadrado.com/{tipo.lower()}-casa-oficina/arriendo/{c_slug}/{int(ban)}-banos-{int(hab)}-habitaciones/?search=form"
40
 
41
  return url_fr, url_mc
42
 
43
+ # --- 2. GENERADOR PDF CON URLS VISIBLES ---
44
+ def generar_pdf_con_fuentes(zona, df, url_fr, url_mc):
45
+ pdf_path = f"Reporte_{int(time.time())}.pdf"
46
+ pdf = FPDF()
47
+ pdf.add_page()
48
+
49
+ # Título
50
+ pdf.set_font("Arial", 'B', 14)
51
+ pdf.cell(0, 10, f"ESTUDIO DE MERCADO: {zona.upper()}", ln=True)
52
+
53
+ # BLOQUE DE FUENTES (URLs Visibles)
54
+ pdf.set_font("Arial", 'B', 8)
55
+ pdf.set_text_color(100, 100, 100)
56
+ pdf.cell(0, 5, "FUENTES DE BÚSQUEDA GENERADAS:", ln=True)
57
+ pdf.set_font("Courier", '', 7)
58
+ pdf.multi_cell(0, 4, f"FR: {url_fr}")
59
+ pdf.multi_cell(0, 4, f"MC: {url_mc}")
60
+ pdf.ln(5)
61
+
62
+ # Tabla de Resultados
63
+ pdf.set_text_color(0, 0, 0)
64
+ pdf.set_font("Arial", '', 10)
65
+
66
+ for _, r in df.iterrows():
67
+ pdf.set_fill_color(240, 240, 240)
68
+ # Título
69
+ pdf.set_font("Arial", 'B', 10)
70
+ pdf.cell(0, 8, f"{r['Portal']} - ${r['Precio']:,.0f}", ln=True, fill=True)
71
+ # Detalle
72
+ pdf.set_font("Arial", '', 9)
73
+ pdf.multi_cell(0, 5, f"Desc: {r['Descripcion']}")
74
+ pdf.set_font("Arial", 'U', 8)
75
+ pdf.set_text_color(0, 0, 255)
76
+ pdf.cell(0, 5, f"Link: {r['URL']}", ln=True)
77
+ pdf.set_text_color(0, 0, 0)
78
+ pdf.ln(2)
79
+
80
+ pdf.output(pdf_path)
81
+ return pdf_path
82
+
83
+ # --- 3. MOTOR DE EXTRACCIÓN ---
84
+ def motor_tramitia_debug(zona, ciudad, area, tipo, hab, ban, park, antiguedad):
85
  resultados = []
 
86
 
87
+ # 1. Generamos URLs
88
+ url_fr, url_mc = construir_urls_final(zona, ciudad, tipo, hab, ban, park, antiguedad)
89
+
90
+ # INICIAMOS EL LOG VISIBLE
91
+ log_visible = f"✅ URLs GENERADAS POR EL SISTEMA:\n\n🌍 FINCA RAÍZ:\n{url_fr}\n\n🌍 METROCUADRADO:\n{url_mc}\n\n--- PROCESANDO ---\n"
92
 
93
  with sync_playwright() as p:
94
  browser = p.chromium.launch(headless=True)
95
+ context = browser.new_context(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
 
 
96
 
97
+ # --- FINCA RAÍZ ---
98
  try:
99
  page = context.new_page()
100
  page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
101
+ for _ in range(3): page.mouse.wheel(0, 1000); time.sleep(1)
102
 
 
 
 
 
 
 
 
103
  wrappers = page.query_selector_all("div.lc-dataWrapper, article")
104
+ for w in wrappers[:5]:
 
105
  try:
106
+ txt = w.inner_text()
107
+ if "$" not in txt: continue
108
+ precios = [int(s) for s in txt.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
 
109
  if not precios: continue
110
 
111
+ href = w.evaluate("el => el.closest('a') ? el.closest('a').href : ''")
 
 
112
  resultados.append({
113
  "Portal": "Finca Raiz",
114
  "Precio": max(precios),
115
  "Precio_M2": max(precios) / area,
116
+ "Descripcion": txt.split('\n')[0][:60],
117
  "URL": href if href else url_fr
118
  })
119
  except: continue
120
  page.close()
121
  except Exception as e:
122
+ log_visible += f"⚠️ Error FR: {str(e)}\n"
123
 
124
+ # --- METROCUADRADO ---
125
  try:
126
  page = context.new_page()
127
  page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
128
+ for _ in range(3): page.mouse.wheel(0, 800); time.sleep(1)
129
 
130
+ # Selector Detail específico que pediste
131
+ details = page.query_selector_all("div.property-card__detail")
132
+ for d in details[:5]:
 
 
 
 
 
133
  try:
134
+ txt = d.inner_text()
135
+ precios = [int(s) for s in txt.replace('.', '').replace('$', '').split() if s.isdigit() and len(s) >= 6]
 
 
136
  if not precios: continue
137
 
138
+ # Búsqueda de link hacia arriba
139
+ href = d.evaluate("""el => {
140
+ let card = el.closest('li') || el.closest('div[class*="card"]');
141
+ return card && card.querySelector('a') ? card.querySelector('a').href : '';
 
 
 
 
 
 
142
  }""")
143
 
 
 
 
 
144
  resultados.append({
145
  "Portal": "Metrocuadrado",
146
  "Precio": max(precios),
147
  "Precio_M2": max(precios) / area,
148
+ "Descripcion": txt.split('\n')[0][:60],
149
  "URL": href if href else url_mc
150
  })
151
  except: continue
152
  page.close()
153
  except Exception as e:
154
+ log_visible += f"⚠️ Error MC: {str(e)}\n"
155
 
156
  browser.close()
157
 
158
  if not resultados:
159
+ return f"{log_visible}\n❌ NO SE ENCONTRARON RESULTADOS.", None, None
160
 
161
  df = pd.DataFrame(resultados)
162
 
163
+ # Generar PDF con las URLs
164
+ pdf_path = generar_pdf_con_fuentes(zona, df, url_fr, url_mc)
 
 
 
 
 
165
 
166
+ return f"{log_visible}\n✅ Éxito: {len(df)} inmuebles encontrados.", df, pdf_path
 
 
 
 
 
 
 
 
167
 
168
  # --- INTERFAZ ---
169
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
170
+ gr.Markdown("## 🤖 TramitIA Pro: Auditoría de URLs")
171
 
172
  with gr.Row():
173
  with gr.Column():
 
185
  ["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"],
186
  label="Antigüedad", value="1 a 8 años"
187
  )
188
+ btn = gr.Button("BUSCAR Y MOSTRAR URLS", variant="primary")
189
 
190
  with gr.Column():
191
+ # CAJA DE TEXTO GRANDE PARA VER LAS URLS
192
+ msg = gr.Textbox(label="🔍 Log de URLs Generadas (Copia y pega para probar)", lines=8)
193
  out_df = gr.Dataframe(label="Resultados")
194
+ out_pdf = gr.File(label="Reporte con Fuentes")
195
 
196
+ btn.click(motor_tramitia_debug, [z, c, a, t, h, b, p, e], [msg, out_df, out_pdf])
197
 
198
  demo.launch()