(Web)browserautomatisierungen im B2B-Social

Ein aktuelles Experiment mit einem bekannten B2B-lastigen Socialmedium dreht sich um die Frage: „Wie-kann-ich-effektiv-fremde-Menschen-auf-mein-Geschäft-aufmerksam-machen?“ und der Ansatz lässt sich via Delphi über die folgenden Codezeilen realisieren:

WebBrowser:= TWebBrowser.Create(Form1);
TWinControl(WebBrowser).Name:= 'WebBrowser';
TWinControl(WebBrowser).Parent:= Form1;
WebBrowser.Silent := true;
WebBrowser.Visible:= true;
webbrowser.Navigate('blabla');
webbrowser.Destroy;

Aktuelle Blogcommentbeispiele

Bei einer Fotogalerie mit den Inhalten „Dom“ und „Domgestaltung“ erschienen vor kurzem die folgenden Commentspams:

(1) Ernährungsberatung
„🙂 Die Figur an der Lehne sieht ja mal witzig aus 🙂 Schöne Bilder die du da veröffentlichst, schiesst du die alle selber? Wenn ja mit welcher Kamera?“
Dieser Spam wurde von mir aus den folgenden Gründen nicht freigegeben:
– Nickname, welcher sich „[name]-freak“ nennt
– absolut keinen Bezug der bespammten Seite zu den Inhalten der Galerie
– keinerlei Querverbindung zwischen Kirche, Dom und „Ernährung“
– schlecht umgesetzte Affiliateseite, schlechtes Layout
– absolute Standardfrage zur Kamera
– mein Impressum und das Postverhalten zeigt, dass die Bilder von mir sind

(2) Amazon-Affiliatespammer für Kühlschränke
„Wirklich wunderschönes Chorgestühl. Die Detailverliebtheit und die Präzision mit der damals schon gearbeitet wurde, ist faszinierend.
Vielen Dank für diese schönen Bilder des Engel-Reliefs und der fein heraugearbeiteten Figuren. 🙂
Liebe Grüße,
[Fakename]“
Dieser Spam wirkte auf mich schon etwas intelligenter und ich dachte mir auch, dass man sich hier etwas mehr Gedanken gemacht hat. Letztendlich gab ich diesen Text nicht frei und die Gründe hierfür sind:
– Nickname passt nicht auf den Namen des Impressums
– Nickname war ein Frauenname, warum?
– keinerlei Querverbindung zwischen „Kühlschrank“ und „Dom“ oder „Kirche“ oder „Mittelalter“
– Warum wird sich hier bedankt?

(3) Affiliatespammer für Tiertransportboxen
„Das ist noch richtige Handwerks Arbeit!“
Die Ablehnungsgründe sind:
– Name, welcher nicht mit dem Impressum der bespammten Seite zusammen passt
– keine logische und inhaltliche Querverbindung zwischen Kirche, Dom, der Ort des Doms (Erfurt) und der bespammten Seite
– die Inhalte der Galerie zeigen „Handwerksarbeit“
– gegenwärtig schaffen Restauratoren, Holzkünstler usw. ähnliche Objekte
– Einzeiler mit Ausrufezeichen

(4) Affiliatespammer für Diäten
„Faszinierend, wie filigran die Kunstwerke bearbeitet wurden.“
Die Ablehnungsgründe sind:
– Name, der im Impressum nicht vorkommt
– Einzeiler!
– absolut keine inhaltliche Querverbindung zu Dom, Kirche und der bespammten Seite
– extrem schlecht umgesetzte Affiliatespamseite auf einem Freehoster

(5) Affiliatespammer für Kurzfristreisen, Reiseangebote
„Die sind mir bei meinem Besuch gar nicht aufgefallen. Schöne Fotos 🙂“
Die Ablehungsgründe sind hier:
– Name, der im Impressum nicht vorkommt
– absoluter Standard-Einzeiler
– Statement zeigt, dass man offensichtlich das Wichtigste der Stadt Erfurt völlig ignorierte
– „Schöne Fotos“ inkl. Smiley wirkt wie Freigabebettelei
– Quercheck auf die Seite zu „Erfurt“ zeigt made4SEO-Text ohne Bezug auf die Inhalte der Galerie

