Jak odczytać maile z Gmaila w R?

Wszystko zaczęło się od awarii Home.pl i jednego postu na Facebooku…

Post, o którym mowa to screen shot skrzynki mailowej pełnej informacji od czytelników Niebezpiecznika donoszących o awarii serwerów Home.pl (o samej awarii tutaj).

W komentarzach pojawił się pomysł, żeby stworzyć mechanizm informujący, że w skrzynce odbiorczej wzrasta liczba maili dotyczących określonego tematu. Jak do tego podejść?

Zastanówmy się czego potrzebujemy, aby taki “sygnalizator” przygotować. W najprostszej formie, przynajmniej na początek. Potrzebujemy:

  • znać treść maili
  • wiedzieć czy dane słowo nagle pojawia się częściej niż wcześniej (jeśli tak – dajemy sygnał)

Zajmijmy się najpierw drugim punktem. To zwykłe łapanie anomalii – czy to będzie zmiana temperatury, zmiana obciążenia procesorów na serwerach, nagły wzrost ruchu na stronie (czy w skrzynce mailowej) czy też kurs akcji – zagadnienie jest analogiczne. Można je sprowadzić do sprawdzania stanu wskaźnika (temperatury, ruchu, liczby maili z określonym słowem itd.) co jakiś czas i jeśli wskaźnik jest większy niż dane historyczne to alarmujemy. O ile w przypadku jakiegoś pomiaru tej samej wartości nie jest to problem (w dużym uproszczeniu mamy szereg czasowy, na przykład: godzina pomiaru i temperatura) o tyle w przypadku treści maili może być problem. Bo czy mamy mierzyć wszystkie słowa jakie przyjdą nam do głowy? Taki Niebezpiecznik może dostać masę maili o awarii w Home.pl (jak na powyższym przykładzie) ale równie dobrze może to być awaria Facebooka, Twittera, czy jakiegokolwiek innego serwisu albo u jakiegokolwiek dostawcy. Jak mamy przewidzieć jakie słowo nagle się pojawi jako bardzo popularne? Co ze słowami, których nie przewidzimy?

Nie wiem na pewno jak działa mechanizm trendów na Twitterze (nikt tego nie wie, ale w poście sprzed ponad roku linkowałem kilka tekstów i wideo na ten temat) a wydaje się być bliski naszym potrzebom. Ale nasz model być może nie musi być skomplikowany i może działać bardzo prosto:

  • bierzemy maile z kilku lat (albo miesięcy, albo tygodni – w zależności ile tego mamy)
  • dzielimy je na grupy według godziny (i dnia) kiedy przyszły. Granulacja może oczywiście być mniejsza – tutaj zakładam mechanizm wczesnego ostrzegania działający co godzinę. Przedziały trzeba dobrać sensownie (w zależności od liczby przychodzących maili), żeby nie było za dużo pustych oraz żeby wartości (kolejny punkt) były znaczące, a nie same zera czy jedynki
  • dla każdej takiej grupy szukamy na przykład 20 najpopularniejszych słów z treści maili
  • oczywiście wcześniej czyszcząc ją z wszelakich stop words i słów często występujących zawsze w mailach (Witam, Pozdrawiam – to można wychwycić przeglądając najpopularniejsze słowa z całej historii)
  • wynik w postaci mini-tabelek z trzema elementami (data i godzina badanego przedziału, słowo, liczba jego wystąpień) wpisujemy do jakiejś tabeli z danymi historycznymi

Mając tak przygotowane dane historyczne możemy w cyklach (co wspomnianą godzinę) wykonywać kolejne zadania:

  • bierzemy aktualną paczkę (z na przykład minionej godziny) maili
  • liczymy dla niej 20 najpopularniejszych słów (bez stop words i tego co zwykle w nich występuje)
  • dla każdego z tych 20 słów sprawdzamy czy wynik (liczba wystąpień) jest w ramach średniego zakresu czy jest anomalią. Najprościej: czy liczba wystąpień jest większa niż średnia historyczna plus dwa odchylenia standardowe z historii?
  • jeśli tak to mamy do czynienia z anomalią i podejmujemy stosowne działania
  • oczywiście zebrane aktualnie dane dopisujemy do danych historycznych

To najprostszy model, oparty jedynie na średniej i odchyleniu standardowym. I tylko jednej cesze – liczbie wystąpień słowa. Możemy go rozbudować dowolnie, dobrym kierunkiem byłoby uwzględnienie cykliczności czy sezonowości (w nocy przychodzi mniej maili; dla przedsiębiorstwa zapewniającego wodę i ciepło w mieszkaniach w lecie więcej będzie skarg na brak wody niż na zimne kaloryfery). Być może nie powinniśmy też liczyć średniej dla całej historii a tylko dla ostatnich kilku dni lub tygodni? A może posiłkować się średnią ruchomą (i jej predykcją na aktualnie badany okres)? Pomysłów może być wiele.

Tyle o punkcie drugim – wykrywaniu ponadprzeciętnej liczy wystąpień danego słowa. To stosunkowo proste zadanie.

Ale aby liczyć słowa występujące w mailach, musimy dobrać się do skrzynki mailowej. Screen zamieszczony we wspomnianym poście sugerował, że mamy do czynienia ze skrzynką Gmaila. Zatem potrzebujemy dobrać się do treści maili z Gmaila. I tutaj mamy clue naszego wpisu.

