Java 根据 HTML 生成 DOM 树

Java 根据 HTML 生成 DOM 树

一、前言

1、先看最终效果

测试 html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    这是不带标签的文本
    <h1>Hello World</h1>
    <div>
      <h1>这是大标题</h1>
      <p>这是一段段落</p>
      <p>这是一段段落</p>
      <div>
        <h1>这是小标题</h1>
        <p>这是一段段落</p>
        <p>这是一段段落</p>
      </div>
    </div>
    <div>
      <h1>这是大标题</h1>
      <p>这是一段段落</p>
      <p>这是一段段落</p>
    </div>
  </body>
</html>

生成 Dom 树并打印

此为简单使用方式测试结果!更复杂的使用方式,见最下方的测试参考!

html
├── head
└── body
    ├── span
    ├── h1
    ├── div
    │   ├── h1
    │   ├── p
    │   ├── p
    │   └── div
    │       ├── h1
    │       ├── p
    │       └── p
    └── div
        ├── h1
        ├── p
        └── p

2、说明

关于加 span 标签

html 中没有任何标签的纯本本我们统一给它们加上了 span 标签!

可能存在的问题

暂不详,如发现问题,会及时修改此文档!

二、第一步:解析 html

1、引入 Jsoup 依赖

<!--jsoup-->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.15.2</version>
</dependency>

2、字符串工具类

package com.zibo.zibo2022.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

public class StringUtility {

    private StringUtility() {
    }

    /**
     * 连接字符串
     * @param strings 字符串列表
     * @return 连接后的字符串
     */
    public static String concat(String... strings) {
        StringBuilder builder = new StringBuilder(strings.length << 3);
        for (String s : strings) {
            builder.append(s);
        }
        return builder.toString();
    }

    /**
     * 连接字符串
     * @param objects 字符串对象列表
     * @return 连接后的字符串
     */
    public static String concat(Object... objects) {
        StringBuilder builder = new StringBuilder(objects.length << 3);
        for (Object o : objects) {
            builder.append(o);
        }
        return builder.toString();
    }

    /**
     * 如果为 null 或者空字符串或空白返回 false,否则返回true
     * @param string 字符串
     * @return 判断结果
     */
    public static boolean isNotNullOrEmptyOrBlank(String string) {
        return string != null && !string.isEmpty() && !string.trim().isEmpty();
    }

}

3、解析 Html 工具类

package com.zibo.zibo2022.top.utils;

import com.zibo.zibo2022.utils.StringUtility;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PrettyHtmlUtil {

    private PrettyHtmlUtil() {
    }

    /**
     * 将 html 格式化 + 加标签
     * @param html html 字符串
     * @param clazz 格式化后的类型,仅支持 Document.class String.class
     * @return 格式化后的 html 字符串
     */
    public static <T> T prettyHtml(String html, Class<T> clazz, boolean onlyBody) {
        if (!StringUtility.isNotNullOrEmptyOrBlank(html)) {
            return null;
        }
        // 清空 html 中所有注释信息
        html = html.replaceAll("<!--.*?-->", "");
        // 解析 html
        Document doc = Jsoup.parse(html);
        // 设置输出格式
        doc.outputSettings(new Document.OutputSettings().outline(true));
        // 根据换行符分割 html 标签
        String[] split = onlyBody ? doc.body().html().split("\n") : doc.html().split("\n");
        // 去掉 split 里面的空内容
        List<String> list = new ArrayList<>();
        for (String s : split) {
            if (s!=null && !"".equals(s.trim())) {
                list.add(s);
            }
        }
        // 再次拼接 html 字符串
        StringBuilder finalHtml = new StringBuilder();
        // html 标签的正则表达式
        Pattern pattern = Pattern.compile("<.*?>");
        for (String s : list) {
            // 匹配
            Matcher matcher = pattern.matcher(s);
            // 如果没有匹配到
            if (!matcher.find()) {
                // 去掉前后空格 + 加上标签
                s = StringUtility.concat("<span>", s.trim(), "</span>");
            }
            finalHtml.append(s);
        }
        // 将再次拼接的 html 重新解析,美化代码格式
        Document parse = Jsoup.parse(finalHtml.toString());
        // 设置输出格式
        parse.outputSettings(new Document.OutputSettings().outline(true));
        // 返回对应格式的数据
        if (clazz.equals(String.class)) {
            return (T)parse.html();
        } else if (clazz.equals(Document.class)) {
            return (T)parse;
        } else {
            throw new RuntimeException("不支持的类型");
        }
    }

}