Bei der aktuellen Commentspam-„Welle“ fällt mir auf, dass die Akteure (auslesbar aus den jeweiligen Impressi) durchaus auch in unserer Branche aktiv sind. Genau hier frage ich mich, warum profane Kommentartexte, falsche Namen und die offensichtliche Zielgruppenirrelevanz trotz bekannter Erkenntnisse im Linkaufbau oder im Onlinemarketing (allgem.) offensichtlich zum Arbeitsalltag gehören.

Instagram: Download der Follower- und Abodatensätze (JSON)

Durch die API-Änderungen von Instagram wurde – wie bekannt – das Herunterladen dieser Datensätze etwas komplizierter. Die folgende Prozedure kann hier Abhilfe schaffen.

procedure TForm1.get_accounts(userid: string; uname: string; abfragetyp: string; maxerg: string; ziel: tmemo);
var
lHTTP: TIdHTTP;
IdSSL: TIdSSLIOHandlerSocketOpenSSL;
Params, login : TStrings;
Reply, _Token, X, lf: string;
Cookie: TIdCookie;
begin
// login - anfang
try
Params := TStringList.Create;
Params.Add('username='+igaccounts.Text);
Params.Add('password='+token_w.text);
lHTTP := TIdHTTP.Create(nil);
try
IdSSL := TIdSSLIOHandlerSocketOpenSSL.Create(lHTTP);
IdSSL.SSLOptions.Method := sslvTLSv1;
IdSSL.SSLOptions.Mode := sslmClient;
lHTTP.IOHandler := IdSSL;
lHTTP.ReadTimeout := 30000;
lHTTP.HandleRedirects := True;
lHTTP.Request.UserAgent := 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36';
lHTTP.Get('https://www.instagram.com', TStream(nil));
Cookie := lHTTP.CookieManager.CookieCollection.Cookie['csrftoken', 'www.instagram.com'];
if Cookie <> nil then
_Token := Cookie.Value;
try
lHTTP.Request.CustomHeaders.Values['X-CSRFToken'] := _Token;
lHTTP.Request.CustomHeaders.Values['X-Instagram-AJAX'] := '1';
lHTTP.Request.CustomHeaders.Values['X-Requested-With'] := 'XMLHttpRequest';
lHTTP.Request.Referer := 'https://www.instagram.com/';
lHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
lHTTP.Request.UserAgent := 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36';
Reply := lHTTP.Post('https://www.instagram.com/accounts/login/ajax/', Params);
finally
end;
finally
end;
Finally
// login => ende
// fans usw. erfassen
lHTTP.Request.UserAgent := 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36';
lHTTP.Request.Connection := 'keep-alive';
cookie := lHTTP.CookieManager.CookieCollection.Cookie['csrftoken', 'www.instagram.com'];
if cookie <> nil then
_token := cookie.Value
else
_token := '';
Params.Clear;
Params.Add('q=ig_user(' + userid + ') {'+LF+
' '+abfragetyp+'('+maxerg+') {'+LF+
' count,'+LF+
' page_info {'+LF+
' end_cursor,'+LF+
' has_next_page'+LF+
' },'+LF+
' nodes {'+LF+
' id,'+LF+
' is_verified,'+LF+
' followed_by_viewer,'+LF+
' requested_by_viewer,'+LF+
' full_name,'+LF+
' profile_pic_url,'+LF+
' username'+LF+
' }'+LF+
' }'+LF+
'}'+LF);
Params.Add('ref=relationships::follow_list');
lHTTP.Request.CustomHeaders.Values['X-CSRFToken'] := _token;
lHTTP.Request.CustomHeaders.Values['X-Instagram-AJAX'] := '1';
lHTTP.Request.CustomHeaders.Values['X-Requested-With'] := 'XMLHttpRequest';
lHTTP.Request.Referer := 'https://www.instagram.com/'+uname+'/';
lHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
lHTTP.Request.UserAgent := 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36';
Reply := lHTTP.Post('https://www.instagram.com/query/', Params);
ziel.Text:=reply;
end;
end;

