Update: IG Locationclient v.0.9B und die Einführung des Prinzips „Themenwelten“

Diverse Überlegungen, Gespräche und Dateninterpretationsideen ergaben die Notwendigkeit der Einführung sog. „Themenwelten“. Die aktuelle Version der Auswertungssoftware erlaubt nun den Auswurf von Überblickstabellen.

Beispiele:
(1) Datenschau nach vordefinierten Locations
-> Suche nach dem Substringschema
themenwelt_loc_ueberblick.xlsx

(2) Datenschau nach vordefinierten Tags|Terms
-> Suche nach dem Substringschema UND Suche nach dem genauen Ausdruck
themenwelt_tags_ueberblick.xlsx

Die verlinkten Beispiele sind Probedatenauszüge, um u.a. auch die Performance zu testen.

ToDo für die Versionsnummer 0.10Beta:

(1) Zielgruppenidentifikation nach folgenden Fragesschemata (Übersetzung in SQL folgt).

(a) Welche Gruppen sprechen in [Ort] über [Tag 01|Tag02|Tag03]?
(b) Welche Gruppen springen von [Ort 01] zu [Ort 02]?
(b) Welche Gruppen springen von [Ort 01] zu [Ort 02] und sprechen über [Tag 01|Tag02|Tag003]?

(2) Aktivierung der Beliebtheitsindikatoren für die Datenschau.

(a) Welche Tags sind beliebt?
-> Einbeziehung der Likes und Comments in Form der Zahlen.
-> (Er)findung eines geeigneten Bewerungssystems auf Basis der Nutzwertanalyse.

(b) Welche Orte sind beliebt?
-> siehe (a)
-> Erweiterung um die Begutachtung der Postzeitstempel.

Locationprojekt, aktuelle Datenbanken (Stand: 03.08.2018)

In der aktuellen Woche fand ich Gelegenheit zu einer finalen Optimierung der Datenerhebungsfunktionen und der maximale Output beträgt nun 5 Städte / Arbeitstag (ca. 8Stunden). Der aktuelle Datenbestand umfasst nun 41 SQLite-Datenbanken und lässt sich hier (Download: loc_datenbestand.xlsx) einsehen.

Ein Update der Auswertungssoftware ist für den 05.08.2018 geplant.

Kontakte? Anfragen gern unter office(at)pontipix.de.

(Instagram)-Locationprojekt, Hashtagcloudanalysen nach Usernamen (Leipzig-Datenbank)

Das Programm kann, in der aktuellen Version, zwischen „%suchwort%“ und „=suchwort“ unterscheiden. Bei der letzteren Option werden alle Ergebnisse ausgeworfen, wo der Term – als Hauptstring – in den Tagclouds zu finden ist (Gegenteil zu der Substringvariante).

Inhaltsschema:
Spalte (1) -> Nummer
Spalte (2) -> Username
Spalte (3) -> Tagcloud
Spalte (4) -> Summe Tagcloud, Vorkommen der Tagcloud je Username

Hinweis:
Sämtliche Accountnamen sind in den nachfolgenden Datensätzen – selbstverständlich – verschlüsselt.

(1) Substring: „city“: city_usersum
(2) Substring: „mensa“: mensa_usersum
(3) Hauptstring: „sport“: -sport-_usersum
(4) Substring: „sport“: sport_usersum
(5) Substring: „fest“: fest_usersum
(6) Substring: „farbfilm“: farbfilm_usersum
(7) Substring: „sommer“: sommer_usersum
(8) Substring: „frühling“: frühling_usersum
(9) Substring: „herbst“: herbst_usersum
(10) Substring: „stadt“: stadt_usersum
(11) Hauptstring: „messe“: -messe-_usersum
(12) Substring: „hafen“: hafen_usersum
(13) Substring: „halle“: halle_usersum
(14) Substring: „design“: design_usersum
(15) Substring: „hochschule“: hochschule_usersum
(16) Substring: „kunst“: kunst_usersum
(17) Substring: „film“: film_usersum
(18) Substring: „dokleipzig“: dokleipzig_usersum
(19) Substring: „leipzig“: leipzig_usersum
(20) Hauptstring: „urlaub“: -urlaub-_usersum
(21) Substring: „urlaub“: urlaub_usersum
(22) Substring: „bar“: bar_usersum
(23) Substring: „deko“: deko_usersum
(24) Substring: „feiern“: feiern_usersum

