mapa

¿Cómo conseguir una BBDD de propiedades en Chile?

foco

Francisco Macaya

-
Follow me on
linkedin icongithub icon
Published on Jul 11, 2025

Actualmente, la industria inmobiliaria en Chile enfrenta una situación complicada debido a las altas tasas de interés y al significativo aumento del valor de la UF en los últimos años. Debido a esto, es relevante contar con la mayor cantidad de información posible sobre los precios de departamentos y viviendas para tomar decisiones. El Portal Inmobiliario es probablemente la fuente más confiable y extensa para obtener información sobre proyectos inmobiliarios. Sin embargo, existen otras alternativas que, aunque menos populares, pueden ofrecer una muestra representativa del comportamiento de precios y disponibilidad de proyectos en Chile.

Chilepropiedades es una página web donde se publican propiedades para comprar y arrendar en Chile. Esta plataforma proporciona información detallada sobre precios, número de dormitorios, baños y la ubicación de las propiedades. Este artículo entrega una API que permite extraer información de este sitio mediante web scraping, utilizando parámetros de entrada para la búsqueda de información, de la misma manera que lo haría un usuario en el navegador.

Es importante destacar que este proceso de web scraping depende de los elementos del HTML del sitio web. Si en el futuro se realizan modificaciones en el HTML, es probable que esta API falle o necesite ajustes para adaptarse a los cambios realizados, como ocurre con cualquier proceso de web scraping.

¿Cómo fue construida la base de datos?

ChilePropiedades es un portal que no tiene ningún elemento de JavaScript que obligue a usar Selenium (una biblioteca de Python más avanzada para web scraping), por lo que la extracción de la información es más simple y rápida. En este caso, se utilizó BeautifulSoup para extraer los elementos del HTML junto con sus valores. Para hacer esto, se tuvieron que entregar los parámetros de entrada en la búsqueda, de la misma manera que lo haría un usuario al buscar información en el sitio. Estos parámetros se muestran en la siguiente imagen:


El sitio web contiene tres parámetros de entrada, que son:

  • Tipo de Búsqueda: Es el tipo de búsqueda del usuario. Sus valores pueden ser arrendar, comprar o arriendo diario.
  • Tipo de Propiedad: Es el tipo de propiedad que el usuario quiere buscar en la página. Hay un conjunto variado de tipos de propiedades, desde bodegas hasta terrenos industriales. La gran mayoría de las propiedades se concentran en departamentos y casas.
  • Ubicación: Es la ubicación de la propiedad que se quiere buscar en el sitio. La ubicación puede ser desde comunas hasta regiones de Chile.

Adicionalmente, para poder extraer la información de forma cronológica, se construyeron dos parámetros más como inputs de la API, permitiendo extraer la información en rangos de tiempo específicos.

  • Fecha mínima de publicación: Define la fecha a partir de la cual se quiere comenzar a extraer la información de las propiedades publicadas.
  • Fecha máxima de publicación: Define la fecha hasta la cual se quiere extraer la información de las propiedades publicadas.

Con todos estos parámetros, se construyó una función en Python que extrae la información de cada una de las propiedades de la siguiente manera:

