0

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

我目前正在使用 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 不允许保留链接中的文本,这太常见了,以至于无法相信他们忽略了这一点特征。

4

3 回答 3

1

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

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

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

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

div div div #text a #text a # text

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

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

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

   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.... 令人惊叹的规格,但这种另一种产品在某种程度上更好。”

希望这可以帮助。

于 2012-10-24T03:27:49.490 回答
1

我没有测试过,但是根据 Element 类的 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() 
于 2012-11-05T00:06:56.493 回答
0

我接受了 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();
于 2012-10-24T22:05:26.173 回答