Instagram scrapen: ohne Token, Teil 2

Diverse Änderungen zwangen mich zur Recherche nach weiteren Endpoints mit inkl. JSON-Export

1. Suche nach Orten

//www.instagram.com/web/search/topsearch/?context=place&query=%22germany%22

2. Suche nach Accounts (öffentliche)

//www.instagram.com/web/search/topsearch/?context=user&query=%22germany%22

3. Suche nach Orten UND Accounts

//www.instagram.com/web/search/topsearch/?context=blended&query=%22germany%22

4. Suche nach Orten, Variante mit Pseudotoken

https://www.instagram.com/graphql/query/?query_hash=ac38b90f0f3981c42092016a37c59bf7&variables={%22id%22:%22236942553%22,%22first%22:12,%22after%22:%22XXXXXXXX%22}

Notiz: Query_Hash ggf. gegenprüfen und Alternativen organisieren!

Tensorboard, div. Tutorials (Sammlung)

Basics

How to Use TensorBoard?“ (Grundlagenartikel zum Verständnis der Funktionsweisen)

What is the word embedding in Tensorflow with Tensorboard’s Embedding projector“ (Grundlagenartikel mit einfachen Importfunktionen für den Projector)

Anaconda / Git (allgem. Artikel mit Erklärung zur Installation v. Git auf Anaconda)

gensim2tensorboard (“ Train word embeddings with gensim and visualize them with TensorBoard.“)

TensorFlow and deep learning, without a PhD (Tutorial, recht einfach gehalten)

InstaLoc2.0, aktuelle Entwicklungen und erste Länderdarstellungen

Vor ca. 3 Wochen beendete ich den Update von InstaLOC1.0 auf InstaLOC2.0. Dies wurde u.a. notwendig, da Instagram tiefgreifendere API-Änderungen durchgeführt hatte und daher wurden die Scrapingalgorithmen umgestellt.

Im Aufwand steckt natürlich auch ein Vorteil und der ergibt ein Datensatzvolumen von 2 Mio Stück je Arbeitstag Arbeitscomputer.

Folgende Bilder zeigen einige erfasste Länder.

Folgende Bilder zeigen Weltkarten mit Filter auf diverse (Luxus)marken

InstaLOC. Wechsel der Datenerfassung und Konzepterweiterung

Um die Jahreswende veranlasste ich den angekündigten Wechsel der Datenerfassungstechnologie. Im Zuge dessen fanden konzeptionelle „Brainstormings“ bzgl. einer Abstrahierung sämtlicher Auswertungsansätze statt. Dies bedeutet, dass die Erkenntnisse aus dem Instagramuniversum auf weitere Plattformen und deren offenen Schnittstellen angewendet werden. Beispiele hierfür sind – aktuell – Twitter, Youtube, Pinterest, Tumblr und in bestimmten / eng gefassten Ansätzen Weibo + Facebook.

Interessant(er) geworden sind die Erkenntnisse aus den Zusammenführungen der anonymisierten InstaLOC-Rohdaten mit Openstreetmap. Folgende Datei / Tabelle verdeutlicht den aktuellen Datenbestand auf Basis von ca. 1.4Mio / 2.5 Mio abgeglichenen Geodaten:

Download: Instaloc_ Datenbestand/Statistik (Ergebnisse aus der Prüfung auf eine ca. 60GB umfassende SQLite-DB)

Die Datei beschreibt die Auflistung aller erfassten Länder nach Aufsummierung der codierten User, der Postings und – im experimentellen Status – der Aktiviäten in Form der Aufsummierten Zeitstempel. Hochinteressant ist die Position von Deutschland, was Rückschlüsse auf die Landesrolle im Sinne des Marketings auf der Plattform vermuten lässt.

In den nächsten 2 Wochen werde ich mich tiefergehender mit diversen Datamining- und Datenauswertungsvarianten auseinander setzen und nach Visualisierungsoptionen recherchieren. Die (Teil)fokussierung auf Geodaten erzwingt bspw. die Anbindung an die bekannten Maps-Services (Googlemaps, Openstreetmap etc.).

Gesprächsanfragen bitte an office(at)pontipix.de senden. Freigaben von Informationen und das Zulassen tiefergehender Gespräche werden in einem 4er Team diskutiert.

Hinweis: SQLite-Dateien in Excel öffnen und da weiter verarbeiten (InstaLOC und HashtagDB)

