Fried Chicken Wars — Mapeando o Território Popeyes vs KFC usando feltro

Recentemente, saí para almoçar com um amigo que havia acabado de se mudar para Orlando. Enquanto falava sobre como a cidade estava evoluindo, mencionei a ele que via novos restaurantes Popeyes abrindo em todos os lugares que eu ia. Do seu ponto de vista, ele estava vendo novos restaurantes KFC abrindo em todos os lugares. Eu sabia que nós dois não poderíamos estar certos. Acabamos debatendo qual rede de frango frito tem uma participação de mercado mais dominante nos Estados Unidos.
Como resultado desse debate acalorado, decidi responder à pergunta coletando dados e construindo uma visualização de mapa. Abaixo, vou orientá-lo através do meu processo.
- Coletando os dados
O primeiro desafio que tive foi encontrar uma fonte confiável de dados para localização de lojas para Popeyes e KFC. Encontrei um site chamado scrapehero.com que se ofereceu para me vender os dados por um preço de US$ 95 pelo conjunto de dados de uma cadeia. No entanto, eu não estava disposto a gastar cerca de US $ 200 para me gabar do meu amigo. Depois de explorar o site do Popeyes, uma ideia surgiu em minha mente. Eu me pergunto se eu poderia fazer engenharia reversa da API (também conhecida como Web Scraping).
A primeira etapa foi abrir as ferramentas do desenvolvedor clicando com o botão direito do mouse e inspecionando. Continuei navegando até a guia Rede e dei uma olhada em todas as solicitações recebidas. Após alguma investigação, descobri que a solicitação estava sendo usada para buscar e preencher os locais da loja na interface do usuário.

Você pode copiar a solicitação cURL e realmente importá-la para o Postman, e o Postman lida com toda a formatação para você. Além disso, o Postman tem a opção de exportar a solicitação para o idioma de sua escolha.


Após essa etapa, escrevi um script básico que enviaria as solicitações e as salvaria em um CSV no meu computador. As principais coisas que notei com a solicitação que estava sendo usada para buscar todos os locais da loja estão dentro da carga útil, com algumas variáveis com as quais você pode brincar, incluindo: userLat, userLng, searchRadius e first. Ajustando essas variáveis, consegui coletar os dados de todas as 2.851 lojas. O usuário do Github, meiqimichelle, fez um trabalho pesado, fornecendo as coordenadas de latitude e longitude de cada estado que eu percorreria. Eu adotaria uma abordagem semelhante ao coletar os dados da loja 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
Agora a parte divertida é mapear todos os dados que coletei. Usei o recurso de upload no Felt para criar duas camadas. Um para Popeyes e outro para KFC.
Com quase 6700 pontos no mapa, usei o painel de estilo construído no Felt para remover rótulos e reduzir o tamanho do marcador. Isso foi importante para limpar o mapa e torná-lo mais apresentável.

Os resultados foram bastante chocantes, embora eu tenha dado uma espiada quando visualizei os dados. KFC tem mais de 1000 restaurantes a mais do que Popeyes e visualmente é impressionante. O KFC engole Popeyes a ponto de você não conseguir ver nenhum marcador para Popeyes.
Senti que a ferramenta de mapeamento que usei também tem a capacidade de ativar e desativar facilmente as camadas.
Notas Finais
Os dados não mentem (na maioria dos casos). KFC domina Popeyes nos Estados Unidos. No entanto, esse experimento mental me fez pensar que seria interessante algum dia potencialmente usar essa ferramenta para mapear o território de vendas, bem como realizar análises sobre se você deve ou não abrir um restaurante/negócio em uma área específica.

PS Popeyes é superior ao KFC
Confira o mapa no Felt:
Você tem perguntas ou comentários? Eu adoraria ouvi-lo!
e-mail: [email protected]
Twitter:https://twitter.com/duckduckquy
Sentiu:https://felt.com/
Github:https://github.com/duckduckquy/popeyesScraper
Fonte de dados Lat Long:https://gist.github.com/meiqimichelle/7727723