link

Logo for Seoseb

Pagespeed-Monitoring mit der Pagespeed Insights API

Pagespeed und Core Web Vitals sind derzeit in aller (SEO) Munde. Hauptsächlich weil Google im Mai 2021 die Core Web Vitals ins Ranking einbeziehen und Suchergebnisse auf Basis der CWV markieren möchte.

Spätestens seit 2014 ist die Ladezeit einer Website (kurz: Pagespeed) ein Thema. Google hat damals die Mobile Friendliness in den Vordergrund gerückt und zeitweise sogar langsame Webseiten in den SERPs markiert.

Google ist so freundlich und stellt Tools zur Überprüfung der Performance der eigenen Website zur Verfügung. Interessierte schauen seitdem auf Lighthouse-Score und Pagespeed-Insights-Ergebnisse. Tools bieten Tests an, die die entsprechenden Werte aus Chrome eigenen Analysetools extrahieren und aufbereiten. Nachdem Erscheinen von Lighthouse war dieses lange Zeit Anlaufstelle Nummer Eins.

Die Pagespeed-Insights greifen seit längerem auf Lighthouse und dessen Audits zurück und stellen die Ergebnisse auch per API zur Verfügung. Mit Aufkommen der CWV bekommen diese jetzt wieder Bedeutung und Lösungen zum regelmäßigen Überprüfen/Überwachen der eigenen Seite oder des Wettbewerbs werden interessanter.

Ich zeige hier eine kleine Lösung, die ich aktuell einsetze.

#Web-Fundstücke

Im Zuge meiner Recherchen zum Thema bin ich auf eine Lösung für Google Sheets & Apps Script gestoßen, die schon fast alle meine Bedürfnisse erfüllt. Auf Basis dieser habe ich mir eine eigene Lösung gestrickt, die die Vorlage um ein Paar Kleinigkeiten erweitert.

Meine Quelle und ihre Quellen sind folgende:

  1. PageDart basiert auf:
  2. Rick Viscomi basiert auf:
  3. Robert Ellison

#Mein Ziel: Google Core Web Vitals tracken

Ich wollte die Core Web Vitals für mehrere URLs in regelmäßigen Abständen abfragen und die gesammelten Daten dann grafisch darstellen.

Die Vorlage von Sam hat schon das meiste geliefert, was ich gebraucht habe. Da aber die CWV mehr als den Pagespeed-Score beinhalten, habe ich die CWV-KPI ebenfalls in die Abfrage hinzugefügt. Ich überwache jetzt folgende Werte für mobile Clients:

Mit der Zeit habe ich festgestellt, dass die Pagespeed-API hin und wieder etwas zickig ist und ein minimales Fehlerhandling eingebaut.

Alle abgefragten URLs und die Response-Codes der PSI-API werden geloggt. Sollte API nicht mit Statuscode 200 antwortet wird ein Fehler geloggt und N/A in die Tabelle geschrieben. Das Ursprüngliche Skript brach hier ab, da keine Daten aus der leeren Response ausgelesen werden konnten.

Jetzt können Fehler nachvollzogen werden und die Tabelle bleibt nicht einfach leer. Auch wenn die Fehler mitten im Abarbeiten der URL-Liste auftreten, bricht das Skript nicht einfach ab, sondern fährt mit den folgenden URLs fort.

Abgefragte URL und Status-Code der Response in einem Auszug aus den Logs
Auszug aus den Logs der Skript-Ausführungen, URL und Status-Code der Response werden geloggt

#Apps Script Code

/** 
 * Script calls Googles Page Speed Insights API for a set of URL in order to extract Core Web Vitals KPI
 * 
 * extended by @seoseb (seoseb.de) https://www.seoseb.de/artikel/texte/pagespeed-monitoring-mit-der-psi-api/
 * Created by PageDart (https://pagedart.com)
 * Adapted from Rick Viscomi https://dev.to/chromiumdev/a-step-by-step-guide-to-monitoring-the-competition-with-the-chrome-ux-report-4k1o
 * Who Adapted it from https://ithoughthecamewithyou.com/post/automate-google-pagespeed-insights-with-apps-script by Robert Ellison
*/
// make check available to run from menu
function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Core Web Vitals Monitor')
      .addItem('Monitor ausführen', 'monitor')
      .addToUi();
}
var scriptProperties = PropertiesService.getScriptProperties();
scriptProperties.setProperties({
  'pageSpeedApiKey' :  'DEIN_API_KEY', // Hier trägst Du Deinen API-Key ein
  'psAPIurl' : 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=',
  'locale' : 'de-DE',
  'deviceStrategy' :'mobile'
  });