Irgendwer aus dem „Netzwerk“ gab mir bei einem Telefonat einen interessanten Hinweis zu den Potentialen von Excel. Wir sprachen da u.a. darüber, dass die freigegebenen SQLite-Datenbanken aus den Projekten „Hashtag-Reichweiten“ und „InstaLoc“ zu viele Auswertungs- und Analyseprobleme provozieren können.

Eine Option beschreibt
https://yourbusiness.azcentral.com/connecting-excel-sqlite-12971.html

und man benötigt für die dargestellte Funktion das Plugin http://www.ch-werner.de/sqliteodbc/.

Möchte man hierüber unsere Dateien anbinden, muss an der entsprechenden Einlesestelle (siehe verlinkter Blogbeitrag!) die Option „SQLite-3“ ausgewählt werden. Die nachgeschaltete EXCEL-Oberfläche ist relativ selbsterklärend. Mit den konkreten Auswertungsfunktionen habe ich mich nicht beschäftigt und ich kann an der Stelle auch keinerlei Support zu den Queries geben.

InstaLOC: Freigabe der ersten Themendatenbank (Home&Living)

Gestern veranlasste ich die Freigabe der ersten Datenbank, welche den DACH-Raum und Teile der EU nach den Themenwelten der Sparte „Home & Living“ abbildet.

Die Grunddaten der SQLite-Datei sind:

Speicher: ca. 5GB
Beiträge: 8.532.446
User_innen: 2.495.311
Locations: 473.903
Medienzeitstempel: 4.863.183

Interessierte Leser_innen des Arbeitsblogs können sich zu der Datenbank unter den folgenden Auszügen weitergehende Eindrücke verschaffen:

(1) Locationliste + Sum(Postings), unverschlüsselt
https://drive.google.com/open?id=18rVNt2zTfKBmUl606O-L1mMsExvhI4iH
(2) Locationliste + Sum(Likes), unverschlüsselt
https://drive.google.com/open?id=1S3QMkwyo82w3SkH7YhrlZoixZRNlsSiI
(3) Medienpostzeitstempel, unverschlüsselt
https://drive.google.com/open?id=1EtRVSRuFhI5oyE9s6KHBcGKrDF_ARMRv
(4) Tagwolkenliste, unverschlüsselt
https://drive.google.com/open?id=14tXw59I3ky7_bbQ5znG_D4dSGo0rYfkw

Hinweise:
Hinter den CSV-Dateien stehen entsprechende SQL-Statements, die in diesem Blog dokumentiert wurden und dokumentiert werden. Die Datensätze sind unkategorisiert und nicht interpretiert: sie befinden sich quasi in einem Rohdatenzustand.

Supportanfragen, Interpretationsanfragen oder Anfragen zur Strategieableitung bitte an:
Anne Lehmann: office(at)pontipix.de (Wobus & Lehmann GbR), Preisliste unter: https://www.pontimania.de/
Dr. Klaus Holthausen: https://www.linkedin.com/in/dr-klaus-holthausen-1464b043/

InstaLOC: Etablierung der Themenwelten-Datenbanken und Datenauszüge aus dem Komplex „Home & Living“

Inhalte der Datenbank: ca. 12.000 * 2000 Beiträge aus „Home&Living“-bezogenen Themenabfragen
Speichergröße der Datenbank: ca. 1.6GB, Format: SQLite

Beispielrohdatensätze
Abfrage via SQLiteStudio
select REPLACE(tag,'%22','"') as Tagwolke, likes, comments, location, erstellzeit from locations where tag like '%[keyword]%'

Datenlinks (CSV)
(1) Tagwolke: Carport.csv (2MB)
(2) Tagwolke: Hausbau.csv (gepackt: 10MB, entpackt: 64MB)
(3) Tagwolke: Haus.csv (gepackt: 27MB, entpackt: 147MB)

CSV-Schema
Tagwolke|Likes|Coments|Location(Geocode+ID+Titel)|Erstellzeitstempel

Allgemeine Datensätze
(1) Liste: Locations.csv (gepackt: 7MB, entpackt: 23MB)

Hinweise
Durch die modifizierte Abfrage wurde die Tagwolke etwas lesbarer gemacht. Man muss bei der Sichtung und Prüfung jedoch die Umlaute und Piktogramme bei Bedarf noch umwandeln, oder eben auf unseren Client zurückgreifen. Die Beispieldatensätze beinhalten nicht die Postingurl, Fotofilter und Usernames und erlauben daher keinerlei Rückschlüsse auf eventuelle Influencer. Diese Angaben werden bei Buchung der kostenpflichtigen Angebote (Workshops, Datenabfragen etc.) offen gelegt und – bei Bedarf – erklärt.

(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

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;