Java爬蟲——微博熱搜

前言

自從寫完關(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代碼:

熱搜html

我們可以看到,右邊那<tbody>里面正是我們需要抓取的數(shù)據(jù)狂丝,話不多說救斑,上碼吧童本!

代碼實現(xiàn)

先吐槽一波,微博在加載熱搜的時候并沒有直接用html加載脸候,而是通過了一段js加載穷娱,如下圖:


抓包結(jié)果

通過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)容非為兩部分:

  1. 中國特色主義推薦位薪寓,第一條:


    推薦
  2. 普通熱搜
    剩下的那些
    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)的元素

結(jié)構(gòu)

之后,我們只需要根據(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ù))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搬泥,一起剝皮案震驚了整個濱河市桑寨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佑钾,老刑警劉巖西疤,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異休溶,居然都是意外死亡,警方通過查閱死者的電腦和手機扰她,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門兽掰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徒役,你說我怎么就攤上這事孽尽。” “怎么了忧勿?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵杉女,是天一觀的道長。 經(jīng)常有香客問我鸳吸,道長熏挎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任晌砾,我火速辦了婚禮坎拐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘养匈。我一直安慰自己哼勇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布呕乎。 她就那樣靜靜地躺著积担,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猬仁。 梳的紋絲不亂的頭發(fā)上帝璧,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音逐虚,去河邊找鬼聋溜。 笑死,一個胖子當(dāng)著我的面吹牛叭爱,可吹牛的內(nèi)容都是我干的撮躁。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼买雾,長吁一口氣:“原來是場噩夢啊……” “哼把曼!你這毒婦竟也來了杨帽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤嗤军,失蹤者是張志新(化名)和其女友劉穎注盈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叙赚,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡老客,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了震叮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胧砰。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苇瓣,靈堂內(nèi)的尸體忽然破棺而出尉间,到底是詐尸還是另有隱情,我是刑警寧澤击罪,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布哲嘲,位于F島的核電站,受9級特大地震影響媳禁,放射性物質(zhì)發(fā)生泄漏眠副。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一损话、第九天 我趴在偏房一處隱蔽的房頂上張望侦啸。 院中可真熱鬧,春花似錦丧枪、人聲如沸光涂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忘闻。三九已至,卻和暖如春恋博,著一層夾襖步出監(jiān)牢的瞬間齐佳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工债沮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炼吴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓疫衩,卻偏偏與公主長得像硅蹦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 1童芹、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評論 3 119
  • 成長這個話題吧涮瞻,我本不想去想,不想去談假褪,因為我對這種比較大的話題有一種抵觸署咽,總覺著我看到的只是冰山一角,在我沒有看...
    Hammwi閱讀 247評論 0 0
  • 風(fēng)雨同舟369閱讀 304評論 0 0
  • 這雨下到連心都是濕的 蔓延了整個靈魂直到眼角 窗外是灰暗生音,是淋漓 天空成了悲傷的載體 冷是雨的伴侶 無孔不入讓你銘...
    安非他閱讀 378評論 6 7