rysson / kodi-misc

Kodi misc
0 stars 0 forks source link

Interpretacja selektorów #2

Open rysson opened 5 years ago

rysson commented 5 years ago

Mam problem.

Wzywam na pomoc @xulek, @notoco , @mbebe, @grzebzi, @po50on. I pozostałych, nie krępujcie się zapraszać choć trochę technicznych ludzi.

Wiem, rozpisałem się, ale proszę o zapoznanie się i komentarz. Łudzę się, że to dla dobra społeczności :-P

Niektórzy z Was pamiętają funkcję parseDOM. W tym roboczym repo jest jej udoskonalona wersja dom.search. I jeszcze ciekawsza dom.select.

Pytanie jest o interpretację rozszerzonej funkcjonalności. Zapraszam do zapoznania się z ułomną dokumentacją. Sam próbuję dojść o co mi chodziło w lipcu :-)

Skrót rozszerzeń

Nieco poważniej, wprowadziłem coś co nazwałem „alternatywą” a właśnie przerobiłem w kodzie na „zestaw” (ang. „set”). Przyświecała mi idea ułatwienia parsowania tabelek i zestawów, np.:

<div class="film">
  <h4>Super tytuł</h4>
  <a href="link1..."><img src="..."/></a>
  <p>Opis.</p>
</div>
<div class="film">
  <h4>Tytuł do bani</h4>
  <a href="link2..."><img src="..."/></a>
  <p>Nie ma co pisać.</p>
</div>
<div class="film">
  <a href="link3...">Reklama</a>
  <p>Nie ma co pisać.</p>
</div>

Można to parsować za pomocą:

for title, link, descr in dom.select(page, 'div.film {h4, a, p}'):
    print('title={title.text!r}, link={link.href}, descr={descr.content!r}', **locals())

I dostaniemy:

'Super tytuł' link1... 'Opis.'
'Tytuł do bani' link2... 'Nie ma co pisać.'

Reklama zostanie pominięta bo nie ma <h4> wymaganego w zestawie.

Do tego dochodzą opcje, czyli można podać np. div.film {h4, a, p?}, jeśli opis w <p> może nie istnieć. Wtedy zamiast Node dostaniemy None. Jest to opisane w dokumentacji.

Pytanie

Selektor zestawu (dawniej alternatywy) można przecież przeplatać. Jak ma być interpretowany zestaw?

Przypadek zwykły

Opisany wyżej (typu div.film {h4, a, p}), tu wydaje się, że interpretacja jest rozsądna.

Płaska struktura

Wyciąganie {a,b} może dać ciekawe efekty.

HTML Wynik
<a>A1</a> <b>B1> <a>A2</a> <b>B2</b> [ (A1, B1), (A2, B2) ]
<a>A1</a> <a>A2</a> <b>B1> <b>B2</b> [ (A1, B1), (A2, B2) ]
<a>A1</a> <a>A2</a> <a>A3</a> <b>B1> <b>B2</b> [ (A1, B1), (A2, B2) ]

Pierwszy wynik wydaje się OK. Drugi nieco zadziwia, ale szukamy zestawów A,B, to mamy zestawy. Trzeci może zaskoczyć, nie ma A3. Po prostu nie ma z czym stworzyć zestawu.

Czy taka interpretacja jest do przyjęcia? Jest to obecna implementacja i wynika z założeń opisanych w pierwszym przypadku (skrót rozszerzeń).

Zestaw z elementami

W zestawie może być użyty selektor, czyli możemy żądać np. {a c, b c}. Wyszukuje zestawu A, B (zawierających C) i z nich wybiera elementy C. I tu zaczyna się robić wesoło.

HTML Wynik
<a><c>C1</c></a> <b><c>C2</c></b> <c>Cx</c> [ (C1, C2) ]
<a><c>C1</c></a> <b><c>C2</c><c>C3</c></b> <c>Cx</c> [ (C1, C2) ]
<b><c>C2</c><c>C3</c></b> <c>Cx</c> [ ]
  1. Znalazł A z C1 i B z C2, pobrał oba C, Cx ominął bo nie był ani pod A ani pod B. OK IMHO.
  2. Interesujące, jak wyżej, ale z B pobrał tylko pierwszy element (C2) ignorując C3.
  3. Tu oczywiste, nie ma A, więc zestaw nie istnieje.