var ApiUrl = scriptProperties.getProperty('psAPIurl');
var ApiKey = scriptProperties.getProperty('pageSpeedApiKey');
var apiLanguage = scriptProperties.getProperty('locale');
var analysisStrategy = scriptProperties.getProperty('deviceStrategy');
var pageSpeedMonitorUrls = [ // Hier trägst Du Deine URLs ein, in Anführungszeichen, Komma getrennt
  'https://www.seoseb.de/',
  'https://www.seoseb.de/labor/tester.htm'
];
function monitor() {
  for (var i = 0; i < pageSpeedMonitorUrls.length; i++) {
    var url = pageSpeedMonitorUrls[i];
    var mobile = callPageSpeed(url);
    addRow(url, mobile);
  }
}
function callPageSpeed(url) {
  var pageSpeedUrl = ApiUrl + url + '&key=' + ApiKey + '&locale=' + apiLanguage + '&strategy=' + analysisStrategy;
  options = {muteHttpExceptions: true};
  var response = UrlFetchApp.fetch(pageSpeedUrl, options);
  Logger.log('fetching: ' + url);
  var ResponseCode = (response.getResponseCode());
  var json = response.getContentText();
  if(ResponseCode == 200){
      Logger.log('fetching ' + url + ': successful');
      return [JSON.parse(json), ResponseCode];
  }
  else {
     Logger.log('fetching ' + url + ' failed with: ' + ResponseCode);
     return [JSON.parse(json), ResponseCode];
  }
}
function addRow(url, mobile) {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName('dataCollection');
  if(mobile[1] == 200){
      var CLS = mobile[0].lighthouseResult.audits['cumulative-layout-shift'].displayValue;
      var FID = mobile[0].lighthouseResult.audits['max-potential-fid'].displayValue.replace(/\sms/g,'');
      var LCP = mobile[0].lighthouseResult.audits['largest-contentful-paint'].displayValue.replace(/\ss/g,'');
      var PSscore = mobile[0].lighthouseResult.categories.performance.score;
      sheet.appendRow([
        Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'),
        url,
        CLS,
        FID,
        LCP,
        PSscore
      ]);
  }
  else{
        sheet.appendRow([
        Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd'),
        url,
        'N/A',
        'N/A',
        'N/A',
        'N/A'
      ])
  }
}

Das Skript läuft bei mir alle vier Stunden und schreibt die Daten in das Tabellenblatt "dataCollection". Das sieht dann in etwa so aus:

date url CLS FID (ms) LCP (s) PS Score
2020-02-20 https://www.seoseb.de/ 0 0 1,1 100
2020-02-20 https://www.seoseb.de/labor/tester.htm 0,334 1470 41,2 46
2020-02-20 https://www.seoseb.de/ 0 0 1,3 99
2020-02-20 https://www.seoseb.de/labor/tester.htm 0,334 1470 40,0 48

#Pushing the limits

Das Zeitlimit für die Ausführung einer Apps Script Funktion liegt bei sechs Minuten (siehe Google Service Quotas).

Ich habe URLs für mehrere Projekte im Monitoring und im Praxiseinsatz hat sich gezeigt, dass bei einer größeren Menge an URLs und besonders bei langsamen Kandidaten, die Antwort der PSI-API recht lang dauert und es bei der Skriptausführung zu Zeitüberschreitungen kommt. (Ich komme nicht umhin darauf hinzuweisen dass der Code in keiner Weise optimiert ist - er kann sicher durchaus effizienter gestrickt werden.)

Um die Zeitüberschreitungen zu verhindern habe ich ein eigenes Array mit URLs für jedes Projekt und einen separaten Aufruf der Monitoring-Funktion für jedes Projekt erstellt:

