Fried Chicken Wars — Mapeo de Popeyes vs KFC Territory usando Felt

Recientemente, salí a almorzar con un amigo que acababa de regresar a Orlando. Mientras hablaba sobre cómo estaba evolucionando la ciudad, le mencioné que veía abrir nuevos restaurantes Popeyes en todos los lugares a los que iba. Desde su punto de vista, había estado viendo la apertura de nuevos restaurantes KFC en todas partes. Sabía que no podíamos tener razón los dos. Terminamos debatiendo qué cadena de pollo frito tiene una participación de mercado más dominante en los Estados Unidos.
Como resultado de este acalorado debate, me dispuse a responder la pregunta mediante la recopilación de datos y la creación de una visualización de mapa. A continuación, lo guiaré a través de mi proceso.
- Recolectando los datos
El primer desafío que tuve fue encontrar una fuente confiable de datos para las ubicaciones de las tiendas de Popeyes y KFC. Encontré un sitio web llamado scrapehero.com que me ofreció venderme los datos por un precio de $95 por el conjunto de datos de una cadena. Sin embargo, no estaba dispuesto a gastar aproximadamente $200 por fanfarronear contra mi amigo. Después de explorar el sitio web de Popeyes, se me ocurrió una idea. Me pregunto si podría aplicar ingeniería inversa a la API (también conocido como Web Scraping).
El primer paso fue abrir las herramientas de desarrollo haciendo clic con el botón derecho del mouse e inspeccionando. Procedí a navegar a la pestaña Red y eché un vistazo a todas las solicitudes entrantes. Después de investigar un poco, descubrí que la solicitud se usaba para obtener y completar las ubicaciones de las tiendas en la interfaz de usuario.

Puede copiar la solicitud cURL e importarla en Postman y Postman se encarga de todo el formato por usted. Además, Postman tiene una opción en la que puede exportar la solicitud al idioma de su elección.