Podobnie jak w przypadku pobierania danych z API Google Analytics potrzebujemy projektu na Google Developers w ramach którego włączone jest Gmail API. Tym razem jednak zamiast Client ID i Client Secret pobieramy plik JSON z danymi dostępowymi (zawieraon Client ID i Client Secret). Jeśli nie mamy: tworzymy projekt, włączamy Gmail API i przygotowujemy klucz autoryzacji (Credentials -> Create credentials -> OAuth Client ID). Wszystko to znajdziemy w konsoli dla developerów. Dokumentację Gmail API znajdziecie na stosownej stronie (są przykłady w Javie, .Net, PHP, Pythonie i JavaScripcie), ale oczywiście jest odpowiedni pakiet w R, który zwie się gmailr i dostępny jest na CRANie albo GitHubie.

W pierwszej kolejności autoryzujmy się w Gmailu i pobierzmy jakieś maile.

Zwróćcie uwagę na zmienną gmail_search_query. To jak jest zbudowana w powyższym przykładzie da nam w efekcie wszystkie maile (precyzyjniej: wiadomości zapisane w Gmailu), które powstały nie więcej niż 90 dni temu, nie pochodzą (“from:”) z dwóch adresów mailowych, których używam (czyli interesują nas maile przychodzące) i nie są wiadomościami z Hangouts (Gmail trzyma archiwum rozmów na Hangouts) za co odpowiada fragment “-in:chats”. Filozofia Google to szukanie we wszystkim, także w mailach – przykłady zapytań znajdziecie na stronach pomocy do Gmaila.

W odpowiedzi na powyższy kod dostajemy listę ID maili z naszej skrzynki. To jeszcze nie jest treść. Do przeglądania maili przyda nam się kilka funkcji, które ułatwią pracę z pakietem gmailr, a będą korzystały z bibliotek:

Pierwsza funkcja przekształci nam tekst zakodowany w base64 (tak kodowane są maile zapisane w HTMLu, załączniki, ale też maile z np. polskimi literkami) na normalny tekst:

Treść maila to elementy o nagłówkach MIME “text/html” (maile w HTMLu) lub “text/plain” (maile czystym tekstem). Tylko, że maile mogą mieć załączniki, te elementy są różnie zagnieżdżone i same z nimi problemy. Niby pakiet pozwala na dobranie się do treści, ale u mnie to średnio działało z różnymi mailami (a różne cuda przychodzą mi na skrzynkę). Całość maila ma strukturę drzewka – jest sobie korzeń, w którym już może być treść, ale mogą wychodzić z niego gałęzie. W gałęziach może być treść, a może być załącznik. Albo jeszcze inna gałąź. No cuda na kiju. Trzeba więc przejść drzewo i znaleźć elementy o typie “text/html” albo “text/plain”.

Drzewa najfajniej przechodzi się rekurencyjnie. Jeśli w korzeniu nie ma tego czego szukamy to zaglądamy do gałęzi. Jeśli jest w gałęzi – kończymy zwiedzanie. A jeśli nie – to do kolejnej gałęzi. Albo do podgałęzi. I tak do końca. Samo zaglądanie jest za każdym razem takie samo, więc możemy wywołać ciągle tą samą funkcję (samą siebie), która wygląda tak:

Wiemy jak wydobyć treść, ale możemy chcieć też innych informacji (a w przypadku detekcji anomalii konieczna będzie data maila) – dodajmy więc wyciąganie ich z nagłówków (nadawcai odbiorca, data, tytuł maila) i upakujmy wszystko w jedną funkcję robiącą całą robotę z konkretnym ID maila:

To wszystko. Teraz wystarczy przepuścić wszystkie ID maili (mamy je w tablicy ids z pierwszego kawałka kodu) przez GetMailInfo() i wszystkie interesujące nas dane zebrane. No to jedźmy (na moich mailach):

I co mam teraz z Wami zrobić? Przecież nie pokażę Wam z kim i o czym pisuję sobie mailiki :) Dlatego nie będzie najpopularniejszych słów czy nadawców. Ale mogę pokazać ile dziennie ich dostaję:

albo o jakich godzinach (łącznie):

albo z określonym tekstem:

Za pomocą pakietu tidytext i funkcji unnest_tokens() możemy podzielić treść maili na słowa i sprawdzić jakie są najpopularniejsze. Dzieląc treść maila w ten sposób i zachowując datę maila możemy wyszukać po 20 najpopularniejszych słów danego dnia:

i wybrać jakieś słowo, które często się powtarza. Niech będzie to “linkedin”, bo często dostaję powiadomienia:

Bawić się tak można dalej – nawet robiąc analizy tekstu podobne do opisywanych przeze mnie już wcześniej, chociażby ostatnio przy okazji szukania autorów przemówień prezydenta Dudy. W końcu maile to też tylko tekst.

Jedna bardzo ważna uwaga: w komunikacji mailowej jest tak, że piszemy jakiegoś maila, dostajemy odpowiedź zawierającą napisaną pierwotnie przez nas treść, później na to odpowiadamy (znowu zostawiając całość w treść maila) i tak w nieskończoność. Mam takie maile, które składają się w sumie z ponad 50 maili. Stąd taki pik dla “linkedin” w początku sierpnia – trafił się tam mail, będący zapisem całego takiego ping-ponga z osobą, która w stopce ma link do swojego profilu na LinkedIn (i link ten opisany jest słowem “LinkedIn” właśnie), a że stopka była doklejana za każdym razem…

Dobrze byłoby oddzielić nową treść od poprzednio cytowanej. Czasem rozdzielaczem może być ciąg typu “—— Wiadomość oryginalna ——” albo coś podobnego. Ale czasem zdarza się tak, że odpowiadamy na maila pisząc “moje niżej w treści, na zielono” i piszemy pod rozdzielaczem. Jeśli chcemy badać tylko maile przychodzące z jakiegoś formularza kontaktowego nie powinno być takiego problemu, a w sumie z tej potrzeby wziął się cały niniejszy wpis.

1 komentarz do wpisu “Jak odczytać maile z Gmaila w R?

Dodaj komentarz