Dane z tabel WWW
Jeśli ktoś publikuje jakieś dane w internecie, to robi to zazwyczaj w taki sposób, by można się było z tymi danymi łatwo zapoznać. Jeśli danych jest dużo, to dobrym zwyczajem jest, poza umieszczeniem ich w tabeli html, udostępnienie pliku xls, csv, czy choćby txt. Dzięki temu użytkownik będzie miał możliwość łatwego pobrania danych i ich dalszej analizy. Gdy jednak gotowy plik z danymi nie istnieje, nie wszystko stracone. Są sposoby, by dość łatwo skopiować dane ze strony www bezpośrednio w środowisku MATLAB. Pomagają w tym wyrażenie regularne.
W czym problem
Do umieszczenia tego wpisu zainspirowała mnie strona www.air24.pl na której można znaleźć informacje o stopniu zanieczyszczenia powietrza pyłami w mojej okolicy. Dane są wyświetlane w tabeli na stronie ale żadne pliki do pobrania nie zostały umieszczone. Nie ma też żadnych informacji kontaktowych do administratora strony. Wygląda na to, że jedyny sposób na pozyskanie danych i stworzenie historycznej bazy danych polega na comiesięcznym kopiowaniu tabeli bezpośrednio ze strony www. Można taką operację wykonać "ręcznie", tj. można zaznaczyć dane w tabeli i skopiować je do arkusza excel, jednak istnieje lepsze rozwiązanie. Stronę można załadować do MATLABa i korzystając z wyrażeń regularnych wyłuskać interesujące nas dane.
Gotowiec
Na początku dobra wiadomość dla tych, którzy w temat wyrażeń regularnych nie chcą się wgryzać i potrzebują po prostu narzędzia do kopiowania danych ze stron www. Narzędzie takie istnieje, można je pobrać z MATLAB Central i przy sprzyjających okolicznościach pomoże w większości przypadków. Ja również w pierwszej kolejności chciałem po powyższe narzędzie sięgnąć, okazało się jednak, że w przypadku strony air24.pl gotowiec nie zadziała. Na stronie widać pojedynczą tabele ale po zerknięciu w kod html okazuje się, że każdy wiersz to osobna tabela. W takich przypadkach pozostaje albo przerabianie gotowca albo stworzenie własnej procedury, co paradoksalnie, często okazuje się szybszym rozwiązaniem 🙂
Czytanie stron w MATLABie
Funkcja umożliwiająca skopiowanie całego tekstu html do zmiennej tekstowej to webread
.
adres = 'http://83.12.141.66/env_stalowa_wola/pm2.php'; strona = webread(adres);
Zmienna strona ma ok. 300000 znaków... Poza samymi danymi znajduje się tam mnóstwo bardziej i mniej (częściej mniej) potrzebnych znaczników html. W zasadzie głównym problemem jest rozszyfrowanie struktury dokumentu. Przyglądając się źródłu strony widać, że dane umieszczone są zawsze pomiędzy znacznikami <td>.
<table width="500" border="1" cellspacing="0" cellpadding="0"> <tr bgcolor="#FFFFFF"> <td div align="center" width="200" > 2017-08-01 10:00 </td> <td width="100" bgcolor="#FFFFFF" ><center> 4</td> <td width="100" style=" color: #FFFFFF; background-color:#57b108;" > <center>4</td> <td width="100" style=" color: #FFFFFF; background-color:#57b108;" ><center>8</td> </tr> </table> <table width="500" border="1" cellspacing="0" cellpadding="0"> <tr bgcolor="#FFFFFF"> <td div align="center" width="200" > 2017-08-01 09:00 </td> <td width="100" bgcolor="#FFFFFF" ><center> 4</td> <td width="100" style=" color: #FFFFFF; background-color:#57b108;" > <center>5</td> <td width="100" style=" color: #FFFFFF; background-color:#57b108;" ><center>10</td> </tr> </table>
Wyrażenia regularne
Gotowego wyrażenia regularnego można się w pierwszej chwili przestraszyć. MATLABowy kod wyszukujący wszystkie liczby całkowite znajdujące się pomiędzy znacznikami td wygląda tak:
dane = regexp(strona, '(?<=>\s?)\d+(?=\s?</td>)', 'match');
Wyrażenie można jednak łatwo rozszyfrować, chodzi tu po prostu o to, by znaną regułę przedstawić MATLABowi. Szukamy wyłącznie liczb całkowitych, a te składają się z jednej lub więcej cyfr. W wyrażeniach regularnych pojedyncze cyfry wskazywane są za pośrednictwem wskaźnika \d
Jeśli ma być ich jedna lub więcej, na końcu dopisać należy plus. Stąd \d+
.
Nie interesują nas wszystkie liczby, jakie się pojawiają w kodzie html, a jedynie takie, które znajdują się tuż przed zamykającym znacznikiem td. To prowadzi do wyrażenia:
\d+(?=</td>)
Zdarza się jednak, że w niektórych kolumnach przed znacznikiem td pojawia się jeszcze spacja. W związku z tym potrzebna jest drobna modyfikacja wyrażenia.
\d+(?=\s?</td>)
Po lewej stronie liczby zawsze znajduje się znak zamknięcia znacznika
(?<=>)\d+(?=\s?</td>)
I tutaj również może się zdarzyć, że pojawi się spacja.
expr_dane = '(?<=>\s?)\d+(?=\s?</td>)'
Mamy w końcu gotowe wyrażanie. Funkcja regexpr uruchomiona z parametrem match sprawia, że wszystkie elementy ciągu znaków znajdujące się w zmiennej strona, które spełniają regułę expr_dane, trafią do wynikowej macierzy typu cell. Pozostaje już tylko konwersja do typu numerycznego i odpowiednie ukształtowanie macierzy.
dane = regexp(strona, expr_dane, 'match'); dane = reshape(str2double(dane),3,[]); dane = dane';
I gotowe! Prawie. Z tabeli można też wyciągnąć informacje o czasie pomiaru. Można to zrobić przygotowując kolejne wyrażenie regularne.
expr_daty = '(?<=>\s?)\d+-\d+-\d+ \d+:\d+(?=\s?</td>)'; dataPomiaru = regexp(strona, expr_daty, 'match')'; dataPomiaru = datetime(strrep(dataPomiaru, ' ', ' ')); % należy jeszcze usunąć znacznik br który zaplątał się między datą i godziną
Na tym etapie dysponujemy macierzą danych i wektorem z informacją o czasie rejestracji poszczególnych wyników. Dobrym pomysłem będzie połączenie tych danych w ramach jednej tabeli.
tabela = [table(dataPomiaru), array2table(dane, 'VariableNames', {'PM_1','PM_2_5','PM_10'})];
Teraz już naprawdę prawie gotowe! Wystarczy tylko uruchomiać systematycznie skrypt przez kilkanaście następnych miesięcy i będą jakieś dane do analizy 😉
Aby nie uruchamiać ręcznie skrytpu można skorzystać z ThingSpeak:
https://thingspeak.com/
Uważam, że przydałby się wpis na blogu o tej platformie.
To nawet dobry pomysł, pewnie w przyszłym miesiącu się coś pojawi na ten temat.