4、测试结果

测试 html 即前言中的 html !可以看到“这是不带标签的文本”加上了 span 标签!

<html>
 <head></head>
 <body>
  <span>这是不带标签的文本</span>
  <h1>Hello World</h1>
  <div>
   <h1>这是大标题</h1>
   <p>这是一段段落</p>
   <p>这是一段段落</p>
   <div>
    <h1>这是小标题</h1>
    <p>这是一段段落</p>
    <p>这是一段段落</p>
   </div>
  </div>
  <div>
   <h1>这是大标题</h1>
   <p>这是一段段落</p>
   <p>这是一段段落</p>
  </div>
 </body>
</html>

三、第二步:生成 Dom 树

1、定义实体类装载标签

Tag 标签类

package com.zibo.zibo2022.top.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 标签类
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tag {

    /**
     * 标签名
     */
    private String name;

    /**
     * 标签的内容或者什么
     */
    private String text;

    /**
     * 当前标签节点的 html 内容
     */
    private String html;

    /**
     * 当前标签节点的属性列表
     */
    private List<Attr> attrList;

    /**
     * 当前标签节点的子节点列表
     */
    private List<Tag> children;

}

Attr 属性类

package com.zibo.zibo2022.top.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 属性类
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Attr {

    /**
     * 属性名
     */
    private String name;

    /**
     * 属性值
     */
    private String value;

}

2、Pair<K, V> 类

package com.zibo.zibo2022.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Pair<K, V> {
    private K key;
    private V value;
}

3、构建树形标签类工具

package com.zibo.zibo2022.top.utils;