/* URLs per project instead of 
var pageSpeedMonitorUrls = []; */
var urlsProjekt1 = [ 
  'www.projekt-1.de/url/a/',
  'www.projekt-1.de/url/b/',
  'www.projekt-1.de/url/c/'
];
var urlsProjekt2 = [ 
  'www.projekt-2.de/url/a/',
  'www.projekt-2.de/url/b/',
  'www.projekt-2.de/url/c/'
];
var urlsProjekt3 = [ 
  'www.projekt-3.de/url/a/',
  'www.projekt-3.de/url/b/',
  'www.projekt-3.de/url/c/'
];
// call monitoring function per project
function monitorProjekt1 () {
 monitor(urlsProjekt1);
};
function monitorProjekt() {
 monitor(urlsProjekt2);
};
function monitorProjekt3 () {
 monitor(urlsProjekt3);
};

Danach habe ich für die Aufruf-Funktion jedes Projektes einen eigenen Trigger eingerichtet. Die Trigger laufen in zeitlich ausreichendem Abstand zueinander. Das Ergebnis: Keine Zeitüberschreitungen mehr.

Basierend auf diesem Sheet werden dann Pivot-Tabellen für die Metriken erstellt, die direkt in der Arbeitsmappe als Chart dargestellt werden können, oder für Data Studio als Datenquelle dienen können.

Google-Sheets Charts, die die Pagespeed-Insights Daten aus dem automatischen Monitoring zeigen
so sehen die Charts in Google Sheets aus

So kann der Core Web Vitals Monitor in Google Data Studio aussehen:

Screenshot: Google Data Studio zeigt die Core Web Vitals Daten im Zeitverlauf
Google Data Studio Core Web Vitals Monitor

Das Sheet und das Apps Script sind keine 100% ausgereiften Programme. Das Fehlerhandling nach gemuteten Exeptions ist z.B. kein elegantes Handwerk, aber es erfüllt seinen Zweck.


#Google Sheet zum Kopieren

Wenn Du magst, kannst Du diese Google-Sheets-Datei als Core Web Vitals Vorlage nutzen, und für Deine Zwecke anpassen und nutzen.

Selbstverständlich kann auch einer der vorhergehenden Beiträge als Einstieg dienen. Reinschauen solltest Du auf jeden Fall.

#Verwendung der Sheet-Vorlage

Lege Dir eine Kopie der Vorlage an:

"Kopie erstellen" unter dem Menüeintrag "Datei" in Google Sheets
Hier legst Du Deine Kopie an, in der Du dann arbeiten kannst

Wenn Du Das Skript angepasst hast, kannst Du Die Einträge im Tabellenblatt "dataCollection" löschen. Das Skript befüllt das Blatt das wieder von Neuem und Du kannst die Pivot-Tabellen aktualisieren.

#API-Key

Hier bekommst Du Deinen eigenen PSI-API-Key .

#automatische Skript-Ausführung

Um Das Skript automatisch ausführen zu lassen, habe ich einen Zeit gesteuerten Trigger eingerichtet. Das geht ganz einfach direkt im Apps Script Editor. Dazu Wählst Du im Menü des Editors den Punkt "Trigger":

der Menüeintrag "Trigger" im Menü des Apps Script Editors
Unter dem Menüeintrag "Trigger" kann die Zeitsteuerung eingerichtet werden

Das Fenster ist zuerst leer. Mit einem Klick auf "Trigger hinzufügen" kannst Du einen neuen Trigger anlegen. Der Einstellungsdialog sieht dann in etwa so aus:

EInrichtungsdialog für automatische Skriptausführung mit markierten Einträge
Die Einstellung der Zeitsteuerung erfolgt kinderleicht über die markierten Drop-Downs

Die Zeitsteuerung kannst Du ganz nach Deinen Wünschen einrichten. Mit "Speichern" wird der Trigger angelegt und beginnt seine Arbeit.

Wenn Du nicht auf die Zeitsteuerung zurückgreifen möchtest, oder erstmal rumprobieren willst: Das Skript bringt einen eigenen Menüeintrag mit, über den Du die Monitoring-Funktion manuell starten kannst.

Menüeintrag im Google Spreadsheet zum Starten der Monitoring-Funktion
Ein Klick auf diesen Eintrag startet die Monitoring-Funktion

In meiner Linksammlung findest Du noch ein paar andere nette Online-Tools, die die Core Web Vitals und andere PSI-Daten ansprechend aggregieren darstellen.