wtorek, 9 grudnia 2014

Pozyskiwanie i organizacja danych - 101% czasu pracy analityka. Baza danych Olis (cz. 1)

Wstęp 

Jako, że jest to pierwszy wpis publiczny w tej części przestrzeni wirtualnej, chciałbym przywitać wszystkich, którzy w niewyjaśnionych okolicznościach znaleźli się na moim blogu. Zgodnie z opisem, blog jest wynikiem mojej przynależności do społeczności eRowej, której chciałbym coś dodać od siebie. Blog w zamyśle ma pełnić z jednej strony funkcję surowego nauczyciela, a z drugiej zaskakiwać ciekawymi rozwiązaniami tych bardziej doświadczonych. Dodawane treści pomimo swojego programistycznego charakteru, dotykać będą sfery danych statystycznych od pozyskiwania (dzisiaj), przez organizację, analizę do wizualizacji. 
Zdaję sobie sprawę, że dzisiejszy wpis może być dla niektórych "magią" - nie przejmujcie się, umyślnie podkręciłem śrubę. Sporo operacji wykonywanych dzisiaj będą powtarzane do znudzenia w przyszłości. To czego dzisiaj nie wiemy, jutro będziemy przekazywać innym. Powodzenia! 


Problem 

Współczesny analityk danych to coś więcej niż naukowiec zgłębiający tajemnice metod numerycznych. Współczesny analityk to inżynier zarządzający całym procesem analitycznym od pozyskiwania danych, poprzez organizację, stosowanie metod statystycznych do wniosków z danych płynących. Z połączenia cech menadżera, informatyka i statystyka powstaje nie Kapitan Planeta ale Mistrz danych, profesja pierwszoligowa potrzebna praktycznie wszędzie. Umiejętności programistyczne wykorzystuje się na każdym etapie pracy z danymi i pozwala zaoszczędzić setki godzin spędzonych na czyszczeniu, wklepywaniu danych, szukaniu modeli. Programowanie w procesie analitycznym ma jedną bardzo, ale to bardzo ważną zaletę - jest dokumentem, zapisem Naszych prac. Gdy coś nie gra, łatwo możemy wrócić do etapu, w którym popełniliśmy błąd i praktycznie bez straty czasu wykonać poprawiony proces. 


New-York Times opublikował jakiś czas temu artykuł poruszający kwestię ważności porządkowania danych. W większości przypadków zdarza się, że dane które otrzymujemy nie są zdatne do analizy. Zwykle trzeba je odpowiednio obrobić zanim zastosujemy odpowiednią metodę statystyczną. Co ciekawe, autor posługując się danymi z różnych opinii i wywiadów podaje, że przygotowywanie danych zajmuje od 50% do 80% czasu pracy ze zbiorami. Nie wnikając w dokładność tych proporcji, można z całą pewnością stwierdzić, że umiejętności nadawania danym właściwego kształtu są niezbędne w pracy analityka danych. 