Verwenden der Prozedur
Die Prozedur verlangt die Übergabe der Account-ID, des Account-Names, der maximalen Datensätze und die „Memo“, welche das Ergebnis der Abfrage für die weitere Verarbeitung erfassen soll.

Sie wird wiefolgt aufgerufen:
1) Fans holen
get_accounts(account_id,account_name, ‚followed_by.first‘,’9999′, ziel_memo);

2) Abos holen

get_accounts(account_id,account_name, ‚follows.first‘,’9999′, ziel_memo);

Die Zahl „9999“ lässt sich beliebig austauschen und sie beschreibt hier den Maximalwert, unter dem die Prozedur fehlerfrei funktioniert. Bei höheren Zahlen muss der Automatismus um eine geeignete Schleife erweitert werden.

Welche konkreten Funktionen lassen sich nun aus den Ergebnissen dieser Prozedur ableiten?
– Datenabgleich der Account-IDs mit den Likenden
– Datenabgleich der Account-IDs mit den Kommentierenden
– Erfassen und Auswerten des Fanengagements
– Erfassen von Likenden / Kommentierenden, welche weder Fans noch Abos sind
– Aktivitätsanalysen konkreter Zielgruppen
– Aufdecken und Analysen von Fakes
– Aktivitätsanalysen der Accounts aus den IDs via Postanalysen

Inspirationsquelle für den Code
How to get Instagram following list using http component in Delphi 10
Find my Instagram-ID

Instagramrecherchearbeiten: ohne API und Token

Folgende Varianten wurden von mir getestet und als „produktiv“ eingestuft:

1) Grobüberlick über einen Account
https://www.instagram.com/[Accountname]/?__a=1
Diese Abfragemöglichkeit zeigt – im Wesentlichen – genau dieselben Inhalte (Accountgrunddaten, Medien plus Likes // Comments, usw) an, als wenn man eine beliebige Account-URL mit Hilfe des Browsers aufruft. Das Ergebnis wird hier im JSON-Format präsentiert und lässt sich über die (hoffentlich) bekannten Scrape- und Auswertungsprozeduren weiter verarbeiten.

2) Suche nach Medien, Accounts und Locations
https://www.instagram.com/web/search/topsearch/?query=test
Diese Abfragemöglichkeit fasst die Tagsuche, Accountsuche und Locationsuche zusammen und präsentiert die Ergebnisse in JSON-Format. Inhalte sind die Mediadaten der Tags, die Grunddaten der Accounts (Name, Nickname, Fans und Abos, Posts usw.) und die Grunddaten der Locations

3) Grunddaten eines Accounts
https://www.instagram.com/query/?q=ig_user(ID){id,username,external_url,full_name,profile_pic_url,biography,followed_by{count},follows{count},media{count},is_private,is_verified}
Diese Abfragemöglichkeit zeigt die wesentlichen Grunddaten eines Accounts an. Diese sind hier: ID, Username, externe URL, Name, Profil-Bild-URL, Abos und Fans, Posts und Bio. Das Ausgabeformat ist JSON.

4) Accountmedien
https://www.instagram.com/[accountname]/media/?max_id=20
Diese Abfragemöglichkeit zeigt die letzten 20 Posts eines beliebigen Accounts inklusive der Rohdaten (Likes, Comments, Zeitstempel usw.) der jeweiligen Medien. Ein komplettes Scrapen großer Feeds erscheint hier als unmöglich.

