前言
自從寫完關(guān)于Lifecycle的文章后就沒有發(fā)現(xiàn)其他有興趣的源碼了申眼,所以呢,我決定看看寫寫后臺代碼蝉衣,嘗試一波括尸。經(jīng)過大概一周的百度,SSM框架基本搭建完成病毡。突發(fā)奇想濒翻,打算收集一下各種熱搜。首先想到的那肯定是微博熱搜了啦膜,so有送,我們來爬下微博熱搜吧!
工具
Jsoup 是一款Java 的HTML解析器功戚,可直接解析某個URL地址娶眷、HTML文本內(nèi)容。它提供了一套非常省力的API啸臀,可通過DOM届宠,CSS以及類似于jQuery的操作方法來取出和操作數(shù)據(jù)。
之前使用過Jsoup來抓取公交車實時到站信息乘粒,并且自己做了個簡單的公交車到站查詢APP豌注。關(guān)于Jsoup的使用后面會講到。
分析網(wǎng)頁結(jié)構(gòu)
在抓取數(shù)據(jù)的時候灯萍,首先要做的就是分析這個網(wǎng)頁的結(jié)構(gòu)轧铁,哪里是我們需要抓取的,哪些數(shù)據(jù)是我們需要的。我們先看下微博熱搜灾挨,可以通過瀏覽器的開發(fā)者模式顯示Html代碼:
我們可以看到,右邊那<tbody>里面正是我們需要抓取的數(shù)據(jù)狂丝,話不多說救斑,上碼吧童本!
代碼實現(xiàn)
先吐槽一波,微博在加載熱搜的時候并沒有直接用html加載脸候,而是通過了一段js加載穷娱,如下圖:
通過fiddler抓包可以看到,這里通過加載js來加載的熱搜运沦,而且里面有一段json泵额,這段json就是我們需要的熱搜數(shù)據(jù)(截圖不好截圖,后面再看吧)携添。
先寫代碼:
先根據(jù)json創(chuàng)建實體類:
// 微博json對于的實體類
public class WeiboJsonEntity {
private String pid;
private List<String> js;
private List<String> css;
private String html;
// get set
}
真正熱搜實體類:
public class WeiboHotEntity {
private Integer id = 0;
private Integer sort = 0;
private Integer num = 0;
private String title;
private String linkUrl;
private String channel;
private String date;
// get set
}
正式抓取網(wǎng)頁:
public static List<WeiboHotEntity> parseWeiboHot() {
try {
long currentTime = System.currentTimeMillis();
// 通過jsoup將對應(yīng)url轉(zhuǎn)為document
Document doc = Jsoup.parse(new URL("http://s.weibo.com/top/summary?cate=realtimehot"), 10000);
// 獲取script標(biāo)簽對應(yīng)的Element list
Elements script = doc.select("script");
for (Element element : script) {
String data = element.data();
// 這里是獲取json開始的位置(最好的方式是正則匹配)
int i = data.indexOf("(");
if (i >= 0) {
String substring = data.substring(i + 1, data.lastIndexOf(")"));
try {
// 通過Gson將json轉(zhuǎn)成WeiboJsonEntity實體嫁盲,出錯肯定不是我們需要的
WeiboJsonEntity weiboJsonEneity = new Gson().fromJson(substring, WeiboJsonEntity.class);
System.out.println(substring);
// 熱搜對應(yīng)的部分
if (weiboJsonEneity.getPid().equals("pl_top_realtimehot")) {
// 開始解析數(shù)據(jù)
return parseSearchTop(weiboJsonEneity, currentTime);
}
} catch (Exception e) {
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
獲取到的熱搜json:
{
"pid": "pl_top_realtimehot",
"js": ["apps\/search_v6\/js\/pl\/top\/unLogin.js?version=20180717111600", "apps\/search_v6\/js\/pl\/searchHead.js?version=20180717111600", "apps\/search_v6\/js\/pl\/top\/realtimehot.js?version=20180717111600"],
"css": ["appstyle\/searchV45\/css_v6\/pl\/pl_Sranklist.css?version=20180717111600", "appstyle\/searchV45\/css_v6\/pl\/pl_Srankbank.css?version=20180717111600", "appstyle\/searchV45\/css_v6\/patch\/Srank_hot.css?version=20180717111600"],
"html": "<div class=\"hot_ranklist\">\n <table tab=\"realtimehot\" id=\"realtimehot\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"star_bank_table \">\n <thead>\n <tr class=\"thead_tr\">\n <th class=\"th_01\">序號<\/th>\n <th class=\"th_02\">關(guān)鍵詞<\/th>\n <th class=\"th_03\">搜索熱度<\/th>\n <th class=\"th_04\"><\/th>\n <th class=\"th_05\"><\/th>\n <\/tr>\n <\/thead>\n <tr action-type=\"tr_hover\">\n <td class=\"td_01\"><span class=\"icon_pinned\"><\/span><\/td>\n <td class=\"td_02\">\n <div class=\"rank_content\">\n <p class=\"star_name\">\n "
}
可以看到我們需要的就是html里面的內(nèi)容,這里面內(nèi)容非為兩部分:
-
中國特色主義推薦位薪寓,第一條:
- 普通熱搜
剩下的那些
private static List<WeiboHotEntity> parseSearchTop(WeiboJsonEntity weiboJsonEneity, long currentTime) {
List<WeiboHotEntity> weiboHotEntities = new ArrayList<>();
// 再次解析亡资,將html代碼轉(zhuǎn)成document
Document parse = Jsoup.parse(weiboJsonEneity.getHtml());
// 中國特色推薦!向叉!
Elements tbody = parse.getElementsByAttributeValue("action-type", "tr_hover");
for (Element element : tbody) {
weiboHotEntities.add(parseDetail(element, currentTime));
}
// 熱搜
Elements hover = parse.getElementsByAttributeValue("action-type", "hover");
for (Element element : hover) {
weiboHotEntities.add(parseDetail(element, currentTime));
}
return weiboHotEntities;
}
通過瀏覽器可以獲取到其結(jié)構(gòu)锥腻,我們需要提取action-type=tr_hover
對應(yīng)的元素
之后,我們只需要根據(jù)需求獲取td_01母谎、td_02等里面的數(shù)據(jù)即可:
private static WeiboHotEntity parseDetail(Element element, long currentTime) {
WeiboHotEntity weiboHotEntity = new WeiboHotEntity();
Elements td01 = element.getElementsByClass("td_01");
// 排序
if (isListNotEmpty(td01)) {
// 獲取熱度排名瘦黑,如果沒有的話,則是推薦設(shè)為0
Elements em = td01.get(0).getElementsByTag("em");
if (em != null && em.size() > 0) {
//
Integer integer = Integer.valueOf(em.get(0).text());
weiboHotEntity.setSort(integer);
} else {
weiboHotEntity.setSort(0);
}
}
// 名稱和鏈接
Elements td02 = element.getElementsByClass("td_02");
if (isListNotEmpty(td02)) {
Elements a = td02.get(0).getElementsByTag("a");
if (isListNotEmpty(a)) {
Element el = a.get(0);
String href = el.attributes().get("href");
Elements i = td02.get(0).getElementsByTag("i");
if (isListNotEmpty(i)) {
String text = i.get(0).text();
// 感覺就是個廣告
if (text.equals("薦")) {
weiboHotEntity.setLinkUrl(DOMAIN_WEIBO + "/weibo/" + el.text() + "&Refer=top");
} else {
weiboHotEntity.setLinkUrl(DOMAIN_WEIBO + href);
}
} else {
weiboHotEntity.setLinkUrl(DOMAIN_WEIBO + href);
}
weiboHotEntity.setTitle(el.text());
}
}
// 熱度值
Elements td03 = element.getElementsByClass("td_03");
if (isListNotEmpty(td03)) {
Elements span = td03.get(0).getElementsByTag("span");
if (isListNotEmpty(span)) {
weiboHotEntity.setNum(Integer.valueOf(span.get(0).text()));
}
}
weiboHotEntity.setChannel("微博");
weiboHotEntity.setDate(getDateStr(currentTime));
return weiboHotEntity;
}
這里面都是比較簡單的獲取數(shù)據(jù)即可奇唤,比如獲取排序幸斥,通過獲取td_01里面的<em>標(biāo)簽的值即可。
鏈接和名稱以及熱度值也是同理咬扇。這里說個比較有意思的甲葬,普通的熱搜都是可以直接通過微博的域名+對應(yīng)的鏈接跳轉(zhuǎn)到相應(yīng)的熱搜,但是有一種標(biāo)簽懈贺,就是為薦的標(biāo)簽需要配置特殊的跳轉(zhuǎn)经窖,這里已經(jīng)處理了。
工具類:
public static final String DOMAIN_WEIBO = "http://s.weibo.com";
public static boolean isListNotEmpty(List list) {
return list != null && list.size() > 0;
}
public static String getDateStr(long currentTime) {
Date time = new Date(currentTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(time);
}
后記
寫這個東西呢前面也說了梭灿,想做個熱搜的整合画侣,不過剛寫沒多少就沒有了熱度。最初是想爬取數(shù)據(jù)+百度搜索+爬取第一條圖片+推送這種思路push到手機上堡妒,然后就可以開開心心看熱搜了E渎摇(流產(chǎn)了,遂記于此,并沒有后續(xù))