Jednak żeby dane organizować, trzeba najpierw te dane zdobyć, co też zwykle zajmuje więcej czasu od samych analiz. W końcu czas przeznaczony na eksploracje danych tracimy na żmudne operacje, których moglibyśmy uniknąć w dobrze zaprojektowanych procesach. 
Dzisiaj przetestujemy R jako narzędzie pozyskiwania danych na żywym organiźmie jakim jest muzyczna lista Olis. Wikipedia pisze: "Jest to lista najlepiej sprzedających się nośników muzycznych na polskim rynku wśród wydawców zrzeszonych w ZPAV, obejmująca 50 pozycji. Zestawienie to kompilowane jest na podstawie danych pochodzących z 233 punktów sprzedaży: 227 sklepów największych sieci handlowych w Polsce: EMPiK (121 sklepów), Real (53 sklepy), Media Markt (38 sklepów) i Saturn (15 sklepów), 2 największych polskich sklepów internetowych Merlin.pl i Rockserwis.pl oraz 4 sklepów detalicznych. Informacje gromadzi i kompiluje Instytut Pentor.". 
Aktualny ranking Olis publikowany jest co tydzień na stronie internetowej http://olis.onyx.pl ale dostępne są również rankingi archiwalne od 13 października 2000 roku. Gdybyśmy chcieli ręcznie zebrać dane olis do analizy, musielibyśmy otworzyć 706 podstron i spisać np. do excella każdą z 50 pozycji w rankingu - co daje w sumie 35300 rzędów w bazie. Zamiast tego, lepiej w godzinę napisać program, który zbierze wszystkie dostępne dane, nada im odowiedni kształt i w dodatku będzie dokonywać automatycznych aktualizacji. 
W tym celu posłużymy się biblioteką selenium, dzięki której łatwej zobrazować dzisiejszą pracę. Ponadto selenium ma dużo więcej zalet przydatnych przy zdobywaniu danych, które wyłonię przy innej nadarzającej się okazji (np. uruchamianie JavaScript). Dla ciekawskich link do anglojęzycznej instrukcji dotyczącej pakietu RSelenium.

Rozwiązanie 