import com.zibo.zibo2022.top.entity.Attr;
import com.zibo.zibo2022.top.entity.Tag;
import com.zibo.zibo2022.utils.Pair;
import com.zibo.zibo2022.utils.StringUtility;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TagUtil {

    private TagUtil() {
    }

    /**
     * html 转成树形结构实体类
     *
     * @param file         html 文件
     * @param removeScript 是否移除 script 标签
     * @param selector     标签选择器
     * @param map          属性取值映 map (将标签的属性值或者文本取到 text 里面)
     * @return 树形结构实体类
     */
    public static Tag buildTag(File file, boolean onlyBody, boolean removeScript, String selector, Map<String, Pair<String, String>> map)
        throws Exception {
        // 读取 html 文件,转换成 Document 对象
        Document document = Jsoup.parse(file, "UTF-8");
        return buildTag(document.html(), onlyBody, removeScript, selector, map);
    }

    /**
     * html 转成树形结构实体类
     *
     * @param html         html 字符串
     * @param onlyBody     是否只取 body 内容
     * @param removeScript 是否移除 script 标签
     * @param selector     标签选择器
     * @param map          属性取值映 map (将标签的属性值或者文本取到 text 里面)
     * @return 树形结构实体类
     */
    public static Tag buildTag(String html, boolean onlyBody, boolean removeScript, String selector,
                               Map<String, Pair<String, String>> map) {
        // 适配选择器
        Document doc;
        if (StringUtility.isNotNullOrEmptyOrBlank(selector)) {
            doc = PrettyHtmlUtil.prettyHtml(Jsoup.parse(html).select(selector).html(), Document.class, onlyBody);
        } else {
            doc = PrettyHtmlUtil.prettyHtml(html, Document.class, onlyBody);
        }
        assert doc != null;
        // 拿到 html Node 对象
        Node node = doc.childNodes().get(0);
        return buildTag(node, removeScript, map);
    }

    /**
     * html 转成树形结构实体类 (递归)
     *
     * @param node         html Node 对象
     * @param removeScript 是否移除 script 标签
     * @param map          属性取值映 map (将标签的属性值或者文本取到 text 里面)
     * @return 树形结构实体类
     */
    private static Tag buildTag(Node node, boolean removeScript, Map<String, Pair<String, String>> map) {
        // 创建一个标签实体类
        Tag tag = new Tag();
        // 如果是 script 标签且需要移除,则移除
        if (removeScript && "script".equals(node.nodeName())) {
            return null;
        }
        // 创建属性列表
        List<Attr> attrList = new ArrayList<>();
        // 如果当前节点有属性,则添加到属性列表中
        if (!node.attributes().isEmpty()) {
            node.attributes().forEach(key -> attrList.add(new Attr(key.getKey(), key.getValue())));
        }
        // 设置标签名称
        tag.setName(node.nodeName());
        // 设置节点 html 内容
        tag.setHtml(node.outerHtml());
        // 设置属性列表
        tag.setAttrList(attrList);
        // 创建子标签列表
        List<Tag> children = new ArrayList<>();
        // 如果当前节点有子节点,则添加到子标签列表中
        if (!node.childNodes().isEmpty()) {
            for (Node child : node.childNodes()) {
                // 跳过纯文本等内容
                if (!child.nodeName().equals("#text") && !child.nodeName().equals("#comment") && !child.nodeName().equals("#document")
                    && !child.nodeName().equals("#document-fragment") && !child.nodeName().equals("#document-type") &&
                    !child.nodeName().equals("#document-type-fragment")) {
                    // 往下一层递归
                    Tag buildTag = buildTag(child, removeScript, map);
                    // 如果不为空,则添加到子标签列表中
                    if (buildTag != null) {
                        children.add(buildTag);
                    }
                }
            }
        }
        // 如果里面是纯文本,则将文本设置到 text 属性中
        if (node.childNodes().size() == 1 && "#text".equals(node.childNodes().get(0).nodeName())) {
            tag.setText(node.childNodes().get(0).toString());
        }
        // 不同的标签根据 map 取值,并设置到 text 属性中
        switch (node.nodeName().trim()) {
            case "a" -> {
                String a = tag.getAttrList().stream().filter(attr -> "href".equals(attr.getName())).findFirst().orElse(new Attr("href", ""))
                    .getValue();
                if (map != null && map.containsKey("a") && !a.startsWith("http")) {
                    Pair<String, String> pair = map.get("a");
                    tag.setText(StringUtility.concat(pair.getKey(), a, pair.getValue()));
                } else {
                    tag.setText(a);
                }
                if (node.childNodes().size() == 1) {
                    if ("#text".equals(node.childNodes().get(0).nodeName())) {
                        tag.setText(StringUtility.concat(tag.getText(), " ", node.childNodes().get(0).toString()));
                    }
                }
            }
            case "img" -> {
                String src = tag.getAttrList().stream().filter(attr -> "src".equals(attr.getName())).findFirst().orElse(new Attr("src", ""))
                    .getValue();
                if (map != null && map.containsKey("img") && !src.startsWith("http")) {
                    Pair<String, String> pair = map.get("img");
                    tag.setText(StringUtility.concat(pair.getKey(), src, pair.getValue()));
                } else {
                    tag.setText(src);
                }
            }
            case "div" -> {
                String replace =
                    tag.getAttrList().stream().filter(attr -> "class".equals(attr.getName())).findFirst().orElse(new Attr("class", ""))
                        .getValue().replace(" ", ".");
                if (StringUtility.isNotNullOrEmptyOrBlank(replace)) {
                    tag.setText("." + replace);
                }
            }
            default -> {
            }
        }
        // 设置子标签列表
        tag.setChildren(children);
        return tag;
    }

    // 这个复杂测试用
    public static Map<String, String> getTextByPath(Tag tag, Map<String, Pair<String, String>> pathMap) {
        Map<String, String> newMap = new HashMap<>();
        Document parse = Jsoup.parse(tag.getHtml());
        for (Map.Entry<String, Pair<String, String>> entry : pathMap.entrySet()) {
            Elements elements = parse.select(entry.getValue().getKey());
            if (elements.size() == 1) {
                if (StringUtility.isNotNullOrEmptyOrBlank(entry.getValue().getValue())) {
                    newMap.put(entry.getKey(), elements.get(0).attr(entry.getValue().getValue()));
                } else {
                    newMap.put(entry.getKey(), elements.get(0).text());
                }
            } else {
                System.out.println(elements);
                throw new RuntimeException("找到的元素数量大于 1 !");
            }
        }
        return newMap;
    }

}

