Wyciąganie danych z HTML-a za pomocą htmlq

narzędzie htmlq w konsoli

Pisałem wcześniej o yq, czyli programie w stylu awka/seda do wyciągania danych z YAML-a. Do JSON-a jest znany szerzej jq. Przydają się do drobnych automatyzacji na własny użytek, ale też do skryptów uruchamianych na CI. Szukałem ostatnio czegoś takiego do HTML-a i zdziwiłem się, bo większość odpowiedzi sugerowała odpalanie jakichś długaśnych one-linerów ręcznie kasujących tagi HTML. Nie, dziękuję! Na szczęście, 2 lata temu (dopiero??) pojawił się projekt htmlq, reklamujący się bardzo trafnym sloganem:

Like jq, but for HTML. Uses CSS selectors to extract bits of content from HTML files.

Brzmi nieźle, prawda?

W dalszej części pokażę krótko, na ile wygodne jest użycie, co robi się łatwo, a co nie.

Działamy w terminalu, więc pojawią się też inne narzędzia, jak awk, którym usuniemy puste linie psujące czytelność (nieoczywista sztuczka, którą moim zdaniem warto znać). Nasz „query language” to CSS, co z jednej strony ma zalety (każdy używał kiedyś CSS-a), ale też wyjdą pewne problemy (bo to CSS).

Podstawy

Używam Ubuntu, więc nawet nie musiałem szukać, który pakiet ściągnąć i jak go zainstalować. Wpisałem w konsoli htmlq i system sam zasugerował komendę do wpisania:

sudo snap install htmlq

Program zajmuje około 1 MB, czyli na potrzeby lokalnego użycia śmiesznie mało, a na potrzeby użycia na CI – to już oceńcie sami.

Chrzest bojowy: wyciągnijmy zsumowany coverage z raportu HTML od JaCoCo:

% cat build/reports/jacoco/test/html/index.html | htmlq 'tfoot'     
  
<tfoot><tr><td>Total</td><td class="bar">1,588 of 13,439</td><td class="ctr2">88%</td><td class="bar">171 of 637</td><td class="ctr2">73%</td><td class="ctr1">268</td><td class="ctr2">813</td><td class="ctr1">168</td><td class="ctr2">1,805</td><td class="ctr1">134</td><td class="ctr2">493</td><td class="ctr1">8</td><td class="ctr2">84</td></tr></tfoot>

Wiemy już, że narzędzie domyślnie zwraca HTML. Spróbujmy wyciągnąć tekst czytelny dla człowieka, potrzebujemy przełącznika --text:

 | htmlq --text 'tfoot'
Total1,588 of 13,43988%171 of 63773%2688131681,805134493884

Za mało odstępów, za dużo odstępów

Niestety, z czytelnością nie jest za dobrze, wszystko zlało się i nie da się odczytać liczb. Najlepsze, co znalazłem, to przepuszczenie HTML-a przez htmlq dwa razy: najpierw żeby dodać trochę odstępów dzięki --pretty, a potem żeby wyrzucić tagi:

 | htmlq --pretty 'tfoot' | htmlq --text

  
    Total
    
    
      1,588 of 13,439
    
    
      88%
    
    
      171 of 637
    
    
      73%
    
    
(i tak dalej)

Ok, lepiej, ale te puste linie są denerwujące. Co zrobić? Sam htmlq nie pomoże, trzeba zatrudnić inne narzędzie. Server Fault sugeruje wiele rozwiązań, najmniej podobał mi się grep (koloruje), mało sed (zrozumiałe, ale wpisać 4 znaki trzymając shift? nie, dziękuję!). Utrudnieniem jest, że niektóre linie tutaj są puste, a niektóre tylko wyglądają na puste i zawierają spacje. Najłatwiejsza do ręcznego wpisania jest wersja z awkiem, więc:

 | htmlq --pretty 'tfoot' | htmlq --text -w | awk NF
    Total
      1,588 of 13,439
      88%
      171 of 637
      73%

Nie będę udawał, że wpadłbym samodzielnie na użycie awka (nigdy nie miałem czasu nauczyć się wszystkich jego kruczków). Szybkie szukanie: NF to funkcja zwracająca liczbę elementów w linii. Nieważne, rozwiązanie znalezione, idźmy dalej.

Pierwszy pasujący element

Możemy wyciągać kilka elementów na raz:

 | htmlq --text 'tfoot .ctr2'
88%
73%
813
1,805
493
84

Działają nawet bardziej zaawansowane konstrukcje CSS, jak pseudoklasa :last-child:

 | htmlq --text 'tfoot .ctr2:last-child'
84

W HTML-u od JaCoCo nie działa natomiast :first-child. Nie działa też w Chromie (document.querySelectorAll("tfoot .ctr2:first-child") zwraca pusty wynik). Czemu mogę wziąć ostatnie dopasowanie, a nie pierwsze? Gdy popatrzyłem na elaborat na StackOverflow na temat prostego wydawało by się problemu „chcę pierwszy pasujący element”, poczułem wdzięczność, że odpuściłem sobie frontend parę lat temu. To typowy CSS, gdzie proste rzeczy są czasem koszmarnie skomplikowane (centrowanie elementu…).

Polecam więc unikać używania CSS-u do zapytań o kolejność. Lepiej ogarnąć to osobnym poleceniem, na przykład head do pierwszego elementu:

 | htmlq --text 'tfoot .ctr2' | head -n 1
88%
Creative Commons License
Except where otherwise noted, the content by Piotr Kubowicz is licensed under a Creative Commons Attribution 4.0 International License.