W p. 2 mam duże wątpliwości. Czy powinien zwracać C2 czy C2 i C3? Teraz zwraca zestaw, a on opisuje po jednym elemencie.

Jeśli miałoby zwracać wszystkie, to trzeba zmienić upakowanie (dodatkowe zagłębienie), wtedy w 1) musi być [ ( [C1], [C2] ) ], a w 2) [ ( [C1], [C2, C3] ) ]. Przestaje to być czytelne i utrudnia korzystanie z pętli for w stylu podanym na początku.

Nie wiadomo co jest intencją użytkownika tej biblioteki. Może oba warianty mają rację bytu i trzeba dodać jakiś znacznik mówiący czy chcemy wszystkie? Coś na wzór {a c, b c*} (czyli jeden C z A i wszystkie C z B).

Zestaw i dalszy selektor

Zestaw też jest częścią innego selektora. Nie tylko przed (jak z div.film) ale i za nim mogą być kolejne selektory, np. {a, b} c.

Po małej dzisiejszej przeróbce (bo wylatywał wyjątek) elementy C są wyszukiwane po wybraniu zestawu, czyli wyszukuje wszystkie C z wybranego zestawu.

HTML Wynik
<a><c>C1</c></a> <b><c>C2</c></b> <c>Cx</c> [ [C1], [C2] ]
<a><c>C1</c></a> <b><c>C2</c><c>C3</c></b> <c>Cx</c> [ [C1], [C2, C3] ]

Wydaje się, że taka interpretacja może być.

Apropos poprzedniego punktu. O ile {a, b} c jest wariantem {a c, b c} z wszystkimi C, to generalnie nie jest to zamiennik. Nie da się zamienić {a c, b d}.

Podsumowanie

Bardo proszę o opinie i uwagi. Także o podanie przypadków, które są najbardziej przez Was pożądane. Coś, co by Wam ułatwiło życie. Także z zestawu standardowych selektorów, których obsługuję na razie znikomy podzbiór.

Ze swojego podwórka natknąłem się na problem tych samych znaczników z zestawie. Po prostu zawsze są zwracane pierwsze znalezione. Nawet jak żądamy ich wielokrotnie. Jest to zgodne z założeniem, ale upierdliwe. Np.

<div id="info">
  <p>Tytuł</p>
  <p>Gatunek</p>
  <p>Rok produkcji<p>
</div>

Żądam div#info {p, p, p} i dostaję... [ 'Tytuł', 'Tytuł', 'Tytuł' ]. Może i poprawne tylko zupełnie nieprzydatne. Jak to opisać?

Pytania mnożą się jak króliki.

BTW. Ani tag + tag ani :nth-child nie są jeszcze zaimplementowane.

Pomocy!

EDIT: Drobiazgi w tym link do dokumentacji ~z roboczej gałęzi „altfix”~ w gałęzi master.

rysson commented 5 years ago

Przemyślałem, div#info {p, p, p} widzi mi się jako kolejne elementy w kolejności. Ale tyle w ramach <p>.

Czyli {d, d, p, p} dla P1 P2 D1 P3 D2 da [ D1, D2, P1, P2 ].

Stare zachowanie (tylko pierwszy) zostawiłem jako {{a, b}}. Chyba w ogóle je wywalę.

xulek commented 5 years ago

Bardzo chętnie bym pomógł ale chyba nie mam startu do ciebie. Moja wiedza jest zbyt uboga :P Poza tym ciężko to przedyskutować pisząc. To trzeba usiąść na spokojnie.. :D

notoco commented 5 years ago

A ja nawet nie wiem o czym ty do mnie piszesz....

rysson commented 5 years ago

Znaczy, że źle o tym napisałem.

Na razie ciekawostka, parseDOM() jest na wiki: https://kodi.wiki/view/Add-on:Parsedom_for_xbmc_plugins

Zaraz opiszę to lepiej, mam nadzieję. Może to i lepiej, że muszę podać to przystępniej. Cel: ułatwienie pracy developerom.