Instagram: Bilder in der großen Auflösung finden und zum Download aufbereiten

Unser Instagram-„Bot“ verfügt nun in der Versionsnummer „0.7b“ über eine Backupfunktion. Diese umfasst im Wesentlichen das Sichern der Bilder aus den IG-Beiträgen des eigenen Accounts.
Diese Prozedure wurde wiefolgt realisiert:

1. Scrapen, Abholen des eigenen Streams
2. Speichern der Posturl-Liste in eine „Memo“
3. Schleife: (for-to-do) Ansurfen der Post-Urls
3.1. Quellcodeanalyse und suchen nach:
og:image“ content=“bildurl?ig_cache_key=key“
3.2. Entferne cache_key
3.3. Download der Bilder

Weiter lesen, Quellcodebeispiele
Datei aus dem WWW downloaden (Delphi)
Quelltext aus TWebbrowser auslesen
Rohdatenanalyse, IG-Account

Instagram, Tagreichweiten und Tags via Json holen und interpretieren.

Möchte man sich die aufwändige Entwicklung eines eigenen Parsers sparen, lässt sich der Export der Daten via API problemlos via JSON realisieren. Hier geht man wiefolgt vor:

[Einbindung der Unit]
uses json;

[Prozedur]
procedure tagdaten_holen;
var lauf: integer;
JSONArray: tJSONArray;
JSONValue: tJSONValue;
JSONPair: TJSONPair;
JSON: TJSONObject;
i: integer;
begin
try
debug.Text:=idhttp1.Get(‚https://api.instagram.com/v1/tags/search?q=’+tagrecherche.text+’&access_token=’+token.text);
JSONValue := TJSONObject.ParseJSONValue(debug.text);
JSON := TJSONObject.ParseJSONValue(debug.Lines.Text) as TJSONObject;
JSONArray := TJSONArray(JSON.Get(‚data‘).JsonValue);
with tagr_roh do
begin
cells[0,0]:=’Nr.‘;
cells[1,0]:=’Tag‘;
cells[2,0]:=’Reichweite‘;
colcount:=3;
rowcount:=1;
end;
for i := 0 to JSONArray.Size – 1 do
begin
with tagr_roh do
begin
cells[0,rowcount]:=inttostr(rowcount);
cells[1,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get(’name‘)).JsonValue.Value);
cells[2,rowcount]:=(TJSONPair(TJSONObject(JSONArray.Get(i)).Get(‚media_count‘)).JsonValue.Value);
rowcount:=rowcount+1;
end;
end;
except
showmessage(‚Fehler mit der Internetverbindung. Prüfe Diese und den Token!‘);
end;
end;

TWebbrowser: ID-lose Elemente anklicken

Bei einem aktuellen Projekt aus dem Bereich „Webautomatisierung“ musste das folgende Problem gelöst werden:

[Ausgangslage]
– Klicken von Tags
– anzuklickende Tags besitzen keine individuelle ID
– es existiert eine Tagliste, aus der der anzuklickende Tag ausgewählt werden muss
– anzuklickende Tags besitzen individuelle „innertext

[Vorgehensweise]

(1) Variablendeklaration
uses mshtml;
var ovElements, ovelements_1: OleVariant;

(2) Suchen und Klicken
ovElements_1 := WebBrowser1.OleObject.Document.all;
for j := 0 to (ovElements.Length - 1) do
if (ovElements_1.item(j).className = 'name') then
begin
if ovelements_1.item(j).innertext="wort" then
begin
ovElements_1.item(j).Click;
end;
end;

Weitere Infos:
TWebbrowser – Oledata

TWebbrowser: Einbindung und Arbeitsspeicherprobleme

Bei der Arbeit an den Webautomatismen entdeckte ich, dass der TWebbrowser enorm viel Arbeitsspeicher verbraucht. Das Problem lässt sich wie folgt lösen:

