Das Webalizer-Tool und Google Analytics haben mir jahrelang geholfen, ein VerstĂ€ndnis dafĂŒr zu bekommen, was auf Websites passiert. Jetzt erkenne ich, dass sie nur sehr wenig nĂŒtzliche Informationen liefern. Mit Zugang zu meiner access.log-Datei ist es sehr einfach, die Statistik zu verstehen, und dafĂŒr sind lediglich elementare Werkzeuge wie SQLite, HTML, die SQL-Sprache und jede Skriptsprache erforderlich.
Die Datenquelle fĂŒr Webalizer ist die Datei access.log Server. So sehen seine Spalten und Zahlen aus, aus denen lediglich das gesamte Verkehrsvolumen ersichtlich ist:


Werkzeuge wie Google Analytics sammeln die Daten von der geladenen Seite selbst. Sie zeigen uns ein paar Diagramme und Linien, anhand derer es oft schwierig ist, korrekte Schlussfolgerungen zu ziehen. Vielleicht hÀtte ich mehr Aufwand investieren sollen? Ich weià es nicht.
Was wollte ich also in der Besuchsstatistik der Website sehen?
Den Verkehr der Benutzer und Bots
Oft hat der Website-Verkehr eine Begrenzung, und es ist notwendig zu sehen, wie viel nĂŒtzlicher Verkehr genutzt wird. Zum Beispiel so:

SQL-Abfragebericht
SELECT
1 AS 'StackedArea: Verkehr erzeugt von Benutzern und Bots',
strftime('%d.%m', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Tag',
SUM(CASE WHEN USG.AGENT_BOT!='n.a.' THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Bots, KB',
SUM(CASE WHEN USG.AGENT_BOT='n.a.' THEN FCT.BYTES ELSE 0 END) / 1000 AS 'Benutzer, KB'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USG
WHERE FCT.DIM_USER_AGENT_ID=USG.DIM_USER_AGENT_ID
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-14 days')
GROUP BY strftime('%d.%m', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY FCT.EVENT_DTAus dem Diagramm ist eine stÀndige AktivitÀt der Bots zu erkennen. Es wÀre interessant, die aktivsten Vertreter genauer zu analysieren.
Nervige Bots
Wir klassifizieren Bots basierend auf den Informationen des Benutzeragenten. ZusĂ€tzliche Statistiken ĂŒber den tĂ€glichen Verkehr, die Anzahl der erfolgreichen und nicht erfolgreichen Anfragen geben ein gutes Bild ĂŒber die AktivitĂ€t der Bots.

SQL-Abfragebericht
SELECT
1 AS 'Tabelle: Nervige Bots',
MAX(USG.AGENT_BOT) AS 'Bot',
ROUND(SUM(FCT.BYTES) / 1000 / 14.0, 1) AS 'KB pro Tag',
ROUND(SUM(FCT.IP_CNT) / 14.0, 1) AS 'IPs pro Tag',
ROUND(SUM(CASE WHEN STS.STATUS_GROUP IN ('Client Error', 'Server Error') THEN FCT.REQUEST_CNT / 14.0 ELSE 0 END), 1) AS 'Fehleranfragen pro Tag',
ROUND(SUM(CASE WHEN STS.STATUS_GROUP IN ('Successful', 'Redirection') THEN FCT.REQUEST_CNT / 14.0 ELSE 0 END), 1) AS 'Erfolgsanfragen pro Tag',
USG.USER_AGENT_NK AS 'Agent'
FROM FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USG,
DIM_HTTP_STATUS STS
WHERE FCT.DIM_USER_AGENT_ID = USG.DIM_USER_AGENT_ID
AND FCT.DIM_HTTP_STATUS_ID = STS.DIM_HTTP_STATUS_ID
AND USG.AGENT_BOT != 'n.a.'
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-14 days')
GROUP BY USG.USER_AGENT_NK
ORDER BY 3 DESC
LIMIT 10In diesem Fall war das Ergebnis der Analyse die Entscheidung, den Zugang zur Website durch HinzufĂŒgen zur Datei robots.txt einzuschrĂ€nken.
User-Agent: AhrefsBot
Disallow: /
User-Agent: dotbot
Disallow: /
User-Agent: bingbot
Crawl-Delay: 5
Die ersten beiden Bots sind aus der Tabelle verschwunden, wÀhrend die MS-Roboter von den obersten PlÀtzen nach unten gerutscht sind.
Tag und Uhrzeit der höchsten AktivitÀt
Im Traffic sind Anstiege zu erkennen. Um diese genauer zu untersuchen, ist es notwendig, die Zeit ihrer Entstehung zu berĂŒcksichtigen, wobei es nicht erforderlich ist, alle Stunden und Tage der Zeitmessung anzuzeigen. So lĂ€sst sich bei Bedarf einfacher nach einzelnen Anfragen im Logfile suchen, wenn eine detaillierte Analyse erforderlich ist.

SQL-Abfragebericht
SELECT
1 AS 'Zeile: Tag und Uhr der Zugriffe von Nutzern und Bots',
strftime('%d.%m-%H', datetime(EVENT_DT, 'unixepoch')) AS 'Datum Uhrzeit',
HIB AS 'Bots, Zugriffe',
HIU AS 'Nutzer, Zugriffe'
FROM (
SELECT
EVENT_DT,
SUM(CASE WHEN AGENT_BOT!='n.a.' THEN LINE_CNT ELSE 0 END) AS HIB,
SUM(CASE WHEN AGENT_BOT='n.a.' THEN LINE_CNT ELSE 0 END) AS HIU
FROM FCT_ACCESS_REQUEST_REF_HH
WHERE datetime(EVENT_DT, 'unixepoch') >= date('now', '-14 day')
GROUP BY EVENT_DT
ORDER BY SUM(LINE_CNT) DESC
LIMIT 10
) ORDER BY EVENT_DTWir beobachten die aktivsten Stunden 11, 14 und 20 am ersten Tag im Diagramm. Am nÀchsten Tag waren die Bots um 13 Uhr aktiv.
Durchschnittliche tÀgliche AktivitÀt der Nutzer nach Wochen
Wir haben uns ein wenig mit der AktivitĂ€t und dem Traffic beschĂ€ftigt. Die nĂ€chste Frage war die AktivitĂ€t der Nutzer selbst. FĂŒr solche Statistiken sind lĂ€ngere AggregationszeitrĂ€ume wĂŒnschenswert, beispielsweise eine Woche.

SQL-Abfragebericht
SELECT
1 AS 'Zeile: Durchschnittliche tÀgliche NutzeraktivitÀt nach Woche',
strftime('%W Woche', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Woche',
ROUND(1.0*SUM(FCT.PAGE_CNT)/SUM(FCT.IP_CNT),1) AS 'Seiten pro IP pro Tag',
ROUND(1.0*SUM(FCT.FILE_CNT)/SUM(FCT.IP_CNT),1) AS 'Dateien pro IP pro Tag'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_USER_AGENT USG,
DIM_HTTP_STATUS HST
WHERE FCT.DIM_USER_AGENT_ID=USG.DIM_USER_AGENT_ID
AND FCT.DIM_HTTP_STATUS_ID = HST.DIM_HTTP_STATUS_ID
AND USG.AGENT_BOT='n.a.' /* nur Nutzer */
AND HST.STATUS_GROUP IN ('Erfolgreich') /* gute Seiten */
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-3 month')
GROUP BY strftime('%W Woche', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY FCT.EVENT_DTDie Statistik fĂŒr die Woche zeigt, dass ein Nutzer im Durchschnitt 1,6 Seiten pro Tag öffnet. Die Anzahl der angeforderten Dateien pro Nutzer hĂ€ngt in diesem Fall von der Anzahl der neuen Dateien ab, die auf die Website hochgeladen werden.
Alle Anfragen und deren Status
Webalizer hat stets die spezifischen Seitenkodierungen angezeigt und man wollte immer einfach die Anzahl der erfolgreichen Anfragen und Fehler sehen.

SQL-Abfragebericht
SELECT
1 AS 'Zeile: Alle Anfragen nach Status',
strftime('%d.%m', datetime(FCT.EVENT_DT, 'unixepoch')) AS 'Tag',
SUM(CASE WHEN STS.STATUS_GROUP='Erfolgreich' THEN FCT.REQUEST_CNT ELSE 0 END) AS 'Erfolg',
SUM(CASE WHEN STS.STATUS_GROUP='Umleitung' THEN FCT.REQUEST_CNT ELSE 0 END) AS 'Umleitung',
SUM(CASE WHEN STS.STATUS_GROUP='Client-Fehler' THEN FCT.REQUEST_CNT ELSE 0 END) AS 'Kundenfehler',
SUM(CASE WHEN STS.STATUS_GROUP='Server-Fehler' THEN FCT.REQUEST_CNT ELSE 0 END) AS 'Serverfehler'
FROM
FCT_ACCESS_USER_AGENT_DD FCT,
DIM_HTTP_STATUS STS
WHERE FCT.DIM_HTTP_STATUS_ID=STS.DIM_HTTP_STATUS_ID
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-14 day')
GROUP BY strftime('%d.%m', datetime(FCT.EVENT_DT, 'unixepoch'))
ORDER BY FCT.EVENT_DTDer Bericht zeigt Anfragen und nicht Klicks (Hits). Im Gegensatz zur LINE_CNT-Metrik wird REQUEST_CNT als COUNT(DISTINCT STG.REQUEST_NK) betrachtet. Ziel ist es, effektive Ereignisse zu zeigen, zum Beispiel fragen MS-Bots hundertmal am Tag die Datei robots.txt ab, und in diesem Fall werden solche Anfragen nur einmal gezĂ€hlt. Dies ermöglicht es, SprĂŒnge im Diagramm zu glĂ€tten.
Aus dem Diagramm sind viele Fehler ersichtlich â dies sind nicht existierende Seiten. Das Ergebnis der Analyse war die HinzufĂŒgung von Weiterleitungen von entfernten Seiten.
Fehlerhafte Anfragen
FĂŒr eine detaillierte Betrachtung der Anfragen kann eine detaillierte Statistik erstellt werden.

SQL-Abfragebericht
SELECT
1 AS 'Tabelle: HĂ€ufigste Fehleranfragen',
REQ.REQUEST_NK AS 'Anfrage',
'Fehler' AS 'Anfragestatus',
ROUND(SUM(FCT.LINE_CNT) / 14.0, 1) AS 'Hits pro Tag',
ROUND(SUM(FCT.IP_CNT) / 14.0, 1) AS 'IPs pro Tag',
ROUND(SUM(FCT.BYTES)/1000 / 14.0, 1) AS 'KB pro Tag'
FROM
FCT_ACCESS_REQUEST_REF_HH FCT,
DIM_REQUEST_V_ACT REQ
WHERE FCT.DIM_REQUEST_ID = REQ.DIM_REQUEST_ID
AND FCT.STATUS_GROUP IN ('Client Error', 'Server Error')
AND datetime(FCT.EVENT_DT, 'unixepoch') >= date('now', '-14 day')
GROUP BY REQ.REQUEST_NK
ORDER BY 4 DESC
LIMIT 20In dieser Liste werden auch alle Anfragen enthalten sein, z.B. die Anfrage an /wp-login.php. Durch Anpassung der Regeln zur Umleitung von Anfragen Server kann die Reaktion des Servers auf solche Anfragen angepasst werden und sie zur Startseite weitergeleitet werden.
Daher liefern einige einfache Berichte auf Basis der Serverprotokolldatei ein recht umfassendes Bild dessen, was auf der Website geschieht.
Wie erhÀlt man Informationen?
Die SQLite-Datenbanken sind völlig ausreichend. Wir werden Tabellen erstellen: eine Hilfstabelle zur Protokollierung der ETL-Prozesse.

Staging-Tabelle, in die wir die Protokolldateien mit PHP schreiben werden. Zwei Aggregat-Tabellen. Wir erstellen eine tĂ€gliche Tabelle mit Statistiken zu den Benutzeragenten und Anfragestati. Eine stĂŒndliche Tabelle mit Statistiken zu Anfragen, Statusgruppen und Agenten. Vier Tabellen fĂŒr die entsprechenden Dimensionen.
Das Ergebnis war das folgende relationale Modell:
Datenmodell
Skript zum Erstellen eines Objekts in der SQLite-Datenbank:
DDL zur Erstellung des Objekts
DROP TABLE IF EXISTS DIM_USER_AGENT;
CREATE TABLE DIM_USER_AGENT (
DIM_USER_AGENT_ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
USER_AGENT_NK TEXT NOT NULL DEFAULT 'n.a.',
AGENT_OS TEXT NOT NULL DEFAULT 'n.a.',
AGENT_ENGINE TEXT NOT NULL DEFAULT 'n.a.',
AGENT_DEVICE TEXT NOT NULL DEFAULT 'n.a.',
AGENT_BOT TEXT NOT NULL DEFAULT 'n.a.',
UPDATE_DT INTEGER NOT NULL DEFAULT 0,
UNIQUE (USER_AGENT_NK)
);
INSERT INTO DIM_USER_AGENT (DIM_USER_AGENT_ID) VALUES (-1);Staging
Im Fall der access.log-Datei mĂŒssen alle Anfragen gelesen, geparsed und in die Datenbank geschrieben werden. Dies kann entweder direkt mit Hilfe einer Skriptsprache oder mithilfe von SQLite-Tools geschehen.
Dateiformat des Protokolls:
//67.221.59.195 - - [28/Dec/2012:01:47:47 +0100] "GET /files/default.css HTTP/1.1" 200 1512 "https://project.edu/" "Mozilla/4.0"
//host ident auth time method request_nk protocol status bytes ref browser
$log_pattern = '/^([^ ]+) ([^ ]+) ([^ ]+) ([[^]]+]) "(.*) (.*) (.*)" ([0-9-]+) ([0-9-]+) "(.*)" "(.*)"$/';
Key-Promotion
Wenn die Rohdaten in der Datenbank sind, mĂŒssen die SchlĂŒssel, die dort fehlen, in die MaĂtabelle geschrieben werden. Dann wird der Verweis auf die MaĂe möglich. Zum Beispiel ist der SchlĂŒssel in der DIM_REFERRER-Tabelle eine Kombination aus drei Feldern.
SQL-Abfrage zur Key-Promotion
/* Propagate the referrer from access log */
INSERT INTO DIM_REFERRER (HOST_NK, PATH_NK, QUERY_NK, UPDATE_DT)
SELECT
CLS.HOST_NK,
CLS.PATH_NK,
CLS.QUERY_NK,
STRFTIME('%s','now') AS UPDATE_DT
FROM (
SELECT DISTINCT
REFERRER_HOST AS HOST_NK,
REFERRER_PATH AS PATH_NK,
CASE WHEN INSTR(REFERRER_QUERY,'&sid')>0 THEN SUBSTR(REFERRER_QUERY, 1, INSTR(REFERRER_QUERY,'&sid')-1) /* ĐŸŃŃĐ”Đ·Đ°Đ”ĐŒ sid - ŃпДŃĐžŃĐžĐșа ŃĐŒŃ */
ELSE REFERRER_QUERY END AS QUERY_NK
FROM STG_ACCESS_LOG
) CLS
LEFT OUTER JOIN DIM_REFERRER TRG
ON (CLS.HOST_NK = TRG.HOST_NK AND CLS.PATH_NK = TRG.PATH_NK AND CLS.QUERY_NK = TRG.QUERY_NK)
WHERE TRG.DIM_REFERRER_ID IS NULLDie Promotion in die Tabelle der Benutzeragenten kann Bot-Logik enthalten, zum Beispiel ein SQL-Ausschnitt:
CASE
WHEN INSTR(LOWER(CLS.BROWSER),'yandex.com')>0
THEN 'yandex'
WHEN INSTR(LOWER(CLS.BROWSER),'googlebot')>0
THEN 'google'
WHEN INSTR(LOWER(CLS.BROWSER),'bingbot')>0
THEN 'microsoft'
WHEN INSTR(LOWER(CLS.BROWSER),'ahrefsbot')>0
THEN 'ahrefs'
WHEN INSTR(LOWER(CLS.BROWSER),'mj12bot')>0
THEN 'majestic-12'
WHEN INSTR(LOWER(CLS.BROWSER),'compatible')>0 OR INSTR(LOWER(CLS.BROWSER),'http')>0
OR INSTR(LOWER(CLS.BROWSER),'libwww')>0 OR INSTR(LOWER(CLS.BROWSER),'spider')>0
OR INSTR(LOWER(CLS.BROWSER),'java')>0 OR INSTR(LOWER(CLS.BROWSER),'python')>0
OR INSTR(LOWER(CLS.BROWSER),'robot')>0 OR INSTR(LOWER(CLS.BROWSER),'curl')>0
OR INSTR(LOWER(CLS.BROWSER),'wget')>0
THEN 'other'
ELSE 'n.a.' END AS AGENT_BOTAggregat-Tabellen
Zuletzt werden wir die Aggregat-Tabellen laden, zum Beispiel kann die tĂ€gliche Tabelle folgendermaĂen geladen werden:
SQL-Abfrage zum Laden des Aggregats
/* Load fact from access log */
INSERT INTO FCT_ACCESS_USER_AGENT_DD (EVENT_DT, DIM_USER_AGENT_ID, DIM_HTTP_STATUS_ID, PAGE_CNT, FILE_CNT, REQUEST_CNT, LINE_CNT, IP_CNT, BYTES)
WITH STG AS (
SELECT
STRFTIME( '%s', SUBSTR(TIME_NK,9,4) || '-' ||
CASE SUBSTR(TIME_NK,5,3)
WHEN 'Jan' THEN '01' WHEN 'Feb' THEN '02' WHEN 'Mar' THEN '03' WHEN 'Apr' THEN '04' WHEN 'May' THEN '05' WHEN 'Jun' THEN '06'
WHEN 'Jul' THEN '07' WHEN 'Aug' THEN '08' WHEN 'Sep' THEN '09' WHEN 'Oct' THEN '10' WHEN 'Nov' THEN '11'
ELSE '12' END || '-' || SUBSTR(TIME_NK,2,2) || ' 00:00:00' ) AS EVENT_DT,
BROWSER AS USER_AGENT_NK,
REQUEST_NK,
IP_NR,
STATUS,
LINE_NK,
BYTES
FROM STG_ACCESS_LOG
)
SELECT
CAST(STG.EVENT_DT AS INTEGER) AS EVENT_DT,
USG.DIM_USER_AGENT_ID,
HST.DIM_HTTP_STATUS_ID,
COUNT(DISTINCT (CASE WHEN INSTR(STG.REQUEST_NK,'.')=0 THEN STG.REQUEST_NK END) ) AS PAGE_CNT,
COUNT(DISTINCT (CASE WHEN INSTR(STG.REQUEST_NK,'.')>0 THEN STG.REQUEST_NK END) ) AS FILE_CNT,
COUNT(DISTINCT STG.REQUEST_NK) AS REQUEST_CNT,
COUNT(DISTINCT STG.LINE_NK) AS LINE_CNT,
COUNT(DISTINCT STG.IP_NR) AS IP_CNT,
SUM(BYTES) AS BYTES
FROM STG,
DIM_HTTP_STATUS HST,
DIM_USER_AGENT USG
WHERE STG.STATUS = HST.STATUS_NK
AND STG.USER_AGENT_NK = USG.USER_AGENT_NK
AND CAST(STG.EVENT_DT AS INTEGER) > $param_epoch_from /* load epoch date */
AND CAST(STG.EVENT_DT AS INTEGER) < strftime('%s', date('now', 'start of day'))
GROUP BY STG.EVENT_DT, HST.DIM_HTTP_STATUS_ID, USG.DIM_USER_AGENT_IDDie SQLite-Datenbank erlaubt komplizierte Abfragen. WITH enthĂ€lt die Vorbereitung der Daten und SchlĂŒssel. Die Hauptabfrage sammelt alle Verweise auf die MaĂe.
Die Bedingung lÀsst demnach die Historie nicht noch einmal laden: CAST(STG.EVENT_DT AS INTEGER) > $param_epoch_from, wobei der Parameter das Ergebnis der Abfrage ist
âSELECT COALESCE(MAX(EVENT_DT), â3600â) AS LAST_EVENT_EPOCH FROM FCT_ACCESS_USER_AGENT_DDâ
Die Bedingung lĂ€dt nur den vollen Tag: CAST(STG.EVENT_DT AS INTEGER) < strftime(â%sâ, date(ânowâ, âstart of dayâ))
Die ZĂ€hlung der Seiten oder Dateien erfolgt auf einfache Weise, indem ein Punkt gesucht wird.
Berichte
In komplexen Visualisierungssystemen gibt es die Möglichkeit, ein Metamodell auf Basis von Datenbankobjekten zu erstellen, Filter und Aggregationsregeln dynamisch zu steuern. Letztendlich erzeugen alle anstÀndigen Werkzeuge SQL-Abfragen.
In diesem Beispiel erstellen wir bereits fertige SQL-Abfragen und speichern sie als Views in der Datenbank â das sind die Berichte.
Visualisierung
Als Visualisierungswerkzeug wurde Bluff: Beautiful graphs in JavaScript verwendet.
DafĂŒr war es notwendig, mit PHP durch alle Berichte zu gehen und eine HTML-Datei mit Tabellen zu generieren.
$sqls = array(
'SELECT * FROM RPT_ACCESS_USER_VS_BOT',
'SELECT * FROM RPT_ACCESS_ANNOYING_BOT',
'SELECT * FROM RPT_ACCESS_TOP_HOUR_HIT',
'SELECT * FROM RPT_ACCESS_USER_ACTIVE',
'SELECT * FROM RPT_ACCESS_REQUEST_STATUS',
'SELECT * FROM RPT_ACCESS_TOP_REQUEST_PAGE',
'SELECT * FROM RPT_ACCESS_TOP_REQUEST_REFERRER',
'SELECT * FROM RPT_ACCESS_NEW_REQUEST',
'SELECT * FROM RPT_ACCESS_TOP_REQUEST_SUCCESS',
'SELECT * FROM RPT_ACCESS_TOP_REQUEST_ERROR'
);Das Tool visualisiert einfach die Ergebnistabellen.
Ausgabe
Anhand des Beispiels der Webanalyse beschreibt der Artikel die Mechanismen, die zur Erstellung von Data Warehouses erforderlich sind. Wie aus den Ergebnissen hervorgeht, sind die einfachsten Werkzeuge ausreichend fĂŒr eine tiefgehende Analyse und Visualisierung der Daten.
Im Folgenden werden wir anhand dieses Data Warehouses versuchen, Strukturen wie langsam sich Àndernde Dimensionen, Metadaten, Aggregationsebenen und die Integration von Daten aus verschiedenen Quellen zu implementieren.
AuĂerdem werden wir ein einfaches Tool zur Verwaltung von ETL-Prozessen auf der Basis einer einzigen Tabelle nĂ€her betrachten.
Kehren wir zum Thema der Messung der DatenqualitĂ€t und der Automatisierung dieses Prozesses zurĂŒck.
Wir werden die Probleme der technischen Umgebung und der Wartung von Data Warehouses untersuchen, indem wir einen Speicher-Server mit minimalen Ressourcen implementieren, beispielsweise auf Basis eines Raspberry Pi.
Quelle: habr.com