Después de este paso, escribí un script aproximado que enviaría las solicitudes y las guardaría en un CSV en mi computadora. Las cosas clave que noté con la solicitud que se estaba utilizando para obtener todas las ubicaciones de las tiendas están dentro de la carga útil, tenía algunas variables con las que podía jugar, incluidas: userLat, userLng, searchRadius y first. Al modificar estas variables, pude recopilar los datos de las 2851 ubicaciones de las tiendas. El usuario de Github, meiqimichelle, hizo un trabajo pesado al proporcionar las coordenadas de latitud y longitud de cada estado por el que pasaría. Adoptaría un enfoque similar con la recopilación de datos de la tienda KFC.
const axios = require('axios');
const ObjectsToCsv = require('objects-to-csv');
const states = require('./constants');
function getPopeyesLocations(){
const promises = []
for( let i = 0; i < states.length; i++){
const state = states[i]
const { latitude : lat, longitude : lon } = state
var data = JSON.stringify([
{
"operationName": "GetRestaurants",
"variables": {
"input": {
"filter": "NEARBY",
"coordinates": {
"userLat": lat,
"userLng": lon,
"searchRadius": 800000
},
"first": 10000,
"status": "OPEN"
}
},
"query": "query GetRestaurants($input: RestaurantsInput) {\n restaurants(input: $input) {\n pageInfo {\n hasNextPage\n endCursor\n __typename\n }\n totalCount\n nodes {\n ...RestaurantNodeFragment\n __typename\n }\n __typename\n }\n}\n\nfragment RestaurantNodeFragment on RestaurantNode {\n _id\n storeId\n isAvailable\n posVendor\n chaseMerchantId\n curbsideHours {\n ...OperatingHoursFragment\n __typename\n }\n deliveryHours {\n ...OperatingHoursFragment\n __typename\n }\n diningRoomHours {\n ...OperatingHoursFragment\n __typename\n }\n distanceInMiles\n drinkStationType\n driveThruHours {\n ...OperatingHoursFragment\n __typename\n }\n driveThruLaneType\n email\n environment\n franchiseGroupId\n franchiseGroupName\n frontCounterClosed\n hasBreakfast\n hasBurgersForBreakfast\n hasCatering\n hasCurbside\n hasDelivery\n hasDineIn\n hasDriveThru\n hasTableService\n hasMobileOrdering\n hasLateNightMenu\n hasParking\n hasPlayground\n hasTakeOut\n hasWifi\n hasLoyalty\n id\n isDarkKitchen\n isFavorite\n isHalal\n isRecent\n latitude\n longitude\n mobileOrderingStatus\n name\n number\n parkingType\n phoneNumber\n physicalAddress {\n address1\n address2\n city\n country\n postalCode\n stateProvince\n stateProvinceShort\n __typename\n }\n playgroundType\n pos {\n vendor\n __typename\n }\n posRestaurantId\n restaurantImage {\n asset {\n _id\n metadata {\n lqip\n palette {\n dominant {\n background\n foreground\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n crop {\n top\n bottom\n left\n right\n __typename\n }\n hotspot {\n height\n width\n x\n y\n __typename\n }\n __typename\n }\n restaurantPosData {\n _id\n __typename\n }\n status\n vatNumber\n __typename\n}\n\nfragment OperatingHoursFragment on OperatingHours {\n friClose\n friOpen\n monClose\n monOpen\n satClose\n satOpen\n sunClose\n sunOpen\n thrClose\n thrOpen\n tueClose\n tueOpen\n wedClose\n wedOpen\n __typename\n}\n"
}
]);
var config = {
method: 'post',
url: 'https://use1-prod-plk.rbictg.com/graphql',
headers: {
'authority': 'use1-prod-plk.rbictg.com',
'accept': '*/*',
'accept-language': 'en-US,en;q=0.9',
'apollographql-client-name': 'wl-web',
'apollographql-client-version': '4ef9144',
'content-type': 'application/json',
'origin': 'https://www.popeyes.com',
'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
'x-forter-token': '4798c310640a4c129119b405583aca62_1669841988907__UDF43_13ck_tt',
'x-session-id': '4C6B1EAF-0D2A-4295-84FB-81CDCDFC25D2',
'x-ui-language': 'en',
'x-ui-platform': 'web',
'x-ui-region': 'US',
'x-user-datetime': '2022-11-30T16:00:05-05:00'
},
data : data
};
promises.push(axios(config))
}
Promise.allSettled(promises).then((res) => {
const popeyes = []
for( let i = 0; i < res.length; i++){
const nodes = res[i].value.data[0].data?.restaurants?.nodes
const restaurants = nodes.map(node => {
return {
id : node?.id, name : 'Popeyes ' + node?.name, lat : node?.latitude, lon : node?.longitude
}
})
restaurants.forEach(restaurant => {
const isInArray = popeyes.find(fRestaurant => fRestaurant.id === restaurant.id )
if( !isInArray){
popeyes.push(restaurant)
}
})
}
const csv = new ObjectsToCsv(popeyes);
csv.toDisk('./Popeyes_Store_Locations.csv');
})
}
getPopeyesLocations();
Popeyes Store Locations CSV
Ahora la parte divertida es mapear todos los datos que recopilé. Usé la función de carga dentro de Felt para crear dos capas. Uno para Popeyes y otro para KFC.
Con casi más de 6700 puntos en el mapa, utilicé el panel de estilo integrado en Felt para eliminar etiquetas y reducir el tamaño del marcador. Esto era importante para limpiar el mapa y hacerlo más presentable.

Los resultados fueron bastante impactantes a pesar de que obtuve un adelanto cuando obtuve una vista previa de los datos. KFC tiene más de 1000 restaurantes más que Popeyes y visualmente es abrumador. KFC se traga a Popeyes hasta el punto de que parece que ni siquiera puedes ver ningún marcador para Popeyes.
Sentí que la herramienta de mapeo que utilicé también tiene la capacidad de activar y desactivar fácilmente las capas.
Notas finales
Los datos no mienten (en la mayoría de los casos). KFC domina a Popeyes en Estados Unidos. Sin embargo, este experimento mental me hizo pensar que sería interesante algún día usar esta herramienta para mapear el territorio de ventas y realizar un análisis sobre si debería abrir un restaurante/negocio en un área en particular.

PD Popeyes es superior a KFC
Mira el mapa en Felt:
¿Tiene preguntas o comentarios? ¡Me encantaría oírlo!
correo electrónico: [email protected]
gorjeo:https://twitter.com/duckduckquy
Sintió:https://felt.com/
GitHub:https://github.com/duckduckquy/popeyesScraper
Fuente de datos de latitud larga:https://gist.github.com/meiqimichelle/7727723