(1) Webbrowser – Prozess durchführen und auf Beendigung warten
(2) SetProcessWorkingSetSize(GetCurrentProcess, $FFFFFFFF, $FFFFFFFF); in den Code einfügen

Bei einem konkreten Projekt wurde hierüber der durchschnittliche Verbrauch von ca. 650MB auf 60-80MB (schwankend) gedrückt.

[Updatenotiz] Marktrecherche 0.8b

Die neue Version der Software wurde soeben fertig gestellt und verteilt.
Die folgende Galerie zeigt einige Ausschnitte aus der Pinterestrecherchefunktion. Hierbei habe ich das System der „Interessensmatrizen“ wieder aufgenommen.
Das Programm „verlangt“ die Eingabe eines Projektes inklusive der zu analysierenden Themenwelten. Hier wurden die Daten aus dem Projektbeispiel „Wohnen“ (=>wohnzimmer, schlafzimmer, kinderzimmer, badezimmer, bad, flur, haus, wohnung) analysiert.

(A) Screenshots

(B) Beispieldatensätze
Die nachfolgenden Excel-Tabellen sind Exports aus der Anwendung.
Sie zeigen:
(1) Rohdaten – Pinterest // Suchvorschläge
pint_suggest.xlsx
Die Datenerhebung erfolgt über die Funktionen, welche ich im Artikel „Pinterest – Suchvorschläge auslesen“ beschrieb.
(2) Rohdaten – Pinterest // Themenwelten
pint_rohdaten.xlsx
Die Datenerhebung erfolgt über die Funktionen, welche ich im Artikel „Pinterest – Themenwelten auslesen“ beschrieb.
(3) Pinterest // Themenwelten gewichten, Zusammenzählung
Um die erhobenen Daten in ein interpretationsfähiges Format zu bringen, werden in der Anwendung die gefundenen „Unterthemen“ oder „weiterführende Themen“ zusammen gezählt und nochmal in Zuordnung zu den „Themenwelten“ dargestellt. Die Datei pint_zfg.xlsx zeigt das entsprechende Beispiel.

[Updatenotiz] Schliessung vom Suchvorschlagstool und Übertrag in „Marktrecherche“

Nach einigen Zahlenstudien, Gesprächen und Experimenten kam ich zum Entschluss, das besprochene Projekt „globaler“ und weitestgehend unabhängig von den üblichen und fast schon klischeehaften Keywordrechercheprozeduren weiter zu entwickeln. Im nächsten Schritt werden hier alle Analysealgorithmen zusammen gefasst, welche in den letzten Jahren gestaltet worden sind.
Die Versionsnummer 0.5b wird folgendes umfassen:

(A) Suchmaschinen
[Datenquelle „Google“]
– Suchvorschläge
– generierte Fragen analog zum „W-Fragen-Tool“
– generierte Phrasen auf Basis von Städten für die Vermarktung von lokal abhängigen „Dingen“
– GoogleTrends

[Datenquelle „Bing“]
– Suchvorschläge

(B) Socialmedia
[Datenquelle „Instagram“]
– Tagrecherche zzgl. Reichweitenauswertung(en)

[Datenquelle „Pinterest“]
– Suchvorschläge
– Themenweltenanalyse(n)

(C) Recherchefunktionen
– Abgleich: Suma <> Socialmedia
– Abgleich: G-Trends <> Socialmedia

Weitere potentielle Datenquellen, welche sich aktuell in einer Art „Werthaltigkeitsanalyse“ befinden, sind:
– GooglePlaces inkl. Öffnungszeiten und ggf. vorhandenen Bewertungen
– GoogleShopping inkl. Preisdifferenzen und ggf. vorhandenen Bewertungen
– Twitter: Hashtagsuche und Trends
– Gplus: Hashtagauswertung
– Facebookpages, sofern „offen“ und auslesbar
– Youtube: Suchvorschläge (?), Taganalysen (!)
– Keywordplaner, sofern kostenlos abrufbar (muss ich noch prüfen)