【问题标题】:Detect if 2 HTML fragments have identical hierarchical structure检测 2 个 HTML 片段是否具有相同的层次结构
【发布时间】:2012-10-28 10:41:12
【问题描述】:

具有相同层次结构的片段示例:

(1)
<div>
  <span>It's a message</span>
</div>

(2)
<div>
  <span class='bold'>This is a new text</span>
</div>

具有不同结构的片段示例:

(1)
<div>
  <span><b>It's a message</b></span>
</div>

(2)
<div>
  <span>This is a new text</span>
</div>

所以,结构相似的片段对应一棵层次树(标签名相同,层次结构相同)。

如何仅使用 lxml 检测 2 个元素(html 片段)是否具有相同的结构?

对于一些更困难的情况(比示例),我有一个功能无法正常工作:

def _is_equal( el1, el2 ):      
    # input: 2 elements with possible equal structure and tag names
    # e.g. root = lxml.html.fromstring( buf )
    # el1 = root[ 0 ]
    # el2 = root[ 1 ]
    # move from top to bottom, compare elements
    result = False  

    if el1.tag == el2.tag:
        # has no children
        if len( el1 ) == len( el2 ):
            if len( el1 ) == 0:             
                return True
            else:
                # iterate one of them, for example el1
                i = 0
                for child1 in el1:
                    child2 = el2[ i ]
                    is_equal2 = _is_equal( child1, child2 )
                    if not is_equal2:
                        return False
                return True                     
        else:
            return False
    else:
        return False

代码未能检测到 class='tovar2' 的 2 个 div 具有相同的结构:

<body>


    <div class="tovar2">
        <h2 class="new">
            <a href="http://modnyedeti-krsk.ru/magazin/product/333193003">
                Куртка  д/д
            </a>
        </h2>
        <ul class="art">
            <li>
                Артикул: <span>1759</span>
            </li>
        </ul>
        <div>
            <div class="wrap" style="width:180px;"> 
                <div class="new">
                    <img src="shop_files/new-t.png" alt="">
                </div>     
                <a class="highslide" href="http://modnyedeti-krsk.ru/d/459730/d/820.jpg" onclick="return hs.expand(this)"> 
                    <img src="shop_files/fr_5.gif" style="background:url(/d/459730/d/548470803_5.jpg) 50% 50% no-repeat scroll;" alt="Куртка  д/д" height="160" width="180"> 
                </a>     
            </div>
        </div>

        <form action="" onsubmit="return addProductForm(17094601,333193003,3150.00,this,false);">
            <ul class="bott ">
                <li class="price">Цена:<br>
                    <span>
                        <b>
                            3 150
                        </b> руб.
                    </span>
                </li>
                <li class="amount">Кол-во:<br><input class="number" onclick="this.select()" value="1" name="product_amount" type="text">
                </li>
                <li class="buy"><input value="" type="submit">
                </li>
            </ul>
        </form>
    </div>


    <div class="tovar2">
        <h2 class="new">
            <a href="http://modnyedeti-krsk.ru/magazin/product/333124803">Куртка  д/д</a>
        </h2>
        <ul class="art">
            <li>
                Артикул: <span>1759</span>
            </li>
        </ul>
        <div>
            <div class="wrap" style="width:180px;"> 
                <div class="new">
                    <img src="shop_files/new-t.png" alt="">
                </div>     
                <a class="highslide" href="http://modnyedeti-krsk.ru/d/459730/d/820.jpg" onclick="return hs.expand(this)"> 
                    <img src="shop_files/fr_5.gif" style="background:url(/d/459730/d/548470803_5.jpg) 50% 50% no-repeat scroll;" alt="Куртка  д/д" height="160" width="180"> 
                </a>      
            </div>
        </div>      

        <form action="" onsubmit="return addProductForm(17094601,333124803,3150.00,this,false);">
            <ul class="bott ">
                <li class="price">Цена:<br>
                    <span>
                        <b>3 150</b> руб.
                    </span>
                </li>
                <li class="amount">Кол-во:<br><input class="number" onclick="this.select()" value="1" name="product_amount" type="text">
                </li>
                <li class="buy">
                    <input value="" type="submit">
                </li>
            </ul>
        </form>
    </div>

    </body>        

【问题讨论】:

  • @Asad 感谢您的回复。当代码无法检测到时,我添加了 2 个片段。这是一个真实的网站,所以比较复杂。

标签: python html xml lxml


【解决方案1】:

你把事情复杂了一点,你只需要在最后证明事情不是True时返回False

当两个元素的标签匹配、长度匹配并且每个配对的子元素相同时,两个元素相等。

Python 使用all() function 可以很容易地测试序列中的所有元素是否为True,并且通过使用zip(),我们可以很好地配对元素子元素。如果任何子对相等,all() 将提前终止:

def _is_equal( el1, el2 ):      
    if el1.tag == el2.tag and len(el1) == len(el2):
        return all(_is_equal(c1, c2) for c1, c2 in zip(el1, el2))

    return False

【讨论】:

  • 谢谢!在我批准之前让我测试一下。
  • @sergzach:代码中有一个小错误,已更正(并进一步简化)。对于您的示例 HTML,它为包含的两个 &lt;div class="twovar"&gt; 元素返回 True
  • 正确!非常感谢。
【解决方案2】:

您现有代码失败的原因是在有多个孩子的情况下, i 设置错误;您将其分配为零,然后从不增加它,因此您将 el1 的每个元素与 el2 的 first 元素进行比较,而不是与 el2 中与其位于相同位置的元素进行比较。

要修复现有代码,只需执行以下操作:

def _is_equal( el1, el2 ):      
    # input: 2 elements with possible equal structure and tag names
    # e.g. root = lxml.html.fromstring( buf )
    # el1 = root[ 0 ]
    # el2 = root[ 1 ]
    # move from top to bottom, compare elements
    result = False  

    if el1.tag == el2.tag:
        # has no children
        if len( el1 ) == len( el2 ):
            if len( el1 ) == 0:             
                return True
            else:
                # iterate one of them, for example el1
                for i, child1 in enumerate(el1):
                    child2 = el2[ i ]
                    is_equal2 = _is_equal( child1, child2 )
                    if not is_equal2:
                        return False
                return True                     
        else:
            return False
    else:
        return False

但是,您现有的代码可以更加简洁。您正在测试三个条件: 1) 标签匹配 2) 相同数量的孩子 3) 每对孩子的所有条件都成立

这些都是 Python 中的单行表达式。因此,您可以执行以下操作:

def _is_equal(el1, el2):
    return (el1.tag == el2.tag and
            len(el1) == len(el2) and
            all(_is_equal(c1, c2) for c1, c2 in zip(el1, el2)))

请注意,由于 and 短路并且 all 在其迭代的任何元素发生时返回 False,因此不会进行任何不必要的额外计算。

【讨论】:

  • 谢谢。 Martijn 是第一个,所以我批准了他的回答,为您的回答 +1。
  • 不错。 zip部分也可以是_is_equal(*p) for p in zip...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-23
  • 1970-01-01
相关资源
最近更新 更多