4、测试结果

测试 html 即前言中的 html !

Tag(name=html, text=null, html=<html>
 <head></head>
 <body>
  <span>这是不带标签的文本</span>
  <h1>Hello World</h1>
  <div>
   <h1>这是大标题</h1>
   <p>这是一段段落</p>
   <p>这是一段段落</p>
   <div>
    <h1>这是小标题</h1>
    <p>这是一段段落</p>
    <p>这是一段段落</p>
   </div>
  </div>
  <div>
   <h1>这是大标题</h1>
   <p>这是一段段落</p>
   <p>这是一段段落</p>
  </div>
 </body>
</html>, attrList=[], children=[Tag(name=head, text=null, html=<head></head>, attrList=[], children=[]), Tag(name=body, text=null, html=<body>
 <span>这是不带标签的文本</span>
 <h1>Hello World</h1>
 <div>
  <h1>这是大标题</h1>
  <p>这是一段段落</p>
  <p>这是一段段落</p>
  <div>
   <h1>这是小标题</h1>
   <p>这是一段段落</p>
   <p>这是一段段落</p>
  </div>
 </div>
 <div>
  <h1>这是大标题</h1>
  <p>这是一段段落</p>
  <p>这是一段段落</p>
 </div>
</body>, attrList=[], children=[Tag(name=span, text=这是不带标签的文本, html=<span>这是不带标签的文本</span>, attrList=[], children=[]), Tag(name=h1, text=Hello World, html=<h1>Hello World</h1>, attrList=[], children=[]), Tag(name=div, text=null, html=<div>
 <h1>这是大标题</h1>
 <p>这是一段段落</p>
 <p>这是一段段落</p>
 <div>
  <h1>这是小标题</h1>
  <p>这是一段段落</p>
  <p>这是一段段落</p>
 </div>
</div>, attrList=[], children=[Tag(name=h1, text=这是大标题, html=<h1>这是大标题</h1>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[]), Tag(name=div, text=null, html=<div>
 <h1>这是小标题</h1>
 <p>这是一段段落</p>
 <p>这是一段段落</p>
</div>, attrList=[], children=[Tag(name=h1, text=这是小标题, html=<h1>这是小标题</h1>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[])])]), Tag(name=div, text=null, html=<div>
 <h1>这是大标题</h1>
 <p>这是一段段落</p>
 <p>这是一段段落</p>
</div>, attrList=[], children=[Tag(name=h1, text=这是大标题, html=<h1>这是大标题</h1>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[]), Tag(name=p, text=这是一段段落, html=<p>这是一段段落</p>, attrList=[], children=[])])])])

四、打印 Dom 树

1、引入 commons-beanutils 依赖

<!--引入依赖commons-beanutils-->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

2、树节点实体类