1class GetDataChilePropiedades:
2
3def __init__(self,region,
4 type_searching,
5 type_house,
6 min_publish_date,
7 max_publish_date):
8 self.region = region
9 self.type_searching = type_searching
10 self.type_house = type_house
11 self.min_publish_date = min_publish_date
12 self.max_publish_date = max_publish_date
13
14def getdata(self):
15
16 extracted_data = []
17 url = 'https://chilepropiedades.cl/propiedades/{}/{}/{}/0'.
18 format(self.type_searching, self.type_house,self.region)
19
20 headers = {
21 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)
22 AppleWebKit/537.36
23 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
24 }
25 response = requests.get(url, headers=headers)
26 soup = BeautifulSoup(response.content, 'html.parser')
27 element_page = soup.find_all('div',
28 class_='clp-results-text-container d-none d-sm-block col-sm-6 text-right')
29
30 max_count_page = element_page[0].text
31 pattern = r"Total de páginas:\s+(\d+)"
32 match = re.search(pattern, max_count_page)
33 if match:
34 total_pages = match.group(1)
35 else:
36 return {
37 'response': extracted_data,
38 'status': True
39 }
40
41 table_count_page = [i for i in range(int(total_pages))]
42
43 for page in table_count_page:
44 try:
45 url =
46
47 headers = {
48 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)
49 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
50 }
51 response = requests.get(url, headers=headers)
52 soup = BeautifulSoup(response.content, 'html.parser')
53 element_page = soup.find_all('p',
54 class_='mt-3 p-3 clp-highlighted-container text-center')
55
56 time.sleep(3)
57
58 if element_page != []:
59 continue
60
61 # Find all the publication elements
62 elements = soup.find_all('div', class_='clp-publication-list')
63 list_element_public = elements[0].find_all('div',
64 class_='clp-publication-element clp-highlighted-container')
65
66
67 # Iterate over each publication element
68 for element in list_element_public:
69
70 try:
71 date_publish = element.find('div', class_='text-center clp-publication-date').text.strip()
72 date_publish_datetime = datetime.strptime(date_publish, "%d/%m/%Y")
73 date_publish_datetime = str(date_publish_datetime)[:10]
74 except:
75 date_publish_datetime = '1990-01-01'
76
77 ## Filter by date
78 if (date_publish_datetime >= self.min_publish_date) and (date_publish_datetime <= self.max_publish_date):
79
80 # Images
81 try:
82 img_publish_list = element.find('a', class_='clp-listing-image-link')
83 img_element = img_publish_list.find('picture').find('img')['src']
84 except:
85 img_element = ''
86
87 # Extract rooms
88 try:
89 rooms_span = element.find('span', title='Habitaciones')
90 rooms = rooms_span.find('span', class_='clp-feature-value').text if rooms_span else None
91 except:
92 rooms = 0
93
94 # Extract bathrooms
95 try:
96 bathrooms_span = element.find('span', title='Baños')
97 bathrooms = bathrooms_span.find('span', class_='clp-feature-value').text if bathrooms_span
98 else None
99 except:
100 bathrooms = 0
101
102 # Extract value price
103 try:
104 value_spans = element.find_all('span', class_='clp-value-container', attrs={'valueunit': '1'})
105 value_price_clp = value_spans[1].text.strip() if len(value_spans) > 1 else None
106 except:
107 value_price_clp = 0
108
109 # Extract value currency
110 try:
111 value_spans = element.find_all('span', class_='clp-value-container', attrs={'valueunit': '1'})
112 value_price_currency_clp = value_spans[0].text.strip() if len(value_spans) > 1 else None
113 except:
114 value_price_currency_clp = 0
115
116 # Extract value price
117 try:
118 value_spans = element.find_all('span', class_='clp-value-container', attrs={'valueunit': '3'})
119 value_price_uf = value_spans[1].text.strip() if len(value_spans) > 1 else None
120 except:
121 value_price_uf = 0
122
123 # Extract value currency
124 try:
125 value_spans = element.find_all('span', class_='clp-value-container', attrs={'valueunit': '3'})
126 value_price_currency_uf = value_spans[0].text.strip() if len(value_spans) > 1 else None
127 except:
128 value_price_currency_uf = 0
129
130 # Extract parking lots
131 try:
132 parking_span = element.find('span', title='Estacionamientos')
133 parking = parking_span.find('span', class_='clp-feature-value').text if parking_span else None
134 except:
135 parking = 0
136
137 try:
138 h2_element = element.find('h2', class_='publication-title-list')
139 a_tag = h2_element.find('a') if h2_element else None
140 href = a_tag['href'] if a_tag else None
141 url_element = 'https://chilepropiedades.cl' + href
142 except:
143 url_element = ''
144
145 try:
146 data_element = element.find('div', class_='d-md-flex mt-2 align-items-center')
147 data_element = data_element.find('h3', class_='sub-codigo-data').text.split('/')
148 type_action = data_element[0].strip()
149 type_property = data_element[1].strip()
150 type_province = data_element[2].strip()
151 except:
152 type_action = ''
153 type_property = ''
154 type_province = ''
155
156 # code publication
157 try:
158 code_publication = element.find('div', class_='d-md-flex mt-2 align-items-center')
159 code_publication = code_publication.find('span', class_='light-bold').next_sibling.strip()
160 except:
161 code_publication = ''
162
163 # get latitude and longitude
164 response = requests.get(url_element, headers=headers)
165 time.sleep(3)
166 soup = BeautifulSoup(response.content, 'html.parser')
167 script_element = soup.find_all('script')
168
169 for i in range(len(script_element)):
170 location_pattern = r'var publicationLocation = \[\s*(-?\d+\.\d+),\s*(-?\d+\.\d+)\s*\];'
171 matches = re.findall(location_pattern, str(script_element[i]))
172 if matches:
173 latitude, longitude = matches[0]
174 break
175 else:
176 latitude = None
177 longitude = None
178
179 # Add to the list
180 extracted_data.append({
181 'rooms': rooms,
182 'bathrooms': bathrooms,
183 'value_price_clp': value_price_clp,
184 'value_price_currency_clp': value_price_currency_clp,
185 'value_price_uf': value_price_uf,
186 'value_price_currency_uf': value_price_currency_uf,
187 'parking': parking,
188 'url': url_element,
189 'type_action': type_action,
190 'type_property': type_property,
191 'type_province': type_province,
192 'latitude': latitude,
193 'longitude': longitude,
194 'page': page,
195 'region': self.region,
196 'type_searching': self.type_searching,
197 'type_house': self.type_house,
198 'date_publish': date_publish,
199 'code_publication': code_publication,
200 'image_picture': img_element,
201 'web': 'chilepropiedades',
202 })
203 elif (date_publish_datetime < self.min_publish_date):
204 return {
205 'response': extracted_data,
206 'status': True
207 }
208 except:
209 pass
210 return {
211 'response': extracted_data,
212 'status': True
213 }

