为什么会写这篇博客,是因为我最近在写博客,对自己的博客浏览量比较好奇,总是会登上来看一下,正好我对爬虫感兴趣,所以就想着写一个爬虫来直接获取一下信息。
基本思路
这个爬虫的思路很简单,就是通过发起 HTTP 请求获取博主的我的博客的信息,来获取Ta的一些基本信息。我主要获取下面这幅图中,圆圈内的信息。
点击我的博客,就能看到这个页面,最上面是需要爬取的链接。
使用的jar包及遇到的问题
使用的jar包
我使用了 HttpClient 和 Jsoup 这两个工具,采用 maven 创建工程,下面是jar包的坐标。
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
遇到的问题
我发现直接发起请求会报一个警告日志:Invalid cookie header。但是我不知道是为什么(但是它不影响使用),根据网络上的方法,需要设置一个东西即可。
解决方法
//下面这两句,是因为总是报一个 Invalid cookie header,
//然后我在网上找到的解决方法。(去掉的话,不影响使用)。
RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
get.setConfig(defaultConfig);
代码实现
Spider 类
功能:获取数据,解析数据。
主要有两个方法:getRawData() 和 getInfo()。
getRawData 是获取相应链接的 html 页面数据,然后由 getInfo 负责解析数据,从中获取相应的信息。
package com.spider;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class InfoSpider {
private String url;
private CloseableHttpClient httpClient;
public InfoSpider(String url) {
this.url = url;
this.httpClient = HttpClients.createDefault();
}
/**
* 从url中获取原始的网页数据
* @throws IOException
* @throws ClientProtocolException
* */
public String getRawData() throws ClientProtocolException, IOException {
HttpGet get = new HttpGet(url);
//下面这两句,是因为总是报一个 Invalid cookie header,然后我在网上找到的解决方法。(去掉的话,不影响使用)。
RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
get.setConfig(defaultConfig);
//因为是初学,而且我这里只是请求一次数据即可,这里就简单设置一下 UA
get.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
HttpEntity entity = null;
String data = null;
try (CloseableHttpResponse response = httpClient.execute(get)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
entity = response.getEntity();
if (entity != null) {
data = EntityUtils.toString(entity, "UTF-8");
}
}
}
return data;
}
/**
* 从原始的网页数据中获取个人相关信息。
* @throws IOException
* */
public Bloger getInfo(String data) throws IOException {
Bloger bloger = null;
Document doc = Jsoup.parse(data, "UtF-8");
//获取描述用户信息的 div
Element element = doc.getElementById("asideProfile");
//获取用户url和昵称
Element infoA = doc.select("a#uid").first();
String homePageUrl = infoA.attr("href");
String nickName = infoA.text();
//获取用户的描述信息,如积分、等级、排名之类的信息。
Elements descriptions1 = element.getElementsByAttributeValue("class", "text-center");
Elements descriptions2 = element.select("div.grade-box.clearfix > dl");
//如果不想看下面这个流的操作,可以直接输出 文本信息 (但是,这里无法获取等级信息)
System.out.println(descriptions1.text());
System.out.println(descriptions2.text());
System.out.println("========================================");
//使用流,将这些描述信息转成 map 集合
Map<String, String> desMap = Stream.of(descriptions1, descriptions2)
.flatMap(des->des.stream())
.collect(Collectors.toMap(description->{
int position = -1;
String key = description.getElementsByTag("dt").first().text();
position = key.lastIndexOf(":");
return -1 == position ? key : key.substring(0, position);
},
description->{
Element level = description
.getElementsByAttributeValue("href", "https://blog.csdn.net/home/help.html#level")
.first();
if (level != null) {
return level.attr("title").split(",")[0];
} else {
return description.getElementsByTag("dd").first().text();
}
}));
//获取所有的勋章信息
Elements medals = element.select("div.icon-badge");
List<String> medalList = medals.stream()
.map(medal->medal.attr("title"))
.collect(Collectors.toList());
//为 Bloger 对象赋值,并返回该对象
bloger = new Bloger();
bloger.setUrl(homePageUrl);
bloger.setNickName(nickName);
bloger.setDesMap(desMap);
bloger.setMedalList(medalList);
return bloger;
}
}
这里说明一下:
//使用流,将这些描述信息转成 map 集合
Map<String, String> desMap = Stream.of(descriptions1, descriptions2)
.flatMap(des->des.stream())
.collect(Collectors.toMap(description->{
int position = -1;
String key = description.getElementsByTag("dt").first().text();
position = key.lastIndexOf(":");
return -1 == position ? key : key.substring(0, position);
},
description->{
Element level = description
.getElementsByAttributeValue("href", "https://blog.csdn.net/home/help.html#level")
.first();
if (level != null) {
return level.attr("title").split(",")[0];
} else {
return description.getElementsByTag("dd").first().text();
}
}));
这个方法是将 d1 标签中的数据取出来,d1 标签含有一个 dt 和 dd 标签,将dt表示的数据作为 key, dd标签的数据作为 value。然后由于有一部分标签中的 key 带有 “:”,所以我会判断一下,把 “:” 去掉。value 也是同样的,对于等级它不是直接的文本数据,所以我也处理了一下。虽然这个方法比较麻烦,但是也能用(偷懒一下。哈哈)。如果你不想使用的话,可以直接使用我上面的语句将文本打印出来。
Bloger 类
package com.spider;
import java.util.List;
import java.util.Map;
public class Bloger {
private String url; //主页
private String nickName; //昵称
private Map<String, String> desMap; //描述
private List<String> medalList; //勋章
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Map<String, String> getDesMap() {
return desMap;
}
public void setDesMap(Map<String, String> desMap) {
this.desMap = desMap;
}
public List<String> getMedalList() {
return medalList;
}
public void setMedalList(List<String> medalList) {
this.medalList = medalList;
}
@Override
public String toString() {
return "Bloger : \n url=" + url + ",\n nickName=" + nickName + ",\n desMap=" + desMap + ",\n medalList=" + medalList
+ "]";
}
}
Main 类
package com.spider;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
public class Main {
public static void main(String[] args) throws ClientProtocolException, IOException {
String url = "https://blog.csdn.net/qq_40734247";
InfoSpider spider = new InfoSpider(url);
String rawData = spider.getRawData();
Bloger bloger = spider.getInfo(rawData);
System.out.println(bloger);
}
}
运行结果
分隔线下面是 Bloger 对象的 toString 方法,上面是直接输出的 html 中的文本信息,因为我中间使用了流来处理 desMap,但是感觉代码失去可读性了,可能是我为了使用一个操作直接处理了吧,反而变麻烦了。不过它也不影响我使用,如果你不想获取到对象,就可以直接使用 text()
方法,获取文本数据,也是非常不错的。
总结
学习爬虫也不知道去搞什么,就自己来写一些简单的小工具了。反正也是有一点效果吧。