czwartek, 18 grudnia 2014

Owoce skryptowania, czyli 0% czasu analityka - cd. Baza danych Olis (cz. 2)

Problem

IC0351460 Pobieranie danych z sieci często nie kończy się po kilku godzinach pracy skryptu. Z reguły bazy potrzebują aktualizacji, ciągłej, regularnej lub doraźnej. Podobnie jest w przypadku notowań Olis. Kolejne wyniki rankingu sprzedaży pojawią się w piątek, w następny piątek kolejne itd. W przypadku gdybyśmy prowadzili systematyczne, częste analizy, nie moglibyśmy pozwolić sobie na cotygodniowe ściąganie danych w całości. Zamiast tego program co jakiś czas odwiedzi jeden/dwa/trzy linki i po sprawie. Jednocześnie chcielibyśmy żeby odbyło się to bez Naszej inicjatywy i aby cały proces działał w tle, bez uruchamiania zasobochłonnego okna przeglądarki, z minimalną kontrolą procesu.
Wyobraźmy sobie teraz sytuację, w której mamy kilkanaście skryptów, które systematycznie muszą wykonywać jakąś robotę. Jak już automatyzować, to do końca, tak abyśmy mogli zajmować się bieżącą pracą, bez konieczności angażowania godziny w tygodniu na uruchamianie crawlerów. Oh, jakże ciężko jest wyrwać się z wiru zadań i deadline'ów tylko po to żeby zrobić update. Zwykle usprawiedliwiamy się, zrobię to jutro, za tydzień zaciągnę z zaległymi itd. W końcu po dwóch miesiącach, uruchamiamy skrypt bo musimy, a tu niespodzianka - zmiany w kodzie źródłowym! $%^@$%&^*

(this line is intentionally left blank)

Zanim jeszcze przejdziemy do rozwiązania warto poruszyć kwestię odpowiedzialnego web-crawlingu. W poprzednim wpisie wspomniałem na temat celowego spowalniania skryptu, szanowania administratora itp. Dzięki sugestii jednego z czytelników prezentuję zestaw poprawnych praktyk, które powinniśmy znać przynajmniej dla ominięcia kłopotów.
Etykieta web-scrapingu
(źródło: Automated Data Collection - pozycja obowiązkowa)
  1. Zanim przystąpimy do zautomatyzowanego pobierania danych, upewnijmy się czy rzeczywiście są to dane, które są nam niezbędne. Po części jest w tym także Nasz własny interes żeby nie tracić czasu na pisanie skryptu i niepotrzebną pracę komputera.
  2. Czy strona internetowa dostarcza API pozwalające na bezpośrednią komunikację z bazą danych?. Jeżeli tak to użyj API.
  3. Czy przypuszczasz, że treść strony, która jest obiektem Twojego zainteresowania jest generowana na podstawie danych zgromadzonych w jakiejś bazie (duża większość przypadków)? Jeżeli tak napisz do administratora, być może dane, których potrzebujesz uzyskasz bez potrzeby crawlingu.
  4. Jeżeli jesteś w tym punkcie, wiadomym jest, że web-scraping będzie konieczny. Zanim rozpoczniesz skakanie po stronach zapoznaj się z tym na jakie działania zezwala administrator strony. W tym celu należy znaleźć na stronie głównej warunki korzystania, terms of service i przede wszystkim zapoznać się z plikiem robots.txt. Jeżeli dane znajdują się w miejscu, w którym zezwala się na działanie robotów, można śmiało przystąpić do działania, zachowując się fair wobec właściciela danych (nie obciążać serwera). 
  5. W przypadku gdy dane znajdują się w miejscu, w którym nie zezwala się na działanie robotów wiedz, że mogą być wobec Ciebie wyciągnięte konsekwencje.

Rozwiązanie

Poprzednim razem poznaliśmy bibliotekę selenium sterującą przeglądarką firefox, a następnie analizowaliśmy kod html danej strony w poszukiwaniu danych. Tym razem lekko zmodyfikujemy poprzedni plan działania:
  • Wchodzimy na stronę archiwum olis i pobieramy listę wszystkich linków do notowań za pomocą utworzonej wcześniej funkcji get_links().
  • Porównujemy linki z bieżącej sesji z linkami które mamy już w bazie.
  • Linki z których nie pobraliśmy jeszcze notowań posłużą do wywoływania funkcji get_olis_ranking().
  • Zapis notowań do bazy
1. Jak za każdym razem definiujemy na początku folder roboczy, w którym zapisywać będziemy pliki. Ważne jest żeby w tym folderze znajdowała się baza olis, którą chcemy zaktualizować (baza danych Olis [6,2MB]). Linijki inicjujące podobnie jak w części 1, uruchamiają przeglądarkę. Za przegądarkę "firefox" wprowadzono zastępstwo w postaci niewidzialnej (headless) przeglądarki "htmlunit". Warto zapoznać się także z pozostałymi przeglądarkami dostępnymi w pakiecie, a w szczególności  "phantomjs".

2. Funkcje napisane w części 1 artykułu pozostają niezmienne. To odkrywa zaletę pisania funkcji, które mogą być łatwo uruchamiane w kolejnych skryptach, które przyjdzie Nam pisać.


3. Uruchamiamy pierwsza funkcję żeby zdobyć aktualną listę adresów do notowań. Filtrujemy adresy notowań zostawiając tylko te, których nie ma w bazie. 

4. Jeżeli po zastosowaniu powyższego filtra nie będzie żadnego rzędu w zmiennej to_update oznaczać to będzie, że baza jest aktualna. W przeciwnym wypadku wykonana zostanie aktualizacja pętlą dla każdego z brakujących adresów. Wyniki notowań zapisuje do nowej tablicy danych (baza_update), która później zostaje dołączona do starej bazy (baza), wieńcząc zapisem do pliku .csv.