Locationprojekt, aktuelle Datenbanken

Mit Optimierung des Scrapers wurde der Output auf 3 Städte (zu 1000-3.500 Locations mit 2000 Medien / Location) je Durchlauf erhöht.

Der Datenbestand umfasst (derzeit) das folgende Volumen:

Datenbank: locations_berlin.db
Anz. Einträge: 178640
Anz. Filter: 45
Anz. Postings: 177454
Anz. Tagclouds: 113530
Anz. Locations: 1193
Anz. Accounts: 93386
Anz. Zeitstempel: 177089

Datenbank: locations_koeln.db
Anz. Einträge: 251900
Anz. Filter: 45
Anz. Postings: 247799
Anz. Tagclouds: 174640
Anz. Locations: 2294
Anz. Accounts: 111119
Anz. Zeitstempel: 247224

Datenbank: locations_leipzig.db
Anz. Einträge: 165272
Anz. Filter: 44
Anz. Postings: 161236
Anz. Tagclouds: 115534
Anz. Locations: 1507
Anz. Accounts: 62306
Anz. Zeitstempel: 160963

Datenbank: locations_london.db
Anz. Einträge: 647379
Anz. Filter: 45
Anz. Postings: 635175
Anz. Tagclouds: 363882
Anz. Locations: 3254
Anz. Accounts: 363278
Anz. Zeitstempel: 630993

Datenbank: locations_muenchen.db
Anz. Einträge: 257895
Anz. Filter: 45
Anz. Postings: 252130
Anz. Tagclouds: 175017
Anz. Locations: 2445
Anz. Accounts: 126274
Anz. Zeitstempel: 251593

Datenbank: locations_paris.db
Anz. Einträge: 409348
Anz. Filter: 45
Anz. Postings: 399706
Anz. Tagclouds: 236446
Anz. Locations: 1158
Anz. Accounts: 241158
Anz. Zeitstempel: 396092

(Instagram)-Locationprojekt, Hashtagcloudanalysen nach Orten (Berlin-Datenbank)

Heute fand ich Gelegenheit, das Projekt in einer ersten Version zu compilieren. Dieses beinhaltet auch die Funktion der Auflistung von Tagclouds nach Locations inkl. deren Aufsummierung.

Folgende Dateien liefern erste Interpretationsgrundlagen zu den Locationbewertungen.

Inhaltsschema:
Spalte (1) -> Nummer
Spalte (2) -> Tagcloud
Spalte (3) -> Location
Spalte (4) -> Location-ID
Spalte (5) -> Summe Tagcloud, Vorkommen der Tagcloud in der Location

Beispiele.
Die nachfolgenden Datensätze basieren auf die Query „like ‚%suchwort%'“ und erfragen die Ergebnisse auf Substringebene.

(1) Substring: „working“: working_locsumm
(2) Substring: „work“: work_locsumm
(3) Substring: „office“: office_locsumm
(4) Substring: „neukölln“: neukölln_locsumm
(5) Substring: „kunst“: kunst_locsumm
(6) Substring: „job“: job_locsumm
(7) Substring: „happy“: happy_locsumm
(8) Substring: „foodporn“: foodporn_locsumm
(9) Substring: „food“: food_locsumm
(10) Substring: „feiern“: feiern_locsumm
(11) Substring: „fashion“: fashion_locsumm
(12) Substring: „coworking“: coworking_locsumm
(13) Substring: „berlin“: berlin_locsumm
(14) Substring: „advokat“: advokat_locsumm

Datenexporte: Instagram-Locationprojekt, Teil (1)

(1) Locationrecherche nach Auflistung der aktiven Accounts

SELECT location, Anz_user from(
Select location, count(username) as Anz_user from locations GROUP BY location ) as my_table
WHERE (Anz_user >= 2) order by Anz_user DESC

Diese Abfrage listet alle Locations auf, wo mind. 2 Accounts Postings abgesetzt hatten.
Datei (Paris): loc_2

(2) Locationrecherche nach Auflistung der aktiven Accounts, Anpassung nach Locationtyp