package com.zibo.zibo2022.top.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Iterator;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode {

    private String name;
    private List<TreeNode> children;

    public String toString() {
        StringBuilder buffer = new StringBuilder(50);
        print(buffer, "", "");
        return buffer.toString();
    }

    private void print(StringBuilder buffer, String prefix, String childrenPrefix) {
        buffer.append(prefix);
        buffer.append(name);
        buffer.append('\n');
        for (Iterator<TreeNode> it = children.iterator(); it.hasNext();) {
            TreeNode next = it.next();
            if (it.hasNext()) {
                next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│   ");
            } else {
                next.print(buffer, childrenPrefix + "└── ", childrenPrefix + "    ");
            }
        }
    }
}

3、树节点工具类

package com.zibo.zibo2022.top.utils;

import com.zibo.zibo2022.top.entity.TreeNode;
import com.zibo.zibo2022.utils.StringUtility;
import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

public class TreeNodeUtil {

    private TreeNodeUtil() {
    }

    /**
     * 根据树结构对象生成树结构
     * @param treeObject 树结构对象
     * @param printPropName 打印属性名称
     * @param childrenPropName 子节点属性名称
     * @return 树结构对象
     */
    public static TreeNode buildTreeNode(Object treeObject, String printPropName, String childrenPropName) {
        // 创建树结构对象
        TreeNode treeNode = new TreeNode();
        // 要打印的内容
        String printValue = "";
        // 子节点列表
        List<?> childrenValue = new ArrayList<>();
        // 子树节点列表
        List<TreeNode> children = new ArrayList<>();
        try {
            // 要打印的值
            printValue = (String) PropertyUtils.getProperty(treeObject, printPropName);
            childrenValue = (List<?>) PropertyUtils.getProperty(treeObject, childrenPropName);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 设置要打印的内容
        treeNode.setName(printValue);
        if (!childrenValue.isEmpty()) {
            for (Object child : childrenValue) {
                // 编辑每一个子节点,递归构建树节点
                TreeNode childTreeNode = buildTreeNode(child, printPropName, childrenPropName);
                // 添加树节点
                children.add(childTreeNode);
            }
        }
        // 设置树节点的子内容
        treeNode.setChildren(children);
        return treeNode;
    }

    /**
     * 根据树结构对象生成树结构
     * @param treeObject 树结构对象
     * @param printPropNameList 打印属性名称列表
     * @param childrenPropName 子节点属性名称
     * @return 树结构对象
     */
    public static TreeNode buildTreeNode(Object treeObject, List<String> printPropNameList, String childrenPropName) {
        TreeNode treeNode = new TreeNode();
        // 获取printPropName属性的值
        String printValue = "";
        List<?> childrenValue = new ArrayList<>();
        List<TreeNode> children = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        try {
            for (String printPropName : printPropNameList) {
                String name = (String) PropertyUtils.getProperty(treeObject, printPropName);
                sb.append(StringUtility.isNotNullOrEmptyOrBlank(name) ? name : "").append(" ");
            }
            printValue = sb.toString().trim();
            childrenValue = (List<?>) PropertyUtils.getProperty(treeObject, childrenPropName);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        treeNode.setName(printValue);
        if (! childrenValue.isEmpty()) {
            for (Object child : childrenValue) {
                TreeNode childTreeNode = buildTreeNode(child, printPropNameList, childrenPropName);
                children.add(childTreeNode);
            }
        }
        treeNode.setChildren(children);
        return treeNode;
    }
}

4、测试结果

html
├── head
└── body
    ├── span
    ├── h1
    ├── div
    │   ├── h1
    │   ├── p
    │   ├── p
    │   └── div
    │       ├── h1
    │       ├── p
    │       └── p
    └── div
        ├── h1
        ├── p
        └── p

五、测试参考

1、简单测试

仅打印标签!测试结果见前言!

package com.zibo.zibo2022.main;

import com.zibo.zibo2022.top.entity.Tag;
import com.zibo.zibo2022.top.entity.TreeNode;
import com.zibo.zibo2022.top.utils.TagUtil;
import com.zibo.zibo2022.top.utils.TreeNodeUtil;

import java.io.File;
import java.util.Arrays;

public class Main2 {

    public static void main(String[] args) throws Exception {
        Tag tag = TagUtil.buildTag(new File("C:\\Users\\Administrator\\Desktop\\test.html"), true, true,
                null,
                null
        );
        System.out.println(tag);
        TreeNode treeNode = TreeNodeUtil.buildTreeNode(tag, "name", "children");
        System.out.println(treeNode);
    }

}

2、复杂测试

仅用于参考,并不能运行起来!时间不多了,后续我补充完整!可参考方法做适配!

代码演示

测试的 html 代码:http://guozhivip.com/rank/

package com.zibo.zibo2022.main;

import com.zibo.zibo2022.top.entity.Tag;
import com.zibo.zibo2022.top.entity.TreeNode;
import com.zibo.zibo2022.top.utils.TagUtil;
import com.zibo.zibo2022.top.utils.TreeNodeUtil;
import com.zibo.zibo2022.utils.Pair;
import com.zibo.zibo2022.utils.StringUtility;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Main {

    public static void main(String[] args) throws Exception {
        // 前后缀
        Map<String, Pair<String, String>> concatMap = new HashMap<>();
        concatMap.put("img", new Pair<>("http://www.baidu.com/rank/", ""));
        Map<String, Pair<String, String>> pathMap = new HashMap<>();
        pathMap.put("a", new Pair<>("a", "href"));
        pathMap.put("span", new Pair<>("a > span", ""));
        pathMap.put("img", new Pair<>("a > div > img", "src"));
        Tag tag = TagUtil.buildTag(new File("C:\\Users\\Administrator\\Desktop\\test.html"), true, true,
                null,
                null
        );
        Optional<Tag> bodyOptional = tag.getChildren().stream().filter(t -> "body".equals(t.getName())).findFirst();
        if (bodyOptional.isPresent()) {
            Tag body = bodyOptional.get();
            for (Tag child : body.getChildren()) {
                Map<String, String> hashMap = TagUtil.getTextByPath(child, pathMap);
                for (Map.Entry<String, String> entry : hashMap.entrySet()) {
                    if (concatMap.get(entry.getKey()) != null) {
                        entry.setValue(StringUtility.concat(concatMap.get(entry.getKey()).getKey(), entry.getValue(), concatMap.get(entry.getKey()).getValue()));
                    }
                }
                System.out.println(hashMap);
            }
        }
        TreeNode treeNode = TreeNodeUtil.buildTreeNode(tag, Arrays.asList("name", "text"), "children");
        System.out.println(treeNode);
    }

}

打印的 Dom 树

太长了,贴一部分出来!

html
├── head
└── body
    ├── div .col.pc
    │   └── a https://weibo.com/newlogin?tabtype=search&url=
    │       ├── span 微博热搜
    │       └── div .slogo
    │           └── img http://www.baidu.com/rank/images/wb.png
    ├── div .col.pc
    │   └── a https://top.baidu.com/board
    │       ├── span 百度风云榜
    │       └── div .slogo
    │           └── img http://www.baidu.com/rank/images/bd.png
    ├── div .col.pc
    │   └── a https://www.sogou.com/web?query=%E4%BB%8A%E6%97%A5%E7%83%AD%E6%90%9C
    │       ├── span 搜狗热搜榜
    │       └── div .slogo
    │           └── img http://www.baidu.com/rank/images/sg.png

打印的选取数据 map

用于取数据!也有点长,贴一部分出来!

{a=https://weibo.com/newlogin?tabtype=search&url=, img=http://www.baidu.com/rank/images/wb.png, span=微博热搜}
{a=https://top.baidu.com/board, img=http://www.baidu.com/rank/images/bd.png, span=百度风云榜}
{a=https://www.sogou.com/web?query=%E4%BB%8A%E6%97%A5%E7%83%AD%E6%90%9C, img=http://www.baidu.com/rank/images/sg.png, span=搜狗热搜榜}
{a=https://trends.so.com/hot, img=http://www.baidu.com/rank/images/360.png, span=360实时热点}
{a=https://weixin.sogou.com/, img=http://www.baidu.com/rank/images/wx.png, span=微信热门}
{a=https://weibo.com/newlogin?tabtype=topic&url=, img=http://www.baidu.com/rank/images/wb.png, span=微博话题榜}
{a=https://m.weibo.cn/p/106003type=25&t=3&disable_hot=1&filter_type=realtimehot, img=http://www.baidu.com/rank/images/wb.png, span=微博热搜}
{a=https://top.baidu.com/board?tab=realtime, img=http://www.baidu.com/rank/images/bd.png, span=百度热搜}
{a=https://ib.snssdk.com/rogue/aladdin_landingpage/template/aladdin_landingpage/hot_words.html?isBrowser=true&traffic_source=, img=http://www.baidu.com/rank/images/tt.png, span=头条热点}
{a=https://m.sogou.com/web/searchList.jsp?keyword=%E4%BB%8A%E6%97%A5%E7%83%AD%E7%82%B9, img=http://www.baidu.com/rank/images/sg.png, span=搜狗热搜}
{a=https://m.so.com/s?q=%E4%BB%8A%E6%97%A5%E7%83%AD%E7%82%B9, img=http://www.baidu.com/rank/images/360.png, span=360热搜}
{a=https://weixin.sogou.com/, img=http://www.baidu.com/rank/images/wx.png, span=微信热门}
{a=https://www.zhihu.com/billboard, img=http://www.baidu.com/rank/images/zh.png, span=知乎热榜}

3、后记

最终使用方式还在优化,基本的路已经走通了!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用Qt的QXmlStreamReader类来解析XML文件并生成结构。下面是一个简单的示例代码: ```cpp #include <QFile> #include <QXmlStreamReader> #include <QDebug> void parseXmlFile(const QString &filePath, QTreeWidgetItem *parentItem) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Failed to open XML file"; return; } QXmlStreamReader xmlReader(&file); while (!xmlReader.atEnd() && !xmlReader.hasError()) { QXmlStreamReader::TokenType token = xmlReader.readNext(); if (token == QXmlStreamReader::StartElement) { QString elementName = xmlReader.name().toString(); QTreeWidgetItem *item = new QTreeWidgetItem(parentItem); item->setText(0, elementName); // 解析元素的属性 QXmlStreamAttributes attributes = xmlReader.attributes(); foreach (const QXmlStreamAttribute &attribute, attributes) { QString attributeName = attribute.name().toString(); QString attributeValue = attribute.value().toString(); QTreeWidgetItem *attributeItem = new QTreeWidgetItem(item); attributeItem->setText(0, attributeName + ": " + attributeValue); } } else if (token == QXmlStreamReader::Characters && !xmlReader.isWhitespace()) { QString text = xmlReader.text().toString(); QTreeWidgetItem *textItem = new QTreeWidgetItem(parentItem); textItem->setText(0, "Text: " + text); } else if (token == QXmlStreamReader::EndElement) { // 当前元素结束,返回上一级 if (parentItem) parentItem = parentItem->parent(); } } if (xmlReader.hasError()) { qDebug() << "XML parsing error: " << xmlReader.errorString(); } file.close(); } int main() { QTreeWidgetItem *rootItem = new QTreeWidgetItem(); QString filePath = "your_xml_file.xml"; parseXmlFile(filePath, rootItem); // 使用生成结构进行其他操作 delete rootItem; return 0; } ``` 在上述示例中,`parseXmlFile`函数用于解析XML文件并生成结构。通过`QXmlStreamReader`逐行读取XML文件,根据不同的`TokenType`进行相应的处理,创建`QTreeWidgetItem`来表示XML元素、属性和文本。你可以根据需要对生成结构进行进一步的操作。记得将上述代码中的`your_xml_file.xml`替换为你要解析的XML文件路径。 希望这可以帮助到你!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值