Pytanie pomocnicze, wiecie co to CSS i czy umiecie używać selektorów?

notoco commented 5 years ago

Coś tam o css wiem ale dawno się w to nie bawiłem...

rysson commented 5 years ago

Przykład o ile selektor może uprościć obsługę stron. Przykład z plugin.video.anime.

def Alfabetycznie():
    url = 'http://animezone.pl'

    r = client.request('http://animezone.pl/anime/lista')

    result = client.parseDOM(r, 'div', attrs={'class': 'btn-group btn-group-xs'})
    linki_litery = client.parseDOM(result, 'a', ret='href')
    litery = client.parseDOM(result, 'a')
    counter = 0
    for link in linki_litery:
        link = url + link
        addon.addDir(str(litery[counter]), link, mode=3)
        counter += 1

Można zamienić na (tak wiem, oczyściłem też kod):

def Alfabetycznie():
    url = 'http://animezone.pl'
    r = client.request(urljoin(url, '/anime/lista'))
    for a in pdom.select(r, 'div.anime-list div a'):
        addon.addDir(a.text, urljoin(url, a.href), mode=3)

Ten selektor można sobie przetestować na sucho wołając bibliotekę (działa z python2 jak i z python3):

git clone https://github.com/rysson/kodi-misc.git
git checkout altfix    # to tylko na czas naprawy selektorów
cd kodi-misc/ParseDOM/rysson
python3 -m pdom http://animezone.pl/anime/lista 'div.anime-list div a'
rysson commented 5 years ago

Dodałem opis dla początkujących o co mniej więcej chodzi w selektorach, patrz https://github.com/rysson/kodi-misc/blob/master/ParseDOM/rysson/doc/pl/podstawy.md.

Chętnie przedyskutuję co powinienem opisać. @xulek ma rację, lepiej byłoby to omówić. Ale pewnie trudno będzie się na piwo spotkać, co?

rysson commented 5 years ago

Drugi przykład z plugin.video.anime.

def ListowanieAnime():
    url = params['url']

    r = client.request(url)

    result2 = client.parseDOM(r, 'div', attrs={'class': 'panel-body categories-newest'})  ## na obrazy
    obrazy = client.parseDOM(result2, 'img', ret='src')
    result = client.parseDOM(r, 'div', attrs={'class': 'description pull-right'})  ## na linki i opisy
    linki = client.parseDOM(result, 'a', ret='href')
    nazwy = client.parseDOM(result, 'a')
    opisy = client.parseDOM(result, 'p')

    counter = 0
    for link in linki:
        linki[counter] = 'http://animezone.pl' + linki[counter]
        obrazy[counter] = 'http://animezone.pl' + obrazy[counter]
        addon.addDir(str(nazwy[counter]).replace("<mark>", "").replace("</mark>", ""), linki[counter], mode=4,
                     thumb=obrazy[counter], plot=opisy[counter])
        counter += 1
    try:
        strony = client.parseDOM(r, 'ul', attrs={'class': 'pagination'})
        strony = client.parseDOM(strony, 'li')
        link_nastepna = client.parseDOM(strony, 'a', ret='href')[-1]
        # nastepna strona
        for strona in strony:
            strona = client.parseDOM(strona, 'a')
            if len(strona) > 0:
                strona = str(strona[0])
                if strona == "&raquo;":
                    addon.addDir("Nastpna strona..", 'http://animezone.pl' + link_nastepna, mode=3)
    except:
        log_exception()
        pass

Nowa wersja.

def ListowanieAnime():
    url = params['url']    # pomijając, że taki dostęp do atrybutów dodatku jest nie bardzo
    r = client.request(url)
    for a, img, desc in pdom.select(r, 'div.well.categories {{.label a, img, p}}'):
        addon.addDir(a.text, urljoin(url, a.href), mode=4, 
                     thumb=urljoin(url, img.src), plot=desc.content)
    # następna strona
    pages = pdom.select(r, '.pagination li a')
    if pages and pages[-1].content == '&raquo;':
        addon.addDir("Następna strona...", urljoin(url, pages[-1].href), mode=3)

