前言
HTML是开发经常遇见的一种报文格式。但是我们日常中,更多是用它来渲染数据。利用他的很多各种标签,格式化我们的数据。一般前端接触的比较多。
但是,随着爬虫技术在互联网上越来越流行,如何处理我们爬到的HTML。。。我们当然可以针对性的代码处理每个HTML,但是每个网站的每个HTML格式,样式都可能会有比较大的差异。市场上急需要一个类库,可以将html中的数据,正常解析,抽取出来。解析HTML的框架组件,受到大家的欢迎追捧。
本节,我就java如何实现HTML解析,做简单介绍
1 JSoup
1.1 介绍
官网英文翻译如下:
jsoup 是一个处理真实世界HTML的java类库。提供了方便的api用于抽取和操作数据,使用最好的HTML5DOM方法和CSS选择器。JSoup实现了 WHATWG 这家公司的h5规范,像浏览器一样,解析HTML到同一个DOM对象。
- 支持从文件,URL,或者字符串中获取解析HTML
- 用dom遍历或者CSS选择器发现和抽取数据
- 操作HTML元素,属性和内容
- 根据安全列表清除用户提交的内容,以防止XSS攻击
- 输出整洁的HTML
- jsoup的设计目的是处理各种各样的HTML,jsoup将创建一个合理的解析树。
1.2 pom坐标引入
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
1.3 使用
1.3.1 加载文档的三种方式
只要解析的不是空字符串,就能返回一个结构合理的文档,其中包含(至少) 一个head和一个body元素。
一旦拥有了一个Document,你就可以使用Document中适当的方法或它父类 Element和Node中的方法来取得相关数据。
1.3.1.1 从字符串加载文档
@Test
public void testHtml1() {
String html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>";
Document doc = Jsoup.parse(html);
System.out.println(doc);
}
1.3.1.2 从文件中加载文档
@Test
public void testHtml2() throws Exception {
Document doc = Jsoup.parse(new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\2023-03\\test\\src\\main\\resources\\html\\test1.html"), "utf-8");
System.out.println(doc);
}
1.3.1.3 从URL中加载文档
@Test
public void testHtml3() throws Exception {
Document doc = Jsoup.connect("https://blog.csdn.net/wlyang666")
//可以在请求头添加cookie
.cookie("token","232893asfasddfasfdas")
//设置爬取超时时间
.timeout(10000)
//get请求
.get();
System.out.println(doc);
}
1.3.1.4 解析一个body片段
/**
* @Description:解析body片段
* @Author: wanlong
* @Date: 2023/4/26 14:43
* @return void
**/
@Test
public void testParsePart() {
String html = "<table><div><p>Lorem ipsum.</p>";
Document doc = Jsoup.parseBodyFragment(html);
Element body = doc.body();
System.out.println(doc);
System.out.println("=============");
System.out.println(body);
}
使用正常的 Jsoup.parse(String html) 方法,通常也可以得到相同的结果,但是明确将用户输入作为 body片段处理,以确保用户所提供的任何糟糕的HTML都将被解析成body元素。
1.3.2 dom主要api介绍
1.3.2.1 获取元素
getElementById(String id);
getElementsByTag(String tag);
getElementsByClass(String className);
getElementsByAttribute(String key);
siblingElements();
firstElementSibling();
lastElementSibling();
nextElementSibling();
previousElementSibling();
siblingElements();
firstElementSibling();
lastElementSibling();
nextElementSibling();
previousElementSibling();
parent();
children();
child(int index);
1.3.2.2 元素数据和属性
//获取属性
attr(String key);
//设置属性
attr(String key, String value);
//获取所有属性
attributes();
//获取元素ID
id();
//获取元素类名
className();
//获取元素所有类名
classNames();
//获取元素文本内容
text();
//设置元素文本内容
text(String value);
//获取元素内HTML
html();
//获取元素外HTML内容
outerHtml();
//获取元素数据内容(例如:script和style标签)
data();
//获取元素tag 和 tag名字
tag();
tagName();
1.4 测试案例
1.4.1 需求:
需要获得CSDN排行榜作者榜每个作者的用户名和名次,这里只简单演示排名如何用解析HTML的方式爬取,方式不一定合理,甚至也能通过直接api接口的方式获取数据。只是为了演示解析HTML。
1.4.2 步骤
- 调接口获取前100个用户的用户名
@Test
public void test(){
Map<String,String> resultMap=new LinkedHashMap<>();
for (int i = 1; i <5 ; i++) {
String url = String.format("https://blog.csdn.net/phoenix/web/v2/rank?page=%s&pageSize=25&rankType=total_author", "" + i);
String data = HttpUtil.get(url);
JSONObject jsonData = JSONObject.parseObject(data);
JSONArray list = jsonData.getJSONObject("data").getJSONArray("list");
for (Object o : list) {
JSONObject json=(JSONObject)o;
resultMap.put(json.getString("userName"),json.getString("currentRank"));
}
}
System.out.println(JSONObject.toJSONString(resultMap));
}
2. 逐个到这100个用户首页,爬取用户首页HTML,解析HTML,获取排名
@Test
public void testHtml3() throws Exception {
//这里正常维护的是前100个用户的用户名和接口获取的排名信息,这里考虑到用户隐私不展示,用我的账号做测试
String str="{\"wlyang666\":\"22\"}";
Map<String, String> resultMap = new LinkedHashMap<>();
//1.读取博客总榜
JSONObject json = JSONObject.parseObject(str);
for (Map.Entry<String, Object> stringObjectEntry : json.entrySet()) {
String username = stringObjectEntry.getKey();
String url = String.format("https://blog.csdn.net/%s", username);
//2.遍历每个作者,读取每个作者排名
Document doc = Jsoup.connect(url)
.cookie("token", "232893asfasddfasfdas")
.timeout(10000)
.get();
//解析文档
Elements paimingDiv = doc.getElementsMatchingOwnText("排名");
for (Element element : paimingDiv) {
try {
String paiming = element.firstElementSibling().text();
resultMap.put(username, paiming);
} catch (Exception e) {
System.out.println("有异常" + element);
e.printStackTrace();
}
}
}
System.out.println(JSONObject.toJSONString(resultMap));
}
1.4.3 运行结果
{“wlyang666”:“17,152”}
1.5 注意事项:
- 解析HTML可传参数 baseUri 是用来将相对 URL 转成绝对URL,并指定从哪个网站获取文档。
- 解析html有各种可能失败的场景,包括上面的案例,在真实测试的时候,也有个别客户解析失败,这种建议先捕获异常,后期考虑看是否做兼容处理,还是个别数据不重要直接丢弃
- 爬取网页过程,可能需要校验登录信息等,此时可以在请求头封装token,cokkie等信息
参考文档:
jsoup官网