lxml xpath problém procházení strukturovaného menu [python]

Menu na stránce webnode.cz má několik úrovní ul. Na každé úrovni ul je třída class level-1 až např. level-3. Je tam seznam položek li a ty které obsahují vnořené menu mají navíc třídu wnd-with-submenu. V pythonu mám rekurzivní funkci, která prochází ty menu resp. ul. Přesněji řečeno první úroveň jsem udělal jako hlavní smyčku tj. ne rekurzivně. Tím projdu první úroveň. Když narazím na podmenu spouštím rekurzivní funkci. Nyní zde je problém s tím xpath. Zdá se to být nevyřešitelné.

Celý html kod sem dávat nechci, je to dlouhé a nepřehledné, zkusím objasnit pseudo kodém. Místo tagu a se spany a názvem článku použiju link A, link B, link c atd... A připomínám, že nyní řeším tu 2. úroveň.
Kód: [Vybrat]
ul level 2
li link 2A
/li
li link 2B
/li
li link 2C (obsahuje menu)
   ul level 3
   li link 3A /li
   li link 3B /li
   li link 3C /li
   /ul
/li
/ul level 2

Takže já spouštím tu funkci a vidím obsah toho ul level 2. Začnu procházet ty jednotlivé položky menu li ale rozlišuje se jestli je to li bez třídy nebo to li kromě linku obsahuje taky vnořený seznam. Tento vnořený seznam nesmí být ve výsledku.

Zkoušel jsem několik možností a ani jedna nefungovala správně:

Kód: [Vybrat]
# li_obj = ul_obj[0].xpath('.//li[@class="wnd-with-submenu"]')Toto vypíše jen články s vnořeným menu.

Kód: [Vybrat]
li_obj = ul_obj[0].xpath('.//li[@class="wnd-with-submenu" or not(@class)]')
Toto myslím vypisuje i ty li které jsou zanořené v tom vnořeném ul. Nefungovalo podle potřeby.

Kód: [Vybrat]
li_obj = ul_obj[0].xpath('.//li[not(.//ul[@class="level-3"]//ancestor::li[@class="wnd-with-submenu"])]//a')
[code]
Toto mělo vypsat všechny li v seznamu ul (2. level), bez toho zanořeného menu. Toto však nefunguje. Místo toho se stane to, že to vypíše články bez vnořeného menu, první vnořený článek nevypíše, a ostatní vnořené články vypíše. To je chyba. [b]Vnořené články se nemají vypisovat vůbec[/b] (na to budu volat funkci).