W ten sposób utworzyliśmy skrypt, którym w zależność od potrzeb możemy aktualizować bazę olis. Pracując na bieżącej bazie możemy sobie pozwolić na kilka ułatwień wynikających z tego, że baza nie jest duża. 6 MB to nie jest żadne "Big Data", nie musimy wyciągać cięższej artylerii do operacji na takim zbiorze. Jednakże nie raz spotkamy się z sytuacją gdy baza danych przekroczy możliwości Naszych maszyn, wtedy sięgniemy po bardziej relewantne rozwiązania.
Link do kompletnego skryptu

Do pełni szczęścia brakuje Nam tylko sposobu na automatyczne uruchomienie skryptu w każdy piątek, bez konieczności uruchamiania żadnego programu. Wykorzystamy filozofię zaprezentowaną w poprzedniej lekcji. Najpierw tworzymy plik wsadowy, będący zestawem poleceń systemowych uruchamiających skypt R. Otwieramy notatnik, wstawiamy dwie poniższe komendy, zapisujemy plik z rozszerzeniem .bat. Stworzony w ten sposób plik możemy przetestować dwukrotnym kliknięciem - w odpowiedzi powinno uruchomić się okno systemowego wiersza poleceń, a w nim sesja R realizująca skrypt olis_updater.R. Bombowo, możemy tworzyć aplikacje wykorzystujące R!

Aby taka aplikacja uruchamiała się tylko np. co piątek należy skorzystać z programu wbudowanego w Windows Planowanie zadania. W pierwszej kolejności tworzymy nowe zadanie, a w oknie, które się pojawi konfigurujemy parametry planu. Finalnie stworzyliśmy zadanie, które w każdy piątek o godzinie 14 uruchomi nasz program aktualizujący.

Ilustracja1. Instrukcja tworzenia nowego zadania


Po wszystkim klikamy OK i pamiętajmy, że żeby mieć włączony komputer w piątek o 14. W taki oto sposób zamykamy tydzień pracy, pijąc herbatę, patrząc jak komputer robi robotę za Nas, a My myślimy spokojnie o weekendzie.

P.S.
Dla użytkowników Linuxa i OS sugeruję zapoznanie się z littler, który jest szybszą alternatywą wobec Rscript.


13 komentarzy:

  1. Sprawdziłem Twój skrypt - jest kaszana na polskich znakach. R nie może zapisać tablicy, ani wczytać: "Error in type.convert(data[[i]], as.is = as.is[i], in 'utf8towcs'". Najpewniej trzeba zmienić kodowanie, tylko na jakie...

    Pzdr,
    Wilq

    OdpowiedzUsuń
    Odpowiedzi
    1. Panie Wilq, trudno mi określić,w którym miejscu dokładnie pojawiły się krzaki - przy parsowaniu html'a czy przy czytaniu .csv?
      Kodowanie krwi nie raz napsuło mi krwi ale w tym przypadku na mojej maszynie wszystko działa.
      Co ciekawe, wszystkie moje problemy z kodowaniem przy parsowaniu stron skończyły się gdy przeskoczyłem z RStudio na StatET/Eclipse.

      Usuń
    2. Oto pełny błąd:
      > baza<-read.csv("baza_olis_aktualna.csv", sep=";", encoding="UTF-8")
      Error in type.convert(data[[i]], as.is = as.is[i], dec = dec, numerals = numerals, :
      invalid input '"http://olis.onyx.pl/listy/index.asp?idlisty=460&lang=";"SIGUR ROS";"MED SUD Í EYRUM VID SPILUM ENDALAUST";"EMI / EMI MUSIC PL";"";43;"";"07.07.2008";"23.06.2008 - 29.06.2008";"OLIS"' in 'utf8towcs'

      Usuń
    3. Widząc powyższe domniemuję, że uruchomił Pan skrypt z cz.1, gdzie nieświadomie zastawiłem pułapkę w postaci zapisu w domyślnym kodowaniu. Następnie otworzyłeś swój plik za pomocą read.csv(...,encoding="UTF-8") i pojawił się ww błąd ponieważ plik już nie był w UTF'ie. Sugeruję użycie pliku, który umieściłem na github (UTF-8)
      Aby nie było błędu w tym miejscu należałoby zrobić to w taki sposób:
      1. Zapisywać plik .csv z argumentem fileEncoding="UTF-8" (już poprawiłem w skryptach)
      2. Albo zapisać plik z kodowaniem domyślnym i otworzyć również z domyślnym (bez fileEncoding/encoding)

      Usuń
  2. Dla mnie to czarna magią. Moja przygoda z analityka dopiero się rozpoczyna ale już widzę, że to nie będzie takie łatwe jak bym chciała. We Wrocku znalazłam firmę która zajmuje się analityka. Wiem, że prowadzą też jakieś kursy i szkolenia. Zresztą zobaczcie analytics Wrocław

    OdpowiedzUsuń
  3. Bardzo mało tak dobrych treści w polskim internecie odnośnie tej tematyki, mam nadzieję że blog będzie aktualizowany (bo ostatnio coś nie jest)

    OdpowiedzUsuń
  4. Analityka to nie jest łatwa sprawa. Dużo czasu zajęło mi,żeby nauczyć się jej podstaw. Świetny wpis :)

    OdpowiedzUsuń
  5. 48 year old Environmental Tech Murry Siney, hailing from Le Gardeur enjoys watching movies like ...tick... tick... tick... and Snowboarding. Took a trip to Sceilg Mhichíl and drives a De Dion, Bouton et Trépardoux Dos-à-Dos Steam Runabout "La Marquise". naucz sie tego teraz

    OdpowiedzUsuń