如何使用JSoup构建NodeTraversor / NodeVisitor?

时间:2016-05-13 12:48:53

标签: java web-scraping jsoup traversal tree-traversal

我几乎是编程的初学者,目前正在尝试使用JSoup构建我的第一个Web scraper。到目前为止,我能够从目标网站的单个页面获取我想要的数据,但我自然希望以某种方式遍历整个网站。

JSoup似乎提供了某种遍历/访问者(这有什么不同?),但我完全不知道如何做到这一点。我知道树和节点是什么,并知道我的目标网站的结构,但我不知道如何创建(?)traverser / visitor-object(?)并让它在我的网站上运行。在工作中是否有一些先进的Java / oo魔法,我不知道?

不幸的是,Jsoup食谱和其他线程似乎都没有真正涵盖细节,所以如果有人能够朝着正确的方向推动我,我会非常感激。

1 个答案:

答案 0 :(得分:2)

  

JSoup似乎提供某种遍历/访问者(差异是什么?)

NodeTraversor将有效地遍历指定根节点下的所有节点。它没有使用递归,因此大型DOM不会创建堆栈溢出。

NodeVisitor(NV)是NodeTraversor(NT)的伴侣。每次NT进入节点时,它都会调用NV的head方法。每次NT离开节点时,它都会调用NV的tail方法。

NT已准备好并由Jsoup API提供给您。您所要做的就是为NT提供NV实现。

以下是从ElasticSearch source code获取的NodeVisitor的实际实现:

protected static String convertElementsToText(Elements elements) {
    if (elements == null || elements.isEmpty())
      return "";
    StringBuilder buffer = new StringBuilder();
    NodeTraversor nt = new NodeTraversor(new ToTextNodeVisitor(buffer));
    for (Element element : elements) {
      nt.traverse(element);
    }
    return buffer.toString().trim();
}

private static final class ToTextNodeVisitor implements NodeVisitor {
    final StringBuilder buffer;

    ToTextNodeVisitor(StringBuilder buffer) {
      this.buffer = buffer;
    }

    @Override
    public void head(Node node, int depth) {
      if (node instanceof TextNode) {
        TextNode textNode = (TextNode) node;
        String text = textNode.text().replace('\u00A0', ' ').trim(); // non breaking space
        if (!text.isEmpty()) {
          buffer.append(text);
          if (!text.endsWith(" ")) {
            buffer.append(" ");
          }
        }
      }
    }

    @Override
    public void tail(Node node, int depth) {
    }
}