Domnívám se, že výraz [b]not(.//ul) je interpretován tak, že zcela vyloučí ty položky, který obsahuje li 2 level, místo aby poskytlo pouze ten odkaz z této položky li 2 level[/b].

Zkrácený html kód...

[code]
<li class="wnd-with-submenu">
<a class="menu-item">LINK LEVEL 1</a>
<ul class="level-2">
<li>
<a>link level 2 A</a>
</li>
<li>
<a>link level 2 B</a>
</li>
<li class="wnd-with-submenu">
<a>LEVEL 2 C with SUBMENU</a>
<ul class="level-3">
<li>
<a>link level 3 D DON'T!!</a>
</li>
</ul>
</li>

Pak to pokračuje další položkou level 2 a další submenu ...

Ještě stručněji:
li class="wnd-with-submenu"
   LINK LEVEL 1
   ul class="level-2"
      li
         link level 2 A
      /li
      li
         link level 2 B
      li
      li class="wnd-with-submenu"
         link LEVEL 2 C with SUBMENU
         ul class="level-3"
      li
         link level 3 D NEVYPISOVAT!!
      /li
   /ul
/li


Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #1 kdy: 25. 07. 2023, 19:38:28 »
Co tedy má být výstupem? Seznam všech listů menu? A jenom jejich názvy/odkazy, nebo i cesta k nim v menu? Pokud vám stačí jenom ty elementy s odkazy, pak to řeší XPath výraz ul//a a nepotřebujete žádnou rekurzivní funkci.

Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #2 kdy: 25. 07. 2023, 19:57:31 »
Co tedy má být výstupem? Seznam všech listů menu? A jenom jejich názvy/odkazy, nebo i cesta k nim v menu? Pokud vám stačí jenom ty elementy s odkazy, pak to řeší XPath výraz ul//a a nepotřebujete žádnou rekurzivní funkci.

Ukážu vám kousek z kódu jak jsem se to snažil řešit.
Kód: [Vybrat]
def extract_data(parent_depth, section, url_dirname, ul_obj, in_submenu=False):
    submenu_level = 1
    li_obj = []
    if ul_obj:
        # NE VŠECHNY li MAJÍ class !!!
        class_name = ul_obj[0].get("class")
        submenu_level =  int(class_name.split("level-")[1])
        # Create Alias for current article Title
        title = section
        # PROBLEMATICKÝ ŘÁDEK:
        li_obj = ul_obj[0].xpath('.//li[not(.//ul[@class="level-3"]//ancestor::li[@class="wnd-with-submenu"])]//a')
        for li in li_obj:
           ... toto byla jen zahřívačka kde jsem se díval co jsem vlastně obdržel
    if li_obj:
        for li in li_obj:
            has_submenu = "wnd-with-submenu" in li.get("class", "")
            link = li.xpath('.//a[@class="menu-item"]')[0]
            if link is None:
                print(f"li: {etree.tostring(li, pretty_print=True).decode()}")
                print("link error: exit()")
                exit()
            # url a název článku, plus hloubka vnoření, jsou hlavní informace, které potřebuju připravit k zápisu
            url = link.get("href").strip("/")
            title = link.xpath('.//span[@class="menu-item-
            #submenu_ul potřebuju předat do funkce pro rekurzivní volání, na další zpracování. Takže nějak se k němu musím dostat.
            if has_submenu:
                submenu_ul = li.xpath('.//ul[contains(@class, "level-")]')
                if debug_level > 5:
                    print(f"link: {etree.tostring(li, pretty_print=True).decode()}")
            else:
                submenu_ul = []

Pak už jen nějaký chaotický kod s komentářema (je to rozdělané a v této fázi je podstatný ten kod výše...

Kód: [Vybrat]
            in_submenu = len(submenu_ul) > 0
            if in_submenu:
                print("In submenu...")
                if not has_submenu:
                    print("ERR: Weird thing happened")
                    exit()
               # VE SMYČCE SE NEMÁ SUBMENU LEVEL ZJIŠŤOVAT
               # JE UŽ ZJIŠTĚN MIMO SMYČKU!
               # submenu_level = int(submenu_ul[0].get("class").split("level-")[1])
                print(f"\"{title}\", \"{url}\", {submenu_level}")
                if debug_level>2:
                    if has_submenu:
                        print ("DEBUG in_submenu:")
                        extract_data(parent_depth + 1, "IN wnd-with-submenu:"+section, url_dirname, submenu_ul, in_submenu=True)
                    else:
                        print ("(DEBUG in_submenu. None submenu found)")

                # print(f"submenu_ul[0]: {etree.tostring(submenu_ul[0], pretty_print=True).decode()}")
                # print(f"Aktuální submenu_ul[0] třída má level {submenu_level}")
                # if parent_depth != submenu_level:
                #    print(f"Kontrola: Rodičovská parent_depth {parent_depth} se nerovná získané ul submenu_level {submenu_level}.")
                 #   extract_data(submenu_level, section, url_dirname, submenu_ul, in_submenu=True)
            else:
                print(f"\"{title}\", \"{url}\", {submenu_level}")

Celé to spouštím takto:
Načti soubor
Kód: [Vybrat]
with open("temp.html", "r") as f:
    html_content = f.read()

parser = etree.HTMLParser()
tree = etree.fromstring(html_content, parser)

menu_div = tree.xpath('//div[@id="menu-slider"]')[0]
menu_nav = menu_div.xpath('.//nav[@id="menu"]')[0]

# Vyber všechny prvky <li> na první úrovni menu
items = menu_nav.xpath('.//ul[@class="level-1"]//li')

# Vybere všechny prvky <li> na DRUHÉ úrovni menu, protože
# !!! třída wnd- je na druhé úrovni !!!
# items = menu_nav.xpath('.//ul[@class="level-1"]//li[contains(@class, "wnd-")]')

Hlavní smyčka pro načtení první úrovně.
Kód: [Vybrat]
parent_depth = 1
items_count = 0
# Získej adresu na objekt link což ve skutečnosti je list item <li>...</li>
for item in items:
    if item.get("class"):
        in_submenu = "wnd-with-submenu" in item.get("class")
    else:
        in_submenu = False
        if debug_level >= 2:
            print("class not present")

    # Získej první odkaz <a>, kde je atribut href
    tag_a = item.xpath('.//a[contains(@class, "menu-item")]')[0]
    url_dirname = tag_a.get("href").strip("/")
    section = tag_a.xpath('.//span[@class="menu-item-text"]/text()')[0]
   
    # Získej objekt <ul> s třídou začínající na "level-2" (ZDE JE ASI STEJNÝ PROBLÉM JAKO VÝŠE)
    ul_obj = item.xpath('.//ul[contains(@class, "level-2")]')
    if ul_obj:
        extract_data(parent_depth, section, url_dirname, ul_obj,

« Poslední změna: 25. 07. 2023, 20:00:38 od exkalibr »

Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #3 kdy: 25. 07. 2023, 20:07:23 »
Ukážu vám kousek z kódu jak jsem se to snažil řešit.
Ale já jsem se neptal jak se to snažíte řešit. Já jsem se ptal, co se snažíte řešit – co má být výstupem toho vašeho kódu.

Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #4 kdy: 25. 07. 2023, 21:41:01 »
Název článku, odkaz a hloubka zanoření v menu.
To je výstup všech položek v menu.
Někdo mi poradil na stackoverflow zdá se že tohle řešení by mělo fungovat:
Kód: [Vybrat]
li_obj = ul_obj[0].xpath('//li[parent::ul[@class="level-2"] and not(parent::ul[@class="level-3"])]/a')
Zítra bych to odladil a uvidím jestli to fakt je ono. Já to budu řešit tak, že číslo levelu si dosadím.


Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #5 kdy: 25. 07. 2023, 22:17:13 »
XPath výraz //li[not(@class="wnd-with-submenu")]/a vám vytáhne všechny elementy a, které jsou přímo uvnitř li, které nemá třídu wnd-with-submenu. Tj. tohle nahrazuje rekurzi, to vám vrátí odkazy rovnou ze všech úrovní. Z a získáte název a odkaz. Pak od každého nalezeného a použijete .//ancestor::ul[1], čímž se vrátíte k nejbližšímu nadřazenému ul a z něj získáte class, kde bude třeba to level-3 (což je předpokládám ta hloubka, kterou chcete).

Takže v tom Pythonu to bude vypadat zhruba takhle nějak:

Kód: [Vybrat]
links = menu_nav.xpath('.//li[not(@class="wnd-with-submenu")]/a')
for link links:
    ul = link.xpath(".//ancestor::ul[1]")
    print(link.text)
    print(link.get("href"))
    print(ul.get("class"))


Kód: [Vybrat]
li_obj = ul_obj[0].xpath('//li[parent::ul[@class="level-2"] and not(parent::ul[@class="level-3"])]/a')
Citovaný XPath výraz najde všechna a v elementech li, které mají jako rodiče ul s třídou level-2 a zároveň nemají jako rodiče ul s třídou level-3. Což je nesmysl, ta druhá podmínka je tam zbytečná – žádný li přímo uvnitř ul s třídou level-2 nemůže mít jako rodiče zároveň ul s třídou level-3. Tenhle výraz by vám tedy vybral jen všechna a v úrovni level-2, bez ohledu na to, zda tam je nebo není zanořená třetí úroveň.

Kit

  • *****
  • 707
    • Zobrazit profil
    • E-mail
Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #6 kdy: 25. 07. 2023, 22:19:05 »
Trochu mi uniká, proč jsou v XPath všechna lomítka zdvojena. To je v Pythonu nutné? Standarní XPath to nebere.

Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #7 kdy: 25. 07. 2023, 23:07:42 »
Trochu mi uniká, proč jsou v XPath všechna lomítka zdvojena. To je v Pythonu nutné? Standarní XPath to nebere.
Dvojité lomítko je normální součást XPath a znamená to potomka v libovolné úrovni zanoření. Je to zkratka pro /descendant-or-self::node()/.

Kit

  • *****
  • 707
    • Zobrazit profil
    • E-mail
Re:lxml xpath problém procházení strukturovaného menu [python]
« Odpověď #8 kdy: 25. 07. 2023, 23:25:57 »
Trochu mi uniká, proč jsou v XPath všechna lomítka zdvojena. To je v Pythonu nutné? Standarní XPath to nebere.
Dvojité lomítko je normální součást XPath a znamená to potomka v libovolné úrovni zanoření. Je to zkratka pro /descendant-or-self::node()/.

Bylo to míněno k //a a podobným komponentám tazatele.