1. Rselenium pozwala sterować przeglądarką (np. Firefox) z poziomu R. Poniższy kod uruchamia Firefoxa i przechodzi na adres podany w funkcji navigate(). Niezależnie od rodzaju strony i danych jakie chcemy pobierać, zwykle te linijki się nie zmieniają. 
2. Przy tzw. web-crawlingu musimy zaplanować dokładnie jak będziemy dane zdobywać. Dlatego najpierw musimy zapoznać się ze źródłem strony archiwum. W tym celu klikamy prawym przyciskiem na odnośnik do jednego z notowań i badamy element za pomocą domyślnej wtyczki ("zbadaj element") lub za pomocą wtyczki firebug ("zbadaj element za pomocą firebuga").

 
W dodatkowym oknie wyświetli nam się fragment kodu html z wyróżnionym badanym elementem. Odnośniki do stron z poszczególnymi notowaniami mają takie same położenie (tzw. xpath) w kodzie html (xpath=//tr/td/a) i sa wartością atrybutu href.  
Zebrane informacje pozwalają na ułożenie planu działania: 
  • Zdobywamy listę linków do wszystkich notowań ze strony archiwum (zmienna linki) 
  • Wchodzimy po kolei na każdy adres ze zmiennej linki i analizując kod html (parsowanie) z podstron wyodrębniamy ranking z danego tygodnia, a następnie zapisujemy w bazie. 
Żeby tego dokonać w prosty i przejrzysty sposób utworzymy dwie funkcje:
  • get_links() zwracającą listę linków z datami notowań 
  • get_olis_ranking() zwracającą albumy wykonawców wraz z miejscem na liście w danym tygodniu, uruchamiana co iterację wyrzucająca poszczególne rankingi. 

 4. Na tym etapie odpalamy pierwszą funkcję za pomocą której otrzymujemy listę notowań w formie data.frame

5. Funkcję get_olis_ranking("adres_strony") uruchamiamy w pętli dla każdego linka - możemy zauważyć, że przeglądarka skacze teraz po zadanych adresach, a w konsoli R pojawia się komunikat "pobrano notowania z adresu -- link_strony". Dobrą praktyką jest kontrola procesu poprzez obserwowanie numeru iteracji (by móc wznowić w tym samym momencie w razie przerwania), obserwowanie nowych danych zapisywanych do bazy (czy skrypt jest aktualny dla wszystkich podstron lub czy administrator nie serwuje nam artefaktów).

Bardzo ważną linijką jest Sys.sleep(4) - wstrzymuje wykonywanie programu na 4 sekundy. Główne powody świadomego spowolnienia poboru danych to:
  •  Z szacunku do zarządzających stroną - nie generować szanownym administratorom niepotrzebnego ruchu. 
  • Z przezorności -  jeżeli jest strona, którą będziemy jeszcze długo odwiedzać lepiej obniżyć prędkość i pozostać nie zauważonym niż mieć później ograniczony dostęp  lub gorzej...


5. Na koniec dołączamy do bazy kolumny z informacją dotyczącą notowań (data i za jaki okres) - łączenie po zmiennej "adres" która występuje w obiekcie baza i w obiekcie notowania. I zapisujemy bazę jako plik tekstowy .csv




W ten oto sposób zdobyliśmy dane, które po krótkim przeczyszczeniu będzie można analizować. W tej części to wszystko. W kolejnym etapie napiszemy skrypt aktualizujący dane o nowe notowania, doczyścimy zbiór i wygenerujemy pierwsze statystyki.
Link do pełnego skryptu
link do zbioru danych w formie .csv 

Opis użytych funkcji

setwd("C:/adres/folderu/robocego") - adres katalogu w którym będziemy pracować. Najpierw należy go utworzyć.

remDr$getPageSource()[[1]] - czyta kod html bieżącej strony.

htmlParse() - przeprowadza analizę składniową kodu html.

readHTMLTable - z zapisanego kodu źródłowego wyodrębniamy wszystkie tablice znajdujące się na danej. stronie. Tablica z datami notowań to [[3]] tablica.

xpathApply(zrodlo,"//a",xmlGetAttr,"href") - pobiera link do notowania. Wchodzi do odsyłacza "a" i pobiera wartość atrybutu "href"

grep("idlisty=[0-9]+",linki,value=T) - zwraca te adresy które zawierają tekst "idlisty=" oraz kilka cyfr 0-9.

mutate(notowania, adres=linki) - do tablicy notowanie dodajemy nową kolumnę adresy do której przypisujemy zebrane linki

rbind_list(tablica, baza) - łączy dwa elementy, dodając rzędami.

write.table(baza,"baza_olis_aktualna.csv", sep=";", row.names=F, na="") - zapisujemy obiekt baza jako "baza_olis_aktualna.csv"

5 komentarzy:

  1. Gratuluje Bloga i artykułu! Mała podpowiedź, by nie używać skrótów myślowych, np. przy fragmencie wywoływania funkcji, mimo że R'a znam kilka lat, to musiałem się pogłowić, jak co do czego poprzypinać (funkcja get_links nie zwróciła mi obiektu notowania).

    Co do kodu - przy petli (co w R jest rzadkością) warto by dodać progressbar taki jak tutaj:
    http://ryouready.wordpress.com/2009/03/16/r-monitor-function-progress-with-a-progress-bar/
    .Niby jedna linijka a dużo daje.
    Dwa, można by wykorzystać w jakiś sposób najnowszy dostępny link i posiadany plik, tak by za każdym razem nie sprawdzać jakie parametry ma przyjmować i.

    Pzdr,
    Wilq

    OdpowiedzUsuń
  2. PS. Nie zauważyłem linku do pełnego skryptu. Teraz wszystko jasne.

    Pzdr,
    Wilq

    OdpowiedzUsuń
  3. Świetnie to wszystko przedstawiłeś, aż chciało się czytać !:)
    Oby tak dalej, powodzenia!

    OdpowiedzUsuń
  4. Przedstawione jest to naprawdę świetnie. Z przyjemnością czytałam. Sama ostatnio zainteresowałam się dokształceniem i szkoleniami. Zrobiłam szkolenie ISTQB, które niezwykle poszerzyło moją wiedzę w zakresie testowania.

    OdpowiedzUsuń
  5. Ciekawy wpis. Obecnie takie zadania jak organizacja danych może być ograniczona do minimum przy zastosowaniu odpowiedniego oprogramowania - click Sap Hana pozwala nie tylko na gromadzenie dużych ilości danych, ale także skuteczne ich przetwarzanie na potrzeby firmy.

    OdpowiedzUsuń