Tutaj uwaga, użyłem {{ }}, który chcę wywalić, bo w { } właśnie znalazłem błąd. A chciałbym abyście mogli to przetestować na żywo. EDIT: Naprawiłem, { } już działa poprawnie.

Na przykład (już bez klonowania i dla Python2):

python -m pdom http://animezone.pl/anime/lista/A 'div.well.categories {{.label a, img, p}}'

Nie wydaje się Wam, że nowa wersja jest nie tylko bardziej zwięzła ale i bardziej czytelna?

rysson commented 5 years ago

Dodałem na dobranoc możliwość wyboru, który element z zestawu nas interesuje (działa tylko w ramach zestawu { }). Takie coś na wzór :nth-type-of dla prostych liczb.

Dajcie znać jak możemy przedyskutować te rozwiązania.

Powtórzenia numerowane

Można wybrać, które powtórzenia mają się znaleźć w wyniku.

Z przykładu wyżej selektor {p:2} zwróci tylko opisy filmów.

Jeśli brak numeru to użyty jest zawsze o jeden większy niż poprzednio dla takiego samego selektora, czyli:

<div>
  <p>Tytuł</p>
  <p>Rok</p>
  <a href="/gatunki">Gatunek</a>
  <p>Opis</p>
  <a href="/patrz">Oglądaj</a>
</div>

Z div {p, p, a:2} daje Tytuł (p1), Rok (p2), Oglądaj (a2).

Z div {p:3, p:1, p, a:2} daje Opis (p3), Tytuł (p1), Rok (p2), Oglądaj (a2).

Z div {p:2, a, p, a, p:1} daje Rok (p2), Gatunek (a1), Opis (p3), Oglądaj (a2), Tytuł (p1).

xulek commented 5 years ago

Powiem Ci, że nie wiem jak to wykombinowałeś ale wygląda super :) Drastycznie zmniejsza ilość kodu

rysson commented 5 years ago

O dzięki, choć tu nie chodzi jak to zrobione, tyko jak działa.

Jak już zainteresowałem przykładem, to pytam co potrzeba, abyście poznali głębiej ideę, a właściwie bieglej się posługiwali tym wynalazkiem. Bardzo bym chciał to poprowadzić tak, aby Wam było jak najłatwiej. Mam nadzieję, że to udało mi się pokazać w przykładzie.

Co proponujecie? Ja preferuję słowo pisane, ale może przygotuję jakiś krótki filmik, wszakże zajmujemy się Kodi :-) Pokazałbym na przykładzie serwisu jak wygenerować odpowiedni kod.

Zdaję sobie sprawę, że to trochę na sucho, bo ostatnie zmiany są tutaj. Ale podstawa jest już w PTW. Do działania potrzebny jest moduł arpeggio i tu dochodzimy do ciekawej zależności. Żeby łatwo dodawać moduły (a jestem przeciwnikiem ich wrzucania bezpośrednio do kodu, przecież mamy zależności), muszę napisać kodi-repo-builder.

Projekt kodi-repo-builder rozpoczęty, ma wstępną i chaotyczną dokumentację, nawet pierwsze linijki zacząłem pisać. Umożliwi to trywialne paczkowanie dowolnego kodu, w tym zewnętrznych bibliotek. I proste składanie tego w repo. Może omawiane tutaj pdom (dawne ParseDOM) też dam jako zewnętrzną zależność, łatwiej będzie zarządzać kodem. Jeszcze zobaczymy.

Tymczasem dodałem obsługę selektora E > F (bezpośredniego dziecka), czyli w przypadku:

<div class="film">
  <a href="http://film">Tytuł</a>
  <div class="opis">
    <a href="http://akcja">Akcja</a>
  </div>
<div>

Selektor potomka div.film a trafi w oba linki, a selektor dziecka div.film > a tylko w jeden (ten od tytułu) bo tylko on jest bezpośrednim dzieckiem.

Na razie wystarczy. Poza dokumentacją, tutorialami itp. wstrzymam się z poprawkami do czasu, aż zgłosicie czego Wam jeszcze trzeba. No chyba, że sam natrafię w praktyce na jakąś ułomność.

Kod w repo, wszystko w masterze.