JSON - prezentacja danych w przystępnej formie

|
Robert
| komentarze | Inne

Dawno 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.