Este código contiene la lógica de la extracción de la información desde la página, dividiéndose en dos principales secciones.

  • Página por página: Para esta sección, se utiliza la biblioteca requests y los parámetros del usuario para extraer la información del html resultante de la consulta del usuario. El sitio web genera URLs paramétricas según la búsqueda del usuario, donde cada página de búsqueda si diferencia por el numero de pagina, sin haber elementos adicionales para su interpretación. La URL estandar de busqueda es:
1'https://chilepropiedades.cl/propiedades/{}/{}/{}/{}'.
2format(self.type_searching, self.type_house, self.region,
3page)

Cualquier búsqueda del usuario empieza en la página 0, y dependiendo de la cantidad de propiedades publicadas, dependerá la máxima cantidad de páginas. Esta información se encuentra al final de la pagina inicial, en el elemento que dice: “Total de páginas: X”. Para extraer todo las propiedades se realizo un ciclo for, el cual recorre todas las páginas de la búsqueda del usuario.

  • Información de cada propiedad: Luego de haber extraído el HTML de la página, se procede a extraer la información de cada una de las propiedades, mediante una iteración de las propiedades obtenidas de la pagina. En esta parte, se identificaron cada uno de los características de una propiedad, tal como: precio, número de baños, habitaciones, moneda, estacionamientos, imágenes, código de publicación, latitud y longitud.

Junto con esto, el codigo en python incluye una manera para poder extraer solo propiedades entre rango de fecha, permitiendo extraer propiedades que fueron publicadas entre dos periodos de tiempo especificos.

¿Cómo empaquetar la función dentro de una API?

Para poder dejar esta función dentro de un componente más estable, se construyó un contenedor con API Flask, el cual almacena la función construida en un endpoint llamado webscraping. Esta implementación recibe los parametros region, type_searching, type_house, min_publish_date y max_publish_date, los cuales son los mismos que se utilizan en la función de Python.

Para poder levantar el contenedor, se debe tener instalado Docker y ejecutar los siguientes comandos:

1docker build -t my-flask-app .
2docker run -d -p 8000:8000 my-flask-app

Se habilitará un endpoint en el puerto 8000 con el nombre de "/webscrapping", el cual puede probarse usando las siguientes lineas de código:

1import requests
2
3url = "http://localhost:8000/webscrapping"
4data = {
5"region": "santiago",
6"type_searching": "arriendo-mensual",
7"type_house": "departamento",
8"min_publish_date": "2024-05-23",
9"max_publish_date": "2024-05-24"
10}
11
12response = requests.post(url, json=data)

Conclusiones

Espero que esta implementación le pueda servir a alguien que esté buscando información de propiedades. Sabiendo que esto es una implementación de web scraping, que depende mucho del HTML, tiene sus pro y contra, ya que es una manera factible de poder extraer información del mercado inmobiliario hoy en día, pero dependerá mucho de los cambios del sitio . Si quiere tener mayores detalles del código, no dude en revisar el repositorio del código.

Si quiere tener mayores detalles del código, no dude en revisar el repositorio del código.

Repositorio:  Github

Ir arriba