【问题标题】:Extract all text from possibly nested <span>s on a webpage从网页上可能嵌套的 <span> 中提取所有文本
【发布时间】:2016-12-09 18:48:19
【问题描述】:

我有一个网页,其中包含&lt;span class="x"&gt;&lt;/span&gt; 标签中包含的各种文本 sn-ps。我想生成每个这样的 sn-p 的有序列表。很直接。

皱纹:经常会出现嵌套在外部标签内的额外&lt;span class="x"&gt; 标签,我不在乎。本质上,我想要一个包含至少一个 &lt;span class="x"&gt; 标记内的每个字符串的列表,但是任何其他嵌套的此类标记都应该被忽略和丢弃。

这是一些 HTML 示例:

<p>
  Outer text. <span class="x">Inside a single span.</span> Back to outer text once more. <span class="x"><span class="x">Inside two spans</span> or just one</span>. Perhaps a <span class="x">single span contains <span class="x">several</span> 
  <span class="x">nests</span>  <span class="x">within <span class="x">it</span>
  </span>!</span>
</p>
<span class="x">Maybe there's a span out here.</span><span class="x">(Or two.)</span>
<p>
  <table>
    <tr>
      <td>
        <span class="x">Or <span class="x">in</span><span class="x">here</span></span>.
      </td>
    </tr>
  </table>
</p>
<p>
  <span>No.</span>  <span>Still no, but<span class="x">yes</span>.</span>
</p>

连同我想要的输出:

[ "Inside a single span.",
  "Inside two spans or just one",
  "single span contains several nests within it!",
  "Maybe there's a span out here.",
  "(Or two.)",
  "Or inhere",
  "yes" ]

我想提请注意此示例的具体功能:

  • 最外层跨度可以出现在较大的 HTML 文档中的任何深度。
  • 跨度可以任意嵌套深度。 (虽然在实践中我到目前为止还没有发现任何超过 3 或 4 层的实例)
  • 相邻的外部跨度之间可能有也可能没有空格;无论哪种方式,我都希望将它们的内容解析为单独的字符串。
  • 不需要没有类“x”的跨度标签。
  • 相邻的内部标签之间可能有也可能没有空格;我想保持原样。
  • 我预计不会有任何&lt;span class="x"&gt; 标记包含任何HTML 标记除了 额外嵌套的&lt;span class="x"&gt; 标记。

我会对 JavaScript + jQuery 解决方案、Python3 + BeautifulSoup 解决方案或其他完全满意的解决方案感到满意,前提是它比其中任何一个都更适合手头的任务。

【问题讨论】:

  • 即使没有x 类,你还想要&lt;span&gt;Maybe there's a span out here.&lt;/span&gt;&lt;span&gt;(Or two.)&lt;/span&gt; 吗?
  • 不,我不知道。很好,这是我在示例中遗漏的一个细节。
  • 如果你尝试什么我可以帮助你,但我不会为你编写代码。除非你想付钱给我:)
  • 我不是在寻找一个完整的解决方案,也许只是一个建议的算法!
  • 好的...这里有个建议:$('span').each(function(){someGlobalArray[] = $(this).text();});现在你只需要解决忽略子部分

标签: javascript jquery python web-scraping beautifulsoup


【解决方案1】:

您可以通过简单的 jQuery 语句获取 JavaScript 中的完整文本列表:

$("span.x").map(function(e) {return $(this).text() == "" ? null : $(this).text()})

由你决定如何使用它。

【讨论】:

    【解决方案2】:

    首先使用类x 获得最高跨度,但检查它没有类x 的父项。然后获取其中的innerText

    var topMost = $('span.x').filter(function() {
      return !$(this).parents('.x').length;
    });
    
    var texts = topMost.map(function() {
      return this.innerText;
    });
    
    console.log(texts);
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    
    <p>
      Outer text. <span class="x">Inside a single span.</span> Back to outer text once more. <span class="x"><span class="x">Inside two spans</span> or just one</span>. Perhaps a <span class="x">single span contains <span class="x">several</span> 
      <span class="x">nests</span>  <span class="x">within <span class="x">it</span>
      </span>!</span>
    </p>
    <span>Maybe there's a span out here.</span><span>(Or two.)</span>
    <p>
      <table>
        <tr>
          <td>
            <span class="x">Or <span class="x">in</span><span class="x">here</span></span>.
          </td>
        </tr>
      </table>
    </p>
    <p>
      <span>No.</span> <span>Still no, but<span class="x">yes</span>.</span>
    </p>

    【讨论】:

      【解决方案3】:

      用空白替换内部跨度标签应该可以完成这项工作:

      var st = [];
      $("span.x").map(function(e) {
          st.push($(this).html().replace('<span class="x">','').replace('</span>',''));
      });
      
      console.log(st);
      

      这有点脏,但你明白了

      【讨论】:

        【解决方案4】:

        试试:

        $('span.x').each(function(index, el) {
        console.log(el.childNodes[0].textContent)
        });
        

        $('span.x').each(function(index, el) {
         $(el).text();
        });
        

        这当然是 jquery 示例。 它将在控制台中列出所有跨度文本值。

        用这个 sn-p 简单地构建你的有序列表。

        【讨论】:

        • TypeError: undefined is not an object (evaluating 'el.childNodes')
        • 如果跨度没有内容,这没有正确的输出+崩溃
        • 对不起,我的错误,我已经修复了示例。
        【解决方案5】:

        不像其他解决方案那样优雅...

        from bs4 import BeautifulSoup
        
        soup = BeautifulSoup(html, 'html.parser')
        
        spans = soup.find_all('span', {'class':'x'})
        
        children = []
        for span in spans:
            chilren.extend(span.findChildren())
        
        children = [child.text for child in children]
        
        results = [span.text for span in spans if span.text not in children]
        

        【讨论】:

          【解决方案6】:

          JS 解决方案:

          function detect(elem, rettext=false){
          var answer=[];
          //loop trough childs
          for(i=0;i<elem.childNodes.length;i++){
            e=elem.childNodes[i];
            if(e.nodeType==3&&rettext){
                //elems child is direct x child+text so lets add it
                answer.push(e.textContent);
            }else{
            //elems child is an element so lets loop trough
            if( (" " + e.className + " ").replace(/[\n\t]/g, " ").indexOf(" x ") > -1 ){
                 //e is x so lets get direct childs and create one string
           answer.push(detect(e,true).join(""));
               }else{
               //not x so lets loop trough and return array
          
               a=detect(e);
               for(b=0;b<a.length;b++){
               answer.push(a[b]);
               }
               }
               }
               }
               return answer;
            }
          
          
          
           //start if window loaded
            window.onload=()=>{
            theansweris=detect(document.body);
            }
          

          这个函数循环遍历 html 树的所有元素。如果其中一个元素是x类,则将所有内部结果连接起来,直接添加textNodes

          注意: 这使用 ES6。如果你不知道那是什么,请写评论,我给你解释一下

          【讨论】:

            【解决方案7】:

            受这里过多的回复启发,我自己编写了一个 BeautifulSoup 解决方案。它的工作原理是在 html 中反复查找下一个 &lt;span class="x"&gt;,然后从其中删除所有标签,然后再找到下一个。

            from bs4 import BeautifulSoup
            soup = BeautifulSoup(html, "html.parser")
            
            current_span = soup.head
            while True:
                current_span = current_span.find_next("span", class_="x")
                if current_span:
                    current_span.string = "".join(current_span.strings)
                else: break
            
            return [span.string for span in soup.find_all("span", class_="x")]
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-10-16
              • 1970-01-01
              • 2020-01-03
              • 2021-09-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多