SELECT location as Ort, Anz_user from(
Select location, count(username) as Anz_user from locations GROUP BY location ) as my_table
WHERE (Anz_user >= 2) AND (location like '%museum%') order by Anz_user DESC

Diese Abfrage listet alle Locations auf, wo mind. 2 Accounts Postings abgesetzt hatten und erlaubt die Feinprüfung auf Locationtyp.
Datei (Paris), Hotel: loc_2_hotel
Datei (Paris), Museum: loc_2_museum

(3) Location- und Accountauflistung nach Suchwort (Tagsubstring)
select location, tag, username from locations where tag like '%fashion%' group by username;

Diese Abfrage listet alle Locations und Accounts auf, welche etwas zu „Fashion“ gepostet hatten
Datei (Paris), „fashion“: paris_fashion

(4) Userauflistung nach Suchwort UND Mindestaufkommen in Bezug auf die Locationaufsummierung

SELECT username, numb from(
Select username, tag, count(location) as numb from locations GROUP BY username ) as my_table
WHERE (numb >= 2) and (tag like '%fashion%') order by numb DESC;

Diese Abfrage listet alle erfassten Accounts auf, welche an mind. 2 Locations aktiv etwas zu „Fashion“ schrieben.
Datei (Paris), Loc=2, „fashion“: user_tag_fashion_2

Spezial-SQLabfragen (Projekt: Instagramlocations), Stand: 14.07.2018

(1) Ausgabe der Usernames als Auflistung, mit Mindestanzahl in Bezug auf die Locations

SELECT username, numb from(
Select username, count(location) as numb from locations GROUP BY username ) as my_table
WHERE numb >= 2 order by numb DESC;

(2) Ausgabe der Usernames als Auflistung mit Mindestanzahl in Bezug auf die Locations UND Vorkommen eines Substrings in den Tagwolken

SELECT username, numb from(
Select username, tag, count(location) as numb from locations GROUP BY username ) as my_table
WHERE (numb >= 2) and (tag like '%fashion%') order by numb DESC;

(3) Ausgabe der Locations als Auflistung mit Mindestanzahl in Bezug auf die erfassten User_innen

SELECT location, Anz_user from(
Select location, count(username) as Anz_user from locations GROUP BY location ) as my_table
WHERE (Anz_user >= 2) order by Anz_user DESC

(4) Ausgabe der Locations als Auflistung mit Mindestanzahl in Bezug auf die erfassten User_innen UND Vorkommen eines Substrings in den Locations

SELECT location, Anz_user from(
Select location, count(username) as Anz_user from locations GROUP BY location ) as my_table
WHERE (Anz_user >= 2) AND (location like '%hotel%') order by Anz_user DESC;

(5) Ausgabe der Locations als Auflistung mit Mindestanzahl in Bezug auf die erfassten User_innen UND Vorkommen eines Substrings in den Locations UND Vorkommen eines Substrings in den Tagwolken

SELECT location, Anz_user from(
Select location, tag, count(username) as Anz_user from locations GROUP BY location ) as my_table
WHERE (Anz_user >= 2) AND (location like '%hotel%') AND (tag like '%fashion%') order by Anz_user DESC;

Basis-SQLabfragen (Projekt: Instagramlocations)

Notiz für mich:
Auflistung der Standardabfragen, ohne Anspruch auf Tiefenanalysen.

(1) Statistiken, „KPI“
select count(*), count(distinct(filter)), count(distinct(url)), count(distinct(tag)), count(distinct(location)), count(distinct(username)), count(distinct(erstellzeit)) from locations;

(2) Überblick, Auflistung der erfassten Accounts
select username from locations group by username;

(3) Auflistung der erfassten Accounts zzgl. Zusammenzählung der Beiträge der jeweiligen Accounts
select username, count(username) from locations group by username order by count(username) DESC

(4) Auflistung der erfassten Accounts zzgl. Zusammenzählung der Beiträge der jeweiligen Accounts + Zusammenzählung der Locations
select username, count(username), count(distinct(location)) from locations group by username order by count(distinct(location)) DESC;

(5) Auflistung der erfassten Locations zzgl. Zusammenzählung der Locations
select location, count(location) from locations group by location order by count(location) DESC;

