La Raya · backend (9): turismo — museos, dólmenes y castillos de la frontera sin pagar APIs

Publicado el 23/06/2026

Di por cerrada la serie del backend en la entrada anterior. Mentí sin querer: quedaba una sección que existía solo como esqueleto. El modelo PuntoTuristicoPage estaba ahí —tipo (monumento, museo, ruta, playa, parque, mirador), descripción, imagen, coordenadas— y su índice por localidad. Pero vacío. Ni un dato, ni un scraper que lo llenara. Esta es la entrada que faltaba.

El mismo dilema que con la gastronomía, pero peor

Con la gastronomía (capítulo 7) ya aprendí que las APIs de lugares cobran lo que de verdad quieres: descripción y fotos. Foursquare las marca como premium. Para una app local y gratuita, descartado. Me impuse la misma regla que entonces: solo fuentes abiertas y reutilizables. La diferencia es que el patrimonio está más disperso que los bares, repartido entre catálogos oficiales de dos países.

OpenStreetMap + Wikidata: el dúo que sustituye a Foursquare

Reutilicé el patrón del scraper de gastronomía sobre Overpass: consultar en un radio alrededor de cada localidad y asignar cada resultado a la más cercana por Haversine, descartando lo lejano. Una sola consulta me trae museos (tourism=museum), yacimientos y castillos (historic=*) e iglesias y ermitas (amenity=place_of_worship), y mapeo cada combinación al tipo del modelo.

OSM da el qué y el dónde. El cuéntame algo lo pone Wikidata, y aquí está la pieza que sustituye a Foursquare: muchos elementos traen su wikidata=Qxxxx, y con él resuelvo el artículo de Wikipedia —prefiriendo español, luego portugués— para sacar descripción e imagen libre de Wikimedia Commons. Si no hay artículo, genero una frase factual para no dejar nunca la descripción vacía.

El primer monumento que probé, el Castelo de Marvão, me llegó descrito en inglés porque su etiqueta de OSM apuntaba a en:. Cambié la resolución del idioma para ir por el sitelink de Wikidata en vez de fiarme de la etiqueta, y pasó a español, con foto, y gratis.

Las cuatro fuentes oficiales (y sus sorpresas)

OSM es enorme pero irregular. Para el patrimonio "con sello" investigué las fuentes oficiales de cada país. Salieron cosas muy distintas.

Portugal — el ArcGIS de la DGPC. No publica CSV, pero expone un servidor ArcGIS REST público que devuelve GeoJSON por HTTP, sin clave, y reproyecta a WGS84 él solo. Dos joyas: Endovélico (sítios arqueológicos: 14.000 en los distritos de frontera, con lat/lon directas) y Ulysses (patrimonio clasificado: monumentos, iglesias, castillos, como polígonos de los que calculo el centroide). Catorce mil yacimientos era demasiado ruido —mucho "mancha de ocupación" sin nada que ver—, así que filtré por tipo a lo visitable: antas, villas romanas, castillos, ermitas, necrópolis.

Extremadura — XLSX y un acertijo de husos. La Junta publica en abierto (CC-BY 4.0), pero su portal está caído y los ficheros viven en URLs directas. El de Bienes de Interés Cultural trae coordenadas… en UTM (ETRS89) sin decir el huso, y Extremadura cae justo entre el 29 y el 30. ¿Solución? Probar los dos y quedarme con el que caiga dentro de Extremadura:

def utm_etrs89_to_wgs84(x, y):
    for epsg in (25829, 25830):          # huso 29, huso 30
        lon, lat = transformer(epsg).transform(x, y)
        if dentro_de_extremadura(lat, lon):
            return lat, lon
    return None                          # coords corruptas (también las hay)

Los museos no traen coordenadas, así que los geocodifico por dirección con Nominatim, validando contra el centro del pueblo: cuando mandó un museo de Badajoz a las afueras de Ciudad Real, descarté el punto y caí al centroide de la localidad. Mejor aproximado que erróneo.

El truco que evita cinco castillos iguales

Cinco fuentes describiendo lo mismo iban a duplicar el Castelo de Marvão cinco veces. No lo resolví con un paso de limpieza, sino con un campo: cada punto guarda su nombre normalizado y se deduplica por (nombre, localidad). Si OSM ya creó el castillo, el BIC y la DGPC del mismo monumento se omiten solos. La dedup es una propiedad del modelo, no un parche.

Y abrir la puerta para la app

Por último, dejé el endpoint /api/turismo/ a la altura del de gastronomía: que devuelva latitud y longitud, que filtre los puntos marcados como excluidos y que sepa ordenar por cercanía. La app necesita esas coordenadas para lo que cuento en su serie: llevarte hasta un dolmen que no tiene dirección postal.

Como son datos de cambio lento, el scraping queda como una tarea semanal (lunes de madrugada), no cada hora como los eventos.

Lo que me llevo

  • La mejor API de pago es a veces dos gratuitas bien combinadas. OSM (geometría) + Wikidata (texto e imagen) cubren lo que Foursquare cobra.
  • Investiga la fuente antes de escribir el scraper. Asumí "datos abiertos = CSV con lat/lon". Acabé con un ArcGIS que reproyecta solo y un XLSX con coordenadas sin huso. Cada fuente impuso su propio parser.
  • Desconfía de las coordenadas. Husos sin declarar, geocodificaciones que se van a otra provincia, polígonos en vez de puntos: validar contra un bounding box o un centroide salva la sección.
  • La deduplicación se diseña, no se remienda. Un nombre normalizado en el modelo hizo que cinco fuentes convivieran sin pisarse.

Ahora sí, con el patrimonio dentro y la API lista, la pelota pasa a la app. Su serie cuenta cómo enseña todo esto y te pone la ruta hasta la puerta.