En este tutorial, veremos:
- La definición de un raspador de Google Maps
- Qué datos se pueden extraer con él
- Cómo crear un script de raspado de Google Maps con Python
¡Vamos allá!
¿Qué es un raspador de Google Maps?
Un raspador de Google Maps es una herramienta especializada para extraer datos de Google Maps. Automatiza el proceso de recopilación de datos de Maps, por ejemplo, mediante un script de raspado de Python. Los datos recuperados con un raspador de este tipo se utilizan comúnmente para estudios de mercado, análisis de la competencia y mucho más.
Qué datos se pueden recuperar de Google Maps
La información que se puede extraer de Google Maps incluye:
- Nombre de la empresa: el nombre de la empresa o ubicación que figura en Google Maps.
- Dirección: dirección física de la empresa o ubicación.
- Número de teléfono: número de teléfono de contacto de la empresa.
- Sitio web: URL del sitio web de la empresa.
- Horario comercial: horarios de apertura y cierre de la empresa.
- Reseñas: opiniones de clientes, incluidas las valoraciones y comentarios detallados.
- Valoraciones: valoración media por estrellas basada en los comentarios de los usuarios.
- Fotos: imágenes subidas por la empresa o los clientes.
Instrucciones paso a paso para raspar Google Maps con Python
En esta sección guiada, aprenderás a crear un script de Python para raspar Google Maps.
El objetivo final es recuperar los datos contenidos en los elementos de Google Maps de la página «Restaurantes italianos»:
¡Sigue los pasos que se indican a continuación!
Paso 1: configuración del proyecto
Antes de empezar, comprueba que Python 3 está instalado en tu equipo. Si no lo está, descárgalo, instálalo y sigue las instrucciones del asistente de instalación.
Luego, usa los siguientes comandos para crear una carpeta para el proyecto, introdúcela y crea un entorno virtual en su interior:
mkdir google-maps-scraper
cd google-maps-scraper
python -m venv env
El directorio google-maps-scraper
representa la carpeta del proyecto del raspador Python Google Maps.
Carga la carpeta del proyecto en tu IDE de Python favorito. PyCharm Community Edition o Visual Studio Code con la extensión de Python serán suficientes.
Dentro de la carpeta del proyecto, crea un archivo scraper.py.
Esta es la estructura de archivos que tu proyecto debería tener ahora mismo:scraper.py
ahora es un script de Python en blanco, pero pronto contendrá la lógica de raspado.
En la terminal del IDE, activa el entorno virtual. Para hacerlo, en Linux o macOS, ejecuta este comando:
./env/bin/activate
Como alternativa, en Windows, ejecuta:
env/Scripts/activate
Genial, ¡ahora tienes un entorno Python para tu raspador!
Paso 2: elige la biblioteca de raspado
Google Maps es una plataforma altamente interactiva, por lo que no tiene sentido dedicar tiempo a determinar si se trata de un sitio estático o dinámico. En casos como este, el mejor método para el raspado es usar una herramienta de automatización del navegador.
Si no conoces esta tecnología, las herramientas de automatización del navegador te permiten renderizar e interactuar con páginas web en un entorno de navegador controlable. Además, no es fácil crear una URL de búsqueda válida en Google Maps. La forma más sencilla de gestionarlo es realizar la búsqueda directamente en un navegador.
Una de las mejores herramientas de automatización de navegadores para Python es Selenium, por lo que es una opción ideal para raspar Google Maps. Prepárate para instalarlo, ¡ya que será la biblioteca principal utilizada para esta tarea!
Paso 3: instala y configura la biblioteca de raspado
Instala Selenium a través del paquete pip selenium
con este comando en un entorno virtual de Python activado:
pip install selenium
Para obtener más información sobre cómo utilizar esta herramienta, sigue nuestro tutorial sobre raspado web con Selenium.
Importa Selenium en scraper.py
y crea un objeto WebDriver
para controlar una instancia de Chrome en modo «headless»:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
El fragmento anterior inicializa una instancia de Chrome WebDriver
para controlar una ventana del navegador Chrome mediante programación. La marca --headless
sirve para iniciar Chrome en modo «headless», que lo abre en segundo plano sin cargar la ventana. Para la depuración, puedes comentar esta línea para ver las acciones del script en tiempo real.
Como última línea del script de raspado de Google Maps, no olvides cerrar el controlador web:
driver.quit()
¡Increíble! Ya tienes todo configurado para empezar a raspar páginas de Google Maps.
Paso 4: conecta con la página de destino
Usa el método get()
para conectarte a la página de inicio de Google Maps:
driver.get("https://www.google.com/maps")
En este momento, scraper.py
contendrá estas líneas:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
# connect to the Google Maps home page
driver.get("https://www.google.com/maps")
# scraping logic...
# close the web browser
driver.quit()
Genial, ¡es hora de empezar a crear un sitio web dinámico como Maps!
Paso 5: gestiona el cuadro de diálogo sobre cookies del RGPD
Nota: si no te encuentras en la UE (Unión Europea), puedes omitir este paso.
Ejecuta el script scraper.py
en modo «headed» y añade un punto de interrupción antes de la última línea, si es posible. De esta forma, la ventana del navegador permanecerá abierta, lo que te permitirá observarla sin que se cierre inmediatamente. Si estás en la UE, verás algo como esto:
Nota: el mensaje «Chrome is being controlled by automated test software.» («El software de pruebas automatizadas controla Chrome») confirma que Selenium está controlando Chrome correctamente.
Google tiene que mostrar algunas opciones de política de cookies a los usuarios de la UE dados los requisitos del RGPD. Si es tu caso, tendrás que seleccionar esta opción si quieres interactuar con la página. Si no es tu caso, puedes pasar al paso 6.
Echa un vistazo a la URL en la barra de direcciones del navegador y verás que no se corresponde con la página especificada en get()
. Esto se debe a que Google te ha redirigido. Tras hacer clic en el botón «Aceptar todo», volverás a la página de destino, que es la página de inicio de Google Maps.
Para gestionar las opciones del RGPD, abre la página de inicio de Google Maps en modo incógnito en el navegador y espera a que se redirija. Haz clic con el botón derecho del ratón en el botón «Aceptar todo» y selecciona la opción «Inspeccionar»:
Como habrás advertido, las clases CSS de los elementos HTML de la página parecen generarse aleatoriamente. Por ello, no son fiables para el raspado web, ya que es probable que se actualicen con cada implementación. Por tanto, debes centrarte en apuntar a atributos más estables, como aria-label
:
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
find_element()
es un método en Selenium utilizado para localizar elementos HTML en una página utilizando varias estrategias. En este caso, utilizamos un selector CSS. Si quieres obtener más información sobre los diferentes tipos de selectores, lee nuestro artículo XPath frente a selector CSS.
No olvides importar By
añadiendo esta importación a scraper.py
:
from selenium.webdriver.common.by import By
A continuación, pulsa el botón:
accept_button.click()
A continuación se muestra cómo encaja todo para gestionar la página opcional de cookies de Google:
try:
# select the "Accept all" button from the GDPR cookie option page
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
# click it
accept_button.click()
except NoSuchElementException:
print("No GDPR requirenments")
El comando click()
pulsa el botón «Aceptar todo» para que Google te redirija a la página de inicio de Maps. Si no te encuentras en la UE, este botón no aparecerá en la página, lo que dará lugar a una NoSuchElementException
. El script detectará la excepción y procederá, ya que no se trata de un error crítico, sino de un escenario posible.
Asegúrate de importar NoSuchElementException
:
from selenium.common import NoSuchElementException
¡Excelente! Ya puedes concentrarte en raspar Google Maps.
Paso 6: envía el formulario de búsqueda
Ahora, tu raspador de Google Maps debería llegar a una página como la siguiente:
Ten en cuenta que la ubicación en los mapas depende de la ubicación de tu IP. En este ejemplo, nos encontramos en Nueva York.
A continuación, debes rellenar el campo «Buscar en Google Maps» y enviar el formulario de búsqueda. Para localizar este elemento, abre la página principal de Google Maps en modo incógnito en tu navegador. Haz clic con el botón derecho del ratón en el campo de búsqueda y elige la opción «Inspeccionar»:
search_input = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#searchboxinput"))
)
WebDriverWait
es una clase de Selenium especializada que detiene el script hasta que se cumpla una condición específica en la página. En el ejemplo anterior, espera hasta 5 segundos a que aparezca el elemento HTML de entrada. Esta espera garantiza que la página se haya cargado por completo, lo cual es obligatorio si has seguido el paso 5 (debido a la redirección).
Para que las líneas anteriores funcionen, añade estas importaciones a scraper.py
:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
Next, fill out the input with the `[send_keys()](https://www.selenium.dev/documentation/webdriver/actions_api/keyboard/#send-keys)` method:
search_query = "italian restaurants"
search_input.send_keys(search_query)
En este caso, la consulta de búsqueda es «restaurantes italianos», pero puedes buscar cualquier otro término.
Solo queda enviar el formulario. Revisa el botón de envío, que es una lupa:
Selecciónalo segmentando su atributo aria-label
y haz clic en él:
search_button = driver.find_element(By.CSS_SELECTOR, "button[aria-label=\"Search\"]")
search_button.click()
¡Increíble! Ahora el navegador controlado cargará los datos para rasparlos.
Paso 7: selecciona los elementos de Google Maps
Aquí es donde debería estar tu script actualmente:
Los datos que se van a raspar se encuentran en los elementos de Google Maps de la izquierda. Como se trata de una lista, la mejor estructura de datos para contener los datos extraídos es una matriz. Inicializa uno:
items = []
El objetivo ahora es seleccionar el elemento de Google Maps de la izquierda. Inspecciona uno de ellos:
De nuevo, las clases CSS parecen generarse de forma aleatoria, por lo que no son fiables para el raspado. En su lugar, puedes segmentar el atributo jsaction
. Dado que algunas partes del contenido de este atributo también aparecen generadas de forma aleatoria, céntrate en la cadena coherente que contiene, en concreto, "mouseover:pane"
.
El selector XPath que aparece a continuación te ayudará a seleccionar todos los elementos <div>
dentro del elemento padre <div>
que contiene role="feed"
, cuyo atributo jsaction
contiene la cadena "mouseover:pane":
maps_items = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[@role="feed"]//div[contains(@jsaction, "mouseover:pane")]'))
)
De nuevo, se requiere WebDriverWait
ya que el contenido de la izquierda se carga dinámicamente en la página.
Itera sobre elemento y prepara tu raspador de Google Maps para extraer datos:
for maps_item in maps_items:
# scraping logic...
¡Fantástico! El siguiente paso es extraer los datos de esos elementos.
Paso 8: raspa los elementos de Google Maps
Inspecciona un único elemento de Google Maps y céntrate en los elementos que contiene:
Aquí puedes ver que puedes raspar:
- El enlace al elemento Maps desde el elemento
a[jsaction][jslog]
- El título del elemento
div.fontHeadlineSmall
- Las estrellas y el número de reseñas de
span[role="img"]
Puedes lograrlo con la siguiente lógica:
link_element = maps_item.find_element(By.CSS_SELECTOR, "a[jsaction][jslog]")
url = link_element.get_attribute("href")
title_element = maps_item.find_element(By.CSS_SELECTOR, "div.fontHeadlineSmall")
title = title_element.text
reviews_element = maps_item.find_element(By.CSS_SELECTOR, "span[role=\"img\"]")
reviews_string = reviews_element.get_attribute("aria-label")
# define a regular expression pattern to extract the stars and reviews count
reviews_string_pattern = r"(\d+\.\d+) stars (\d+[,]*\d+) Reviews"
# use re.match to find the matching groups
reviews_string_match = re.match(reviews_string_pattern, reviews_string)
reviews_stars = None
reviews_count = None
# if a match is found, extract the data
if reviews_string_match:
# convert stars to float
reviews_stars = float(reviews_string_match.group(1))
# convert reviews count to integer
reviews_count = int(reviews_string_match.group(2).replace(",", ""))
La función get_attribute()
devuelve el contenido del atributo HTML especificado, mientras que .text
devuelve el contenido de la cadena dentro del nodo.
Ten en cuenta el uso de una expresión regular para extraer los campos de datos específicos de la cadena “X.Y stars in Z reviews”. Obtén más información en nuestro artículo sobre el uso de regex para el raspado web.
No olvides importar el paquete re
de la biblioteca estándar de Python:
import re
Continúa inspeccionando el elemento de Google Maps:
Dentro del <div>
con la clase fondBodyMedium
, puedes obtener la mayor parte de la información de nodos <span>
sin atributos o solo con el atributo style
. En cuanto al elemento de precios opcional, puedes seleccionarlo dirigiéndote al nodo que contiene “Price” en el atributo aria-label
:
info_div = maps_item.find_element(By.CSS_SELECTOR, ".fontBodyMedium")
# scrape the price, if present
try:
price_element = info_div.find_element(By.XPATH, ".//*[@aria-label[contains(., 'Price')]]")
price = price_element.text
except NoSuchElementException:
price = None
info = []
# select all <span> elements with no attributes or the @style attribute
# and descendant of a <span>
span_elements = info_div.find_elements(By.XPATH, ".//span[not(@*) or @style][not(descendant::span)]")
for span_element in span_elements:
info.append(span_element.text.replace("⋅", "").strip())
# to remove any duplicate info and empty strings
info = list(filter(None, list(set(info))))
Dado que el elemento del precio es opcional, debes envolver esa lógica con un bloque try... except
. De esta forma, si el nodo del precio no está en la página, el script continuará sin fallar. Si omites el paso 5, añade la importación para NoSuchElementException
:
from selenium.common import NoSuchElementException
Para evitar cadenas vacías y elementos de información duplicados, ten en cuenta el uso de filter()
y set()
.
Ahora, concéntrate en la imagen:
Puedes rasparla con:
img_element = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img[decoding=\"async\"][aria-hidden=\"true\"]"))
)
image = img_element.get_attribute("src")
Ten en cuenta que se requiere WebDriverWait,
ya que las imágenes se cargan de forma asincrónica y pueden tardar un poco en aparecer.
El último paso es raspar las etiquetas del elemento de abajo:
Puedes recuperarlos todos de los nodos <span>
con el atributo style
en el último elemento .fontBodyMedium
:
tags_div = maps_item.find_elements(By.CSS_SELECTOR, ".fontBodyMedium")[-1]
tags = []
tag_elements = tags_div.find_elements(By.CSS_SELECTOR, "span[style]")
for tag_element in tag_elements:
tags.append(tag_element.text)
¡Estupendo! La lógica de raspado de Google Maps de Python está completa.
Paso 9: recopila los datos extraídos
Ahora tienes los datos extraídos en varias variables. Crea un nuevo objeto de elemento
y rellénalo con esos datos:
item = {
"url": url,
"image": image,
"title": title,
"reviews": {
"stars": reviews_stars,
"count": reviews_count
},
"price": price,
"info": info,
"tags": tags
}
Luego, añádelo a la matriz de elementos
:
items.append(item)
Al final del bucle for
en los nodos de elementos de Google Maps, los elementos
contendrán todos tus datos de raspado. Solo tienes que exportar esa información a un archivo legible por humanos, como CSV.
Paso 10: exporta a CSV
Importa el paquete csv
de la biblioteca estándar de Python:
import csv
A continuación, utilízalo para rellenar un archivo CSV plano con tus datos de Google Maps:
# output CSV file path
output_file = "items.csv"
# flatten and export to CSV
with open(output_file, mode="w", newline="", encoding="utf-8") as csv_file:
# define the CSV field names
fieldnames = ["url", "image", "title", "reviews_stars", "reviews_count", "price", "info", "tags"]
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
# write the header
writer.writeheader()
# write each item, flattening info and tags
for item in items:
writer.writerow({
"url": item["url"],
"image": item["image"],
"title": item["title"],
"reviews_stars": item["reviews"]["stars"],
"reviews_count": item["reviews"]["count"],
"price": item["price"],
"info": "; ".join(item["info"]),
"tags": "; ".join(item["tags"])
})
El fragmento anterior exporta los elementos
a un archivo CSV llamado items.csv
. Las principales funciones utilizadas son:
open()
: abre el archivo especificado en modo escritura con codificación UTF-8 para gestionar la salida de texto.CSV.DictWriter()
: crea un objeto de escritura CSV con nombres de campo especificados, lo que permite escribir las filas como diccionarios.writeheader()
: escribe la fila del encabezado en el archivo CSV según los nombres de los campos.writer.writerow()
: escribe cada elemento como una fila en el CSV.
Ten en cuenta el uso de la función de cadena join()
para transformar las matrices en cadenas planas. Esto garantiza que el CSV de salida sea un archivo limpio de un solo nivel.
Paso 11: reúnelo todo
Este es el código final del raspador Python de Google Maps:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.common import NoSuchElementException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re
import csv
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
# connect to the Google Maps home page
driver.get("https://www.google.com/maps")
# to deal with the option GDPR options
try:
# select the "Accept all" button from the GDPR cookie option page
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
# click it
accept_button.click()
except NoSuchElementException:
print("No GDPR requirenments")
# select the search input and fill it in
search_input = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#searchboxinput"))
)
search_query = "italian restaurants"
search_input.send_keys(search_query)
# submit the search form
search_button = driver.find_element(By.CSS_SELECTOR, "button[aria-label=\"Search\"]")
search_button.click()
# where to store the scraped data
items = []
# select the Google Maps items
maps_items = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[@role="feed"]//div[contains(@jsaction, "mouseover:pane")]'))
)
# iterate over the Google Maps items and
# perform the scraping logic
for maps_item in maps_items:
link_element = maps_item.find_element(By.CSS_SELECTOR, "a[jsaction][jslog]")
url = link_element.get_attribute("href")
title_element = maps_item.find_element(By.CSS_SELECTOR, "div.fontHeadlineSmall")
title = title_element.text
reviews_element = maps_item.find_element(By.CSS_SELECTOR, "span[role=\"img\"]")
reviews_string = reviews_element.get_attribute("aria-label")
# define a regular expression pattern to extract the stars and reviews count
reviews_string_pattern = r"(\d+\.\d+) stars (\d+[,]*\d+) Reviews"
# use re.match to find the matching groups
reviews_string_match = re.match(reviews_string_pattern, reviews_string)
reviews_stars = None
reviews_count = None
# if a match is found, extract the data
if reviews_string_match:
# convert stars to float
reviews_stars = float(reviews_string_match.group(1))
# convert reviews count to integer
reviews_count = int(reviews_string_match.group(2).replace(",", ""))
# select the Google Maps item <div> with most info
# and extract data from it
info_div = maps_item.find_element(By.CSS_SELECTOR, ".fontBodyMedium")
# scrape the price, if present
try:
price_element = info_div.find_element(By.XPATH, ".//*[@aria-label[contains(., 'Price')]]")
price = price_element.text
except NoSuchElementException:
price = None
info = []
# select all <span> elements with no attributes or the @style attribute
# and descendant of a <span>
span_elements = info_div.find_elements(By.XPATH, ".//span[not(@*) or @style][not(descendant::span)]")
for span_element in span_elements:
info.append(span_element.text.replace("⋅", "").strip())
# to remove any duplicate info and empty strings
info = list(filter(None, list(set(info))))
img_element = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img[decoding=\"async\"][aria-hidden=\"true\"]"))
)
image = img_element.get_attribute("src")
# select the tag <div> element and extract data from it
tags_div = maps_item.find_elements(By.CSS_SELECTOR, ".fontBodyMedium")[-1]
tags = []
tag_elements = tags_div.find_elements(By.CSS_SELECTOR, "span[style]")
for tag_element in tag_elements:
tags.append(tag_element.text)
# populate a new item with the scraped data
item = {
"url": url,
"image": image,
"title": title,
"reviews": {
"stars": reviews_stars,
"count": reviews_count
},
"price": price,
"info": info,
"tags": tags
}
# add it to the list of scraped data
items.append(item)
# output CSV file path
output_file = "items.csv"
# flatten and export to CSV
with open(output_file, mode="w", newline="", encoding="utf-8") as csv_file:
# define the CSV field names
fieldnames = ["url", "image", "title", "reviews_stars", "reviews_count", "price", "info", "tags"]
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
# write the header
writer.writeheader()
# write each item, flattening info and tags
for item in items:
writer.writerow({
"url": item["url"],
"image": item["image"],
"title": item["title"],
"reviews_stars": item["reviews"]["stars"],
"reviews_count": item["reviews"]["count"],
"price": item["price"],
"info": "; ".join(item["info"]),
"tags": "; ".join(item["tags"])
})
# close the web browser
driver.quit()
Con alrededor de 150 líneas de código, ¡acabas de crear un script de raspado para Google Maps!
Comprueba que funciona iniciando el archivo scraper.py
. En Windows, ejecuta el raspador con:
python scraper.py
De manera equivalente, en Linux o macOS, ejecuta:
python3 scraper.py
Espera a que el raspador acabe de ejecutarse y aparecerá un archivo items.csv
en el directorio raíz de tu proyecto. Abre el archivo para ver los datos extraídos, que deben contener datos como los siguientes:
Enhorabuena: ¡misión cumplida!
Conclusión
En este tutorial, has aprendido qué es un raspador de Google Maps y cómo crear uno en Python. Como has visto, crear un script sencillo para recuperar automáticamente datos de Google Maps solo requiere unas pocas líneas de código Python.
Si bien la solución funciona para proyectos pequeños, no es práctica para el raspado a gran escala. Esto se debe a que Google cuenta con medidas antibots avanzadas, como CAPTCHA y prohibiciones de IP, que pueden bloquearte. Ampliar el proceso a varias páginas también aumentaría los costes de infraestructura. Además, este sencillo ejemplo no tiene en cuenta todas las interacciones complejas necesarias en las páginas de Google Maps.
¿Significa eso que es imposible raspar Google Maps de manera eficiente y fiable? ¡En absoluto! Solo necesitas una solución avanzada como la API de raspado para Google Maps de Bright Data.
La API de raspado para Google Maps ofrece puntos finales para recuperar datos de Google Maps, olvidándose de los retos principales. Con sencillas llamadas a la API, puedes obtener los datos que necesites en formato JSON o HTML. Si las llamadas a la API no son lo tuyo, también puedes explorar nuestros conjuntos de datos de Google Maps listos para usar.
¡Crea una cuenta gratuita de Bright Data hoy mismo para probar nuestras API de raspado o explorar nuestros conjuntos de datos!
No se requiere tarjeta de crédito