(6) Auflistung der erfassten Locations zzgl. Zusammenzählung der Locations bei Vorkommen eines Tags / Zeichenketten
select location, count(location) from locations where tag like '%gucci%' group by location order by count(location) DESC

(7) Auflistung der erfassten Usernames zzgl. Zusammenzählung der Usernames bei Vorkommen eines Tags / Zeichenketten
select username, count(username) from locations where tag like '%fashion%' group by location order by count(username) DESC

(8) Auflistung der erfassten Tags zzgl. Zusammenzählung der Tags bei Vorkommen von zwei gesuchten Tags / Zeichenketten
select tag, count(tag) from locations where (tag like '%fashion%') and (tag like '%woman%') group by location order by count(tag) DESC;

Instagram – Zielgruppenerfassungen via Locations, Projektstart

Vor einigen Tagen entschied ich mich dazu, die Erfassungsfunktionen von CSV in SQLite zu wechseln.

Hierbei hat die entsprechende Datenbankdatei folgende Struktur (Einrichtungsprozedur):

procedure TForm1.LocationsMain1Click(Sender: TObject);
begin
with sql_befehle do
begin
clear;
lines.Add('drop table if exists locations;');
lines.Add('CREATE TABLE `locations` (');
lines.Add(' `id` integer primary key AUTOINCREMENT,');
lines.Add('`url` varchar(400),');
lines.Add('`tag` varchar(800),');
lines.Add('`likes` varchar(400),');
lines.Add('`comments` varchar(400),');
lines.Add('`erstellzeit` varchar(400),');
lines.Add('`post_id` varchar(1600),');
lines.Add('`username` varchar(400),');
lines.Add('`location` varchar(400),');
lines.Add('`filter` varchar(400),');
lines.Add('`pruefzeit` varchar(400)');
lines.Add(');');
lines.Add('vacuum;');
end;
fdquery3.ExecSQL(sql_befehle.text);
end;

Mit Datenbankwechsel wurde der Scraper so optimiert, dass an einem typischen Arbeitstag zwischen 1200 und 4500 Locations zu max. 1500 Einträge erfasst werden können. Erste Testläufe mit dem Raum Paris / Frankreich ergaben eine Ausbeute von ~400.000 Einträgen mit Aktivitäten von ~200.000 Accounts (Unique!).

Rohdatenerfassung zu den Locations via Instagram-API (finaler Ansatz)

Im Zuge des Roland-Berger-Projektes fand ich endlich Gelegenheit, den Locationrohdatenscraper zu optimieren. Hier nun der Quellcode:

(1) Scrapingprozedur
Erklärung:
get_loc_short(mytable: TStringGrid; locid: string; rounds: integer);
mytable -> Stringgridobjekt, verlangt die Übergabe des Objektnamens
locid -> LocationID aus Instagram (siehe: Locationparser)
rounds -> maximale Anzahl der Subprüfung je Durchlauf

