【问题标题】:Get the text in anchors within text nodes获取文本节点内锚点中的文本
【发布时间】:2012-10-24 02:57:52
【问题描述】:

我正在解析亚马逊上的产品评论,我想获取评论的完整文本,其中包括链接中的文本。

我目前正在使用 jSoup,尽管它很好,但它只会忽略锚点。当然,我可以只使用选择器从锚点中获取所有文本,但我会丢失有关该文本所在上下文的信息。

我认为一个例子是解释自己的最佳方式。

结构示例:

<div class="container">
  <div style="a">Something...</div>
  <div style="b">...Nested spans and divs... </div>
  <div class="tiny">_____ </div>
  " From the makers of the incredible <a href="SOMELINK">SOMEPRODUCT</a> we have this other product that blablabla.... Amazing specs, but <a href="SOME_OTHER_LINK">this other product</a> is somehow better".

我得到的信息:“从令人难以置信的制造商那里,我们得到了这个 blablabla 的其他产品......令人惊叹的规格,但不知何故更好”。

我想要什么:“来自令人难以置信的 SOMEPRODUCT 的制造商,我们有这个其他产品,blablabla...惊人的规格,但这个其他产品在某种程度上更好”。

我使用 jSoup 的代码:

Elements allContainers = doc.select(".container");
for (Element container : allContainers) {
  String reviewText = container.ownText(); // THIS EXCLUDES TEXT FROM LINKS
StdOut.println(reviewText);

我找不到这样做的方法,因为它看起来不像 jSoup 将文本节点视为实际节点,因此这些锚似乎不被考虑在下一个节点的子节点中。

我也对其他想法持开放态度,例如尝试使用 :not 选择器来获取它们,但我无法相信 jSoup 不允许保留链接中的文本,这太常见了,令人难以置信他们忽略了这个功能。

【问题讨论】:

    标签: java xpath jsoup


    【解决方案1】:

    它看起来不像 jSoup 将文本节点视为实际节点,

    否 - JSoup 文本节点是实际节点,元素也是。

    按照您描述问题的方式,您有一个非常具体的要求,我同意没有内置功能可以在一次调用中完全满足您的要求。然而,通过一个简单的辅助方法,问题是可以解决的。

    首先让我们回顾一下问题 - 父 div 有以下子级:

    div div div #text a #text a # text
    

    当然每个div 和a 元素都有其他子元素,包括文本节点。根据您的示例,听起来您想遍历所有子节点,而忽略任何不是文本节点的节点。当你找到第一个文本节点时,收集它的文本和任何后续节点的文本。

    当然可行,但我并不惊讶没有内置方法可以做到这一点。

    这是解决问题的一种实现方式:

       public static String textPlus(Element elem)
       {
          List<TextNode> textNodes = elem.textNodes();
          if (textNodes.isEmpty())
             return "";
    
          StringBuilder result = new StringBuilder();
          // start at the first text node
          Node currentNode = textNodes.get(0);
          while (currentNode != null)
          {
             // append deep text of all subsequent nodes
             if (currentNode instanceof TextNode)
             {
                TextNode currentText = (TextNode) currentNode;
                result.append(currentText.text());
             }
             else if (currentNode instanceof Element)
             {
                Element currentElement = (Element) currentNode;
                result.append(currentElement.text());
             }
             currentNode = currentNode.nextSibling();
          }
          return result.toString();
       }
    

    调用这个用法:

    Elements allContainers = doc.select(".container");
    for (Element container : allContainers) {
      String reviewText = textPlus(container);
      StdOut.println(reviewText);
    }
    

    鉴于您的示例 html 文本,此代码返回:

    “来自令人难以置信的 SOMEPRODUCT 的制造商,我们有这个其他产品 blablabla.... 惊人的规格,但这个其他产品在某种程度上更好。”

    希望这会有所帮助。

    【讨论】:

    • 很遗憾没有!如果您使用 container.text(),我将获得包含在包含 div 中的所有内容。回到这个例子,结果是这样的:“一些东西......(包含在中的文本)嵌套跨度和 div......____\”来自令人难以置信的 SOMEPRODUCT 的制造商,我们有这个其他产品,blablabla......惊人规格,但这个其他产品在某种程度上更好\""
    【解决方案2】:

    我没有测试过,但是根据元素类的jsoup API文档你应该使用方法文本而不是ownText

    文字

    公共字符串文本()

    Gets the combined text of this element and all its children.
    
    For example, given HTML <p>Hello <b>there</b> now!</p>, p.text() returns "Hello there now!"
    
    Returns:
        unencoded text, or empty string if none. 
    See Also:
        ownText(), textNodes() 
    

    拥有文本

    公共字符串 ownText()

    Gets the text owned by this element only; does not get the combined text of all children.
    
    For example, given HTML <p>Hello <b>there</b> now!</p>, p.ownText() returns "Hello now!", whereas p.text() returns "Hello there now!". Note that the text within the b element is not returned, as it is not a direct child of the p element.
    
    Returns:
        unencoded text, or empty string if none. 
    See Also:
        text(), textNodes() 
    

    【讨论】:

    • 是的,但不幸的是,DIV 是外部 DIV 文本的子级,因此,仅使用 text() 将不起作用 :-) 所以最后我确实使用了 text(),但是连同一个过滤器,消除了所有非链接节点(element.select("a[href]"))
    【解决方案3】:

    我接受了 Guido 的回答,因为即使它对我不起作用,但它肯定让我走上了正确的道路。

    Guido 的代码从第一个节点获取文本,然后迭代地遍历兄弟节点。不幸的是,我在现实世界中的例子还有两个复杂之处:

    1 - 仍然对来自锚点的文本没有任何要求,没有其他要求。我想要更健壮的东西,所以我在 Guido 的结构中添加了该选择。

    2 - 这仍然会从不需要的链接中获取文本,例如每个亚马逊评论末尾的“评论”和“永久链接”链接。其他选择器可以清除它们。

    我将发布对我有用的代码以供将来参考。希望它有所帮助:-)

    public static String textPlus(Element elem)
    {
        List<TextNode> textNodes = elem.textNodes();
        if (textNodes.isEmpty())
            return "";
    
        StringBuilder result = new StringBuilder();
    
        Node currentNode = textNodes.get(0);
    
        while (currentNode != null)
        {
            // append deep text of all subsequent nodes
            if (currentNode instanceof TextNode)
            {
                TextNode currentText = (TextNode) currentNode;
                String curtext = currentText.text();
                result.append("\n\n" + currentText.text());
            }
            else if (currentNode instanceof Element)
            {
                Element currentElement = (Element) currentNode;
                Elements anchorElements = currentElement.select("a[href]").select(":not(:contains(Comment))").select(":not(:contains(Permalink))");
                if (!anchorElements.isEmpty()) {
                    for (Element anchorElement : anchorElements)
                        result.append("\n\n" + anchorElement.text());
                }
            }
            currentNode = currentNode.nextSibling();
        }
        return result.toString().trim();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 2019-12-18
      相关资源
      最近更新 更多