JSON - prezentacja danych w przystępnej formie
| Robert | komentarze | InneDawno nic nie pisałem, a że kres majówkowy niewątpliwie sprzyja nieco większym zasobom czasowym wrzucam dzisiaj na tapet nieco luźniejszy wpis z bonusem na końcu (klikbajty! ;) ). Swego czasu oglądałem Kubę Wojewódzkiego (nie oceniaj, skup się), a że filtry i prezentacja danych w Plejerze jest nieco kulawa imo postanowiłem poszperać trochę co można by z tym zrobić by móc np. filtrować odcinki nie po sezonach, a gościach programu. Łatwizna, nie?
Swoją drogą, wtrącę na moment co nieco nt. samego bloga - dopisać do listy todo należy implementację tagów, tutaj gościem będzie dżejson, piejdżpi oraz trochę frontu więc sama kategoria PHP tutaj słabo wygląda, poza tym i tak ten blog idzie na śmieci - silnik, nie zawartość - spokojnie, nie mam czasu na blogowanie bo praca u podstaw i pracuję nad nową wersją której towarzyszy przyswajanie wiedzy o docelowo zastosowanych technologiach i która będzie wykonana tak jak być powinna - profesjonalnie, niezależny front od backendu itd.
Wracając do tematu, nie przykuwaj uwagi do kontekstu w postaci programu, to co zobaczysz można by zastosować w dowolnym innym miejscu. Lecimy z tematem.
Research - znajdujemy punkt zaczepienia
Na początek należy się rozejrzeć za możliwościami. Celem jest pozyskanie danych o odcinkach, im więcej info tym lepiej więc celujemy w korelację odcinek - sezon - gość - data, wystarczy. Pierwszy pomysł? Parsowanie kolejnych podstron i pozyskiwanie info o kolejnych odcinkach. Rozwiązanie mało wydajne, awaryjne bo każda zmiana w DOMie strony ubija projekt i zmusza do hotfixów.
I cyk, interesujący dżejson namierzony, przy okazji dowiadując się, że id serialu to 40135.
Dalej badamy zachowanie aplikacji. Mam na myśli ichniejszą stronę, podgląd w konsoli daje niesamowite informacje gdyż dżejson pięknie jest zasysany dynamicznie, jeden z listą sezonów, drugi na podstawie pierwszego z listą odcinków. Dalej, docelowe śledztwo wskazuje, że lista sezonów znajduje się pod adresem:
<?php
$seasons_list_url = 'https://player.pl/playerapi/product/vod/serial/40135/season/list?4K=true&platform=BROWSER';
Powyższy json zawiera listę sezonów wraz z ich id - to nas interesuje. Przykładowe wpisy.
[{"id":7368706,"externalUid":"455-33","title":"","type":"SEASON","number":33,"display":"33","slug":""},{"id":6596618,"externalUid":"455-32","title":"","type":"SEASON","number":32,"display":"32","slug":""},
Development - PHP
Wspierając się oop napisałem bardzo prostą klasę do pobierania jsonów na podstawie url, metody do cacheowania oraz m.in. metodę od pobierania szczegółów na podstawie listy sezonów:
<?php
public static function getListOfSeason($list) {
foreach($list as $k=>$l) {
$list[$k]['url'] = str_replace(['__ID__'],[$l['id']],'https://player.pl/playerapi/product/vod/serial/40135/season/__ID__/episode/list?4K=true&platform=BROWSER');
$list[$k]['list'] = self::getJson($list[$k]['url']);
}
return $list;
}
Kod więc jest mniej więcej wykonywany w dwóch krokach: pobranie listy epizodów oraz na jej podstawie listy odcinków sprawdzając czy lifetime cacheu się nie skończył oraz pobranie informacji o gościach poszczególnego programu bo json ich nie zawiera:
<?php
require_once('./class/tools.php');
$seasons_list_url = 'https://player.pl/playerapi/product/vod/serial/40135/season/list?4K=true&platform=BROWSER';
$seasons_list = Tools::checkInSession('listOfSeasons',function() use ($seasons_list_url) {return Tools::getJson($seasons_list_url);});
$seasons_list = Tools::checkInSession('listOfSeasonsWithEpisode',function() use ($seasons_list){return Tools::getListOfSeason($seasons_list);});
$persons = Tools::getPersons($seasons_list);
$col = array_column( $persons, "name" );
array_multisort( $col, SORT_STRING, $persons );
Metoda checkInSession ma proste zadanie, sprawdzanie lifetimeu identyfikatora (np. listOfSeasons) oraz wykonanie zdefiniowanego w ramach argumentu callbacku jeśli liftetime minął. To tyle jeśli chodzi o przygotowanie prezentacji. Zajrzymy jeszcze na chwilę do metody getPersons.
public static function getPersons($list) {
$persons = Array();
foreach($list as $s) {
foreach ($s['list'] as $e) {
$title = $e['title'];
if(preg_match("#\s(-\s)?ciąg d(al|la)szy#i",$title))
continue;
$title = preg_replace('#\s(-\s)?(odcinek specjalny|u kuby!)#i','',$title);
$title = str_replace(['”','„',', '],['"','"',','],$title);
//$title = preg_replace('/[^"]? (i|oraz|vs) [^"]?/',',',$title);
$title = preg_replace('/\b(i|oraz|vs)\b(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/',',',$title);
$persons_flat = explode(',',$title);
foreach($persons_flat as $p) {
$md5 = md5(trim($p));
if (!isset($persons[$md5])) {
$persons[$md5] = Array(
'name' => trim($p),
'episodes' => Array()
);
}
$persons[$md5]['episodes'][] = $e;
}
}
}
return $persons;
}
Metoda ta przyjmuje w argumencie listę odcinków oraz następnie wyciąga z tytułów imiona oraz nazwiska lub też nawet pseudonimy i koreluje je z odcinkiem. Zawiera wyjątek dla zawsze najświeższego odcinka którego tytuł zawiera "ciąg dalszy", wycina "odcinek specjalny" i stosuje mniejsze zabiegi kosmetyczne takie jak podmiana apostrofów na prostsze, zamienia spójniki na przecinki itd. Na podstawie tak spreparowanego tytułu otrzymujemy listę osób goszczących w poszczególnych odcinkach.
Development - front
Czas na prezentację danych. By przyśpieszyć pracę z pominięciem wynajdowania dobrze znanego nam wszystkim koła na nowo klasycznie wybrałem bootstrapa wersji piątej oraz świetnie nadające się dataTables.js które ma wsparcie bootstrapowe jeśli chodzi o css. Z lenistwa (sic!) dołączyłem jQuery by już nie komplikować bardziej i tak już napisanego na kolanie mini projektu.
Docelowo sam DOM nie jest może skomplikowany, ale za to jest też nieoptymalny!
<div class="container-fluid">
<div class="row">
<div class="col m-1">
<table id="episodesTable" class="table table-sm">
<thead>
<tr>
<th>Osoba</th>
<th>Odcinki</th>
<th>Sezon, odcinek</th>
<th>Data</th>
<th>Rok</th>
</tr>
</thead>
<tbody>
<?php
foreach($persons as $p) {
?>
<?php
foreach($p['episodes'] as $e) {
?>
<tr>
<td><?php echo $p['name']; ?></td>
<td>
<a href="<?php echo $e['shareUrl'] ?>" target="_blank">
<span class="episodeTitle"><?php echo $e['title'] ?></span> (sezon <?php echo $e['season']['number'] ?>, odcinek <?php echo $e['episode'] ?>)
</a>
</td>
<td><?php echo 'S'.str_pad($e['season']['number'], 2, '0', STR_PAD_LEFT).'E'.str_pad($e['episode'], 2, '0', STR_PAD_LEFT); ?></td>
<td><?php echo date('Y-m-d', strtotime($e['since'])); ?></td>
<td><?php echo (isset($e['year']) ? $e['year'] : date('Y', strtotime($e['since']))); ?></td>
</tr>
<?php
}
?>
<?php
}
?>
</tbody>
</table>
</div>
</div>
</div>
Mimo niskiego imo stopnia skomplikowania większość roboty robi wspomniany wyżej dataTables, załatwia wyszukiwarkę, paginację oraz eksport danych do interesujących formatów jak pdf czy też cvs.
Interfejs nie jest skomplikowany - to dobrze
Podsumowanie
Powyższą tabelę znajdziesz tutaj, nie wymagaj wiele od tego projektu, publikuję go wyłącznie ponieważ spotyka się z aprobatą zainteresowanych więc może i przyda się Tobie. :)
Czego brakuje mi w tym projekcie? Być może kiedyś zaimplementuję prosty system oceniania odcinków, komentowanie może być zbyt dalece nadwyraz i wiązało by się z moderacją stąd proste gwiazdki wraz ze skalą 0-5 wydają się idealne.