procedure TForm1.get_loc_short(mytable: TStringGrid; locid: string; rounds: integer);
var
JSONArray: tJSONArray;
JSONValue,jvalue: tJSONValue;
JSONPair: TJSONPair;
JSON, json_sub: TJSONObject;
size: integer;
j_array: tJSONArray;
s: string;
i,j: integer;
next_id: string;
zaehl: integer;
begin
zaehl:=0;
try
debug.text:=idhttp1.Get('https://api.instagram.com/v1/locations/'+locid+'/media/recent?access_token='+token.text);
JSONValue := TJSONObject.ParseJSONValue(debug.text);
JSON := TJSONObject.ParseJSONValue(debug.Lines.Text) as TJSONObject;
JSONArray := TJSONArray(JSON.Get('data').JsonValue);
try next_id:= JSONValue.GetValue('pagination.next_url');
except
next_id:='N/A';
end;
for i := 0 to JSONArray.Size - 1 do
begin
with mytable do
begin
cells[0,rowcount]:=inttostr(rowcount);
cells[1,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('link')).JsonValue.Value);
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('tags')).ToString);
s:= StringReplace(s, '"tags":[', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, ']', '', [rfReplaceAll,rfIgnoreCase]);
cells[2,rowcount]:=s;
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('likes')).ToString);
s:= StringReplace(s, '"likes":{"count":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
cells[3,rowcount]:=s;
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('comments')).ToString);
s:= StringReplace(s, '"comments":{"count":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
cells[4,rowcount]:=s;
cells[5,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('created_time')).JsonValue.Value);
cells[5,rowcount]:=datetimetostr(UnixToDateTime(strtoint(cells[5,rowcount])));
cells[6,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('id')).JsonValue.Value);
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('user')).ToString);
s:= StringReplace(s, '"user":{"username":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '"', '', [rfReplaceAll,rfIgnoreCase]);
cells[7,rowcount]:=s;
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('location')).ToString);
s:= StringReplace(s, '"location":', '', [rfReplaceAll,rfIgnoreCase]);
cells[8,rowcount]:=s;
cells[9,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('filter')).JsonValue.Value);
cells[10,rowcount]:=datetimetostr(now);
rowcount:=rowcount+1;
end;
grdColWidth(mytable, 40);
end;
except
end;
repeat
// -> tiefenpruefung
try
debug.text:=idhttp1.Get(next_id);
JSONValue := TJSONObject.ParseJSONValue(debug.text);
JSON := TJSONObject.ParseJSONValue(debug.Lines.Text) as TJSONObject;
JSONArray := TJSONArray(JSON.Get('data').JsonValue);
try next_id:= JSONValue.GetValue('pagination.next_url');
except
next_id:='N/A';
end;
for i := 0 to JSONArray.Size - 1 do
begin
with mytable do
begin
cells[0,rowcount]:=inttostr(rowcount);
cells[1,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('link')).JsonValue.Value);
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('tags')).ToString);
s:= StringReplace(s, '"tags":[', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, ']', '', [rfReplaceAll,rfIgnoreCase]);
cells[2,rowcount]:=escape(s);
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('likes')).ToString);
s:= StringReplace(s, '"likes":{"count":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
cells[3,rowcount]:=s;
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('comments')).ToString);
s:= StringReplace(s, '"comments":{"count":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
cells[4,rowcount]:=s;
cells[5,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('created_time')).JsonValue.Value);
cells[5,rowcount]:=datetimetostr(UnixToDateTime(strtoint(cells[5,rowcount])));
cells[6,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('id')).JsonValue.Value);
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('user')).ToString);
s:= StringReplace(s, '"user":{"username":', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '}', '', [rfReplaceAll,rfIgnoreCase]);
s:= StringReplace(s, '"', '', [rfReplaceAll,rfIgnoreCase]);
cells[7,rowcount]:=s;
s:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('location')).ToString);
s:= StringReplace(s, '"location":', '', [rfReplaceAll,rfIgnoreCase]);
cells[8,rowcount]:=s;
cells[9,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get('filter')).JsonValue.Value);
cells[10,rowcount]:=datetimetostr(now);
rowcount:=rowcount+1;
end;
grdColWidth(mytable, 40);
end;
except
end;
// -> tiefenpruefung, ende
zaehl:=zaehl+1;
until zaehl=rounds;
//uebertrag auf tabelle
grdColWidth(mytable, 40);
form1.Caption:=version+' alle Posts herunter geladen';
end;

(2) Aufruf der Scrapingprozedur
procedure TForm1.Button45Click(Sender: TObject);
var lauf: integer;
begin
with locmedia do
begin
cells[0,0]:='Nr.';
cells[1,0]:='URL';
cells[2,0]:='Tag';
cells[3,0]:='Likes';
cells[4,0]:='Comments';
cells[5,0]:='Erstellzeit';
cells[6,0]:='ID';
cells[7,0]:='User';
cells[8,0]:='Location';
cells[9,0]:='Filter';
colcount:=10;
rowcount:=1;
end;
for lauf := 1 to locroh.RowCount do
begin
randomize;
token.Text:=token.Items[random(token.Items.Count-1)];
get_loc_short(locmedia,locroh.Cells[1,lauf],20);
savetocsv(locmedia,verz+'\support\dummydata.csv');
form1.Caption:=version+' Locationscraper: '+inttostr(lauf)+' / '+inttostr(locroh.RowCount);
delay(1000);
end;
end;