Aller au contenu

Récupérer ses mots clés GSC via l’API

L’autre jour, pour un client, j’ai voulu récupérer les mots clés sur lesquels il remontait dans la Search Console, mais j’avais un peu la flemme de regarder la Search Console pour demander un export, du coup, j’ai voulu passer via l’API (et ça a d’autres intérêts, on verra ça dans un autre article).

Du coup, comment faire ?

En premier lieu, on va faire un projet sur GCP : Une fois qu’il est créé, on se rend sur la bibliothèque d’API et on active l’API Google Search Console ici. Il va ensuite falloir aller créer un compte de service. Heureusement la doc Google est complète là dessus, on y apprends qu’il faut aller sur cette page pour créer un compte de service.

Il va falloir nommer le compte de service, n’hésitez pas à inclure des informations qui feront que le client vous y reconnaîtra, c’est plus sympa pour le client, et ça évite qu’un tiers qui manage des accès coupe par erreur l’accès à votre service.

Ensuite, on fait accéder l’email créé à la Search Console (un accès total est nécessaire à minima). Et ça, c’est aux admins du compte GSC de le faire, c’est assez simple (allez dans paramètres, utilisateurs, créer un utilisateur etc…).

Une fois que c’est fait, je vous invite à vous rendre ici pour télécharger le fichier json avec les clés API

et garder en mémoire que le nom du fichier sera utile par la suite.

La partie Fun :

On va créer une petite fonction python pour récupérer les mots clés

En un : la partie import de librairies:

J’ai besoin de librairies qui vont gérer mes csv, lire et stocker les infos en json, gérer les dates, et gérer les infos d’authentification google.

import datetime
import json
import csv
from google.oauth2 import service_account
from googleapiclient.discovery import build

J’ajoute os pour gérer les emplacements de fichiers, time pour éviter de dépasser le nombre de requête autorisées à la minute, et defaultdict pour créer un dictionnaire (je trouve ça plus pratique pour json). Enfin argparse pour ne faire qu’une seule ligne de commande avec toutes les infos.

import os
import argparse
import time
from collections import defaultdict

Le paramétrage : à vous de jouer :

Remplacez ‘client_secrets.json’ par le chemin de votre fichier de clés API (qu’on a téléchargé avant).

N’oubliez pas d’indiquer l’url du projet (même si techniquement on pourrait indiquer ça sur l’argparse, je me suis pas embêté et je l’ai mis en dur ici, c’est à corriger pour un code plus adaptable).

Attention, si vous êtes en mode “propriété de domaine”, il faut indiquer “sc-domain:exemple.com” où vous remplacez exemple.com par votre nom de domaine (domaine de 2nd niveau). Sinon, c’est directement l’url : “https://www.exemple.com”

SCOPES = ['https://www.googleapis.com/auth/webmasters.readonly']
KEY_FILE_LOCATION = 'client_secrets.json'
SITE_URL = 'sc-domain:url ou url'  # Remplacez par l'URL de votre site ou le domaine selon la propriété GSC.
JSON_OUTPUT_FILE = 'search_console_data.json'
CSV_OUTPUT_FILE = 'search_console_data.csv'

Ensuite, on va définir des fonctions “de service”, celles qui vont faire les tâches annexes :

(bon, j’ai l’habitude de recalculer le CTR, parce que s’il prend en compte des clics ou des impressions non affichés, mon point de vue c’est que c’est de la data non fournie, et même si elle existe techniquement, je ne l’ai pas). Je sais que c’est pas idéal, mais le but du jour c’est pas de la récupérer en particulier, on voulait surtout les clics & impressions.

def initialize_service():
    credentials = service_account.Credentials.from_service_account_file(
        KEY_FILE_LOCATION, scopes=SCOPES)
    service = build('searchconsole', 'v1', credentials=credentials)
    return service

def save_to_json(data, filename):
    with open(filename, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, indent=4, ensure_ascii=False)

def organize_data_by_date(data, organized_data, date_key):
    for entry in data:
        keys = entry['keys']
        query = keys[0]
        url = keys[1]
        clicks = entry.get('clicks', 0)
        impressions = entry.get('impressions', 0)
        position = entry.get('position', 0)  # Position moyenne
        ctr = (clicks / impressions) if impressions > 0 else 0  # Calcul du CTR moyen
        organized_data[date_key][query][url] = {
            'Clics': clicks,
            'Impressions': impressions,
            'Position': position,
            'CTR': ctr
        }

def json_to_csv(json_filename, csv_filename):
    with open(json_filename, 'r', encoding='utf-8') as json_file:
        data = json.load(json_file)
    
    # Ouverture du fichier CSV avec l'encodage UTF-8
    with open(csv_filename, 'w', newline='', encoding='utf-8') as csv_file:
        csv_writer = csv.writer(csv_file)
        csv_writer.writerow(['Date', 'Query', 'URL', 'Clics', 'Impressions', 'Position', 'CTR'])

        for date, queries in data.items():
            for query, urls in queries.items():
                for url, metrics in urls.items():
                    csv_writer.writerow([
                        date, 
                        query, 
                        url, 
                        metrics['Clics'], 
                        metrics['Impressions'], 
                        metrics['Position'], 
                        metrics['CTR']
                    ])

Ici, l’idée c’est que je vais stocker dans un json un dictionnaire de dates, (puisque je récupère tous jour par jour) et que dans ces dates, j’aie un dictionnaire de mots clés avec les clics et impressions associés. Je trouve ça plus simple.

Enfin, la fonction pour faire la requête à l’API :

def fetch_search_console_data(service, date):
    request = {
        'startDate': date,
        'endDate': date,
        'dimensions': ['query', 'page'],
        'rowLimit': 25000
    }
    
    response = service.searchanalytics().query(siteUrl=SITE_URL, body=request).execute()
    return response.get('rows', [])

La rowLimit est toujours à 25000 max parce que c’est le max indiqué dans la doc.

Enfin, la fonction main qui va articuler tout ça :

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--days', type=int, default=16*30, help='Nombre de jours à récupérer (par défaut 16 mois).')
    args = parser.parse_args()

    service = initialize_service()

    end_date = datetime.datetime.now()
    start_date = end_date - datetime.timedelta(days=args.days)

    organized_data = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))

    current_date = start_date
    while current_date <= end_date:
        date_str = current_date.strftime('%Y-%m-%d')
        print(f"Fetching data for {date_str}...")

        day_data = fetch_search_console_data(service, date_str)
        
        # Organiser les données par date
        organize_data_by_date(day_data, organized_data, date_str)
        
        # Sauvegarder les données au fur et à mesure
        save_to_json(organized_data, JSON_OUTPUT_FILE)
        
        # Pause pour respecter les quotas d'API
        time.sleep(1)  # Ajuster si nécessaire

        current_date += datetime.timedelta(days=1)

    json_to_csv(JSON_OUTPUT_FILE, CSV_OUTPUT_FILE)
    print(f"Data successfully saved to {CSV_OUTPUT_FILE}")

if __name__ == '__main__':
    main()

La fonction complète :

import os
import argparse
import datetime
import json
import time
import csv
from google.oauth2 import service_account
from googleapiclient.discovery import build
from collections import defaultdict

# Remplacez 'client_secrets.json' par le chemin de votre fichier de clés.
SCOPES = ['https://www.googleapis.com/auth/webmasters.readonly']
KEY_FILE_LOCATION = 'client_secrets.json'
SITE_URL = 'sc-domain:url ou url'  # Remplacez par l'URL de votre site ou le domaine selon la propriété GSC.
JSON_OUTPUT_FILE = 'search_console_data.json'
CSV_OUTPUT_FILE = 'search_console_data.csv'

def initialize_service():
    credentials = service_account.Credentials.from_service_account_file(
        KEY_FILE_LOCATION, scopes=SCOPES)
    service = build('searchconsole', 'v1', credentials=credentials)
    return service

def fetch_search_console_data(service, date):
    request = {
        'startDate': date,
        'endDate': date,
        'dimensions': ['query', 'page'],
        'rowLimit': 25000
    }
    
    response = service.searchanalytics().query(siteUrl=SITE_URL, body=request).execute()
    return response.get('rows', [])

def save_to_json(data, filename):
    with open(filename, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, indent=4, ensure_ascii=False)

def organize_data_by_date(data, organized_data, date_key):
    for entry in data:
        keys = entry['keys']
        query = keys[0]
        url = keys[1]
        clicks = entry.get('clicks', 0)
        impressions = entry.get('impressions', 0)
        position = entry.get('position', 0)  # Position moyenne
        ctr = (clicks / impressions) if impressions > 0 else 0  # Calcul du CTR moyen
        
        organized_data[date_key][query][url] = {
            'Clics': clicks,
            'Impressions': impressions,
            'Position': position,
            'CTR': ctr
        }

def json_to_csv(json_filename, csv_filename):
    with open(json_filename, 'r', encoding='utf-8') as json_file:
        data = json.load(json_file)
    
    # Ouverture du fichier CSV avec l'encodage UTF-8
    with open(csv_filename, 'w', newline='', encoding='utf-8') as csv_file:
        csv_writer = csv.writer(csv_file)
        csv_writer.writerow(['Date', 'Query', 'URL', 'Clics', 'Impressions', 'Position', 'CTR'])

        for date, queries in data.items():
            for query, urls in queries.items():
                for url, metrics in urls.items():
                    csv_writer.writerow([
                        date, 
                        query, 
                        url, 
                        metrics['Clics'], 
                        metrics['Impressions'], 
                        metrics['Position'], 
                        metrics['CTR']
                    ])

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--days', type=int, default=16*30, help='Nombre de jours à récupérer (par défaut 16 mois).')
    args = parser.parse_args()

    service = initialize_service()

    end_date = datetime.datetime.now()
    start_date = end_date - datetime.timedelta(days=args.days)

    organized_data = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))

    current_date = start_date
    while current_date <= end_date:
        date_str = current_date.strftime('%Y-%m-%d')
        print(f"Fetching data for {date_str}...")

        day_data = fetch_search_console_data(service, date_str)
        
        # Organiser les données par date
        organize_data_by_date(day_data, organized_data, date_str)
        
        # Sauvegarder les données au fur et à mesure
        save_to_json(organized_data, JSON_OUTPUT_FILE)
        
        # Pause pour respecter les quotas d'API
        time.sleep(1)  # Ajuster si nécessaire

        current_date += datetime.timedelta(days=1)

    # Convertir les données JSON en CSV pour une meilleure lisibilité
    json_to_csv(JSON_OUTPUT_FILE, CSV_OUTPUT_FILE)
    print(f"Data successfully saved to {CSV_OUTPUT_FILE}")

if __name__ == '__main__':
    main()

Voilà !

Prochaine étape, on va programmer la sauvegarde de ces données de façon récurrente en vérifiant qu’on ne réécrit pas par dessus l’existant.

Amusez vous bien avec Python 🙂