Fried Chicken Wars - Cartographier le territoire de Popeyes vs KFC à l'aide de Felt

Dec 01 2022
Récemment, je suis sorti déjeuner avec un ami qui venait de rentrer chez lui à Orlando. En parlant de l'évolution de la ville, je lui ai mentionné voir de nouveaux restaurants Popeyes ouvrir partout où je vais.

Récemment, je suis sorti déjeuner avec un ami qui venait de rentrer chez lui à Orlando. En parlant de l'évolution de la ville, je lui ai mentionné voir de nouveaux restaurants Popeyes ouvrir partout où je vais. De son point de vue, il avait vu de nouveaux restaurants KFC ouvrir partout. Je savais que nous ne pouvions pas avoir raison tous les deux. Nous avons fini par débattre de la chaîne de poulet frit qui détient la part de marché la plus dominante aux États-Unis.

À la suite de ce débat houleux, j'ai décidé de répondre à la question en collectant des données et en créant une visualisation cartographique. Ci-dessous, je vais vous guider à travers mon processus.

  1. Collecte des données

Le premier défi que j'ai eu était de trouver une source fiable de données pour les emplacements des magasins pour Popeyes et KFC. J'ai trouvé un site Web appelé scrapehero.com qui proposait de me vendre les données au prix de 95 $ pour l'ensemble de données d'une chaîne. Cependant, je n'étais pas sur le point de dépenser environ 200 $ pour me vanter contre mon ami. Après avoir exploré le site Web de Popeyes, une idée m'est venue à l'esprit. Je me demande si je pourrais désosser l'API (alias Web Scraping).

La première étape a été d'ouvrir les outils de développement en cliquant avec le bouton droit de la souris et en inspectant. J'ai continué à naviguer vers l'onglet Réseau et j'ai jeté un coup d'œil à toutes les demandes entrantes. Après quelques recherches, j'ai découvert que la requête était utilisée pour récupérer et remplir les emplacements des magasins sur l'interface utilisateur.

Trouver la bonne requête API récupérant les données

Vous pouvez copier la requête cURL et l'importer réellement dans Postman et Postman gère tout le formatage pour vous. De plus, Postman a une option où vous pouvez exporter la demande dans la langue de votre choix.

Importation de la demande
Traduire la demande dans la langue de votre choix

Après cette étape, j'ai écrit un script approximatif qui enverrait les demandes et l'enregistrerait dans un CSV sur mon ordinateur. Les éléments clés que j'ai remarqués avec la requête qui était utilisée pour récupérer tous les emplacements des magasins se trouvent dans la charge utile, vous aviez quelques variables avec lesquelles vous pouviez jouer, notamment : userLat, userLng, searchRadius et first. En ajustant ces variables, j'ai pu collecter les données pour les 2851 magasins. L'utilisateur de Github, meiqimichelle, a fait du gros travail en fournissant les coordonnées de latitude et de longitude de chaque état que je parcourrais. J'adopterais une approche similaire avec la collecte des données du magasin 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

Maintenant, la partie amusante consiste à cartographier toutes les données que j'ai collectées. J'ai utilisé la fonction de téléchargement dans Felt pour créer deux calques. Un pour Popeyes et un autre pour KFC.

Avec près de 6700+ points sur la carte, j'ai utilisé le panneau de style intégré à Felt pour supprimer les étiquettes et réduire la taille du marqueur. C'était important de nettoyer la carte pour la rendre plus présentable.

Styliser la carte dans Felt

Les résultats ont été assez choquants même si j'ai eu un aperçu lorsque j'ai prévisualisé les données. KFC compte plus de 1000 restaurants de plus que Popeyes et visuellement, c'est écrasant. KFC avale Popeyes au point que vous ne semblez même pas voir de marqueurs pour Popeyes.

Felt l'outil de cartographie que j'ai utilisé a également la capacité d'activer et de désactiver facilement les calques.

Remarques finales

Les données ne mentent pas (dans la plupart des cas). KFC domine Popeyes aux États-Unis. Cette expérience de pensée m'a cependant fait penser qu'il serait intéressant d'utiliser un jour cet outil pour cartographier le territoire de vente ainsi que pour analyser si vous devriez ou non ouvrir un restaurant/entreprise dans une zone particulière.

Carte KFC vs Popeyes construite avec Felt

PS Popeyes est supérieur à KFC

Consultez la carte sur Felt :

Avez-vous des questions ou des commentaires ? J'aimerais l'entendre !

Courriel : [email protected]

Twitter:https://twitter.com/duckduckquy

Feutre:https://felt.com/

Github :https://github.com/duckduckquy/popeyesScraper

Source de données Lat Long :https://gist.github.com/meiqimichelle/7727723