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標簽對應(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>標簽的值即可。

排序

鏈接和名稱以及熱度值也是同理。這里說個比較有意思的,普通的熱搜都是可以直接通過微博的域名+對應(yīng)的鏈接跳轉(zhuǎn)到相應(yīng)的熱搜,但是有一種標簽,就是為的標簽需要配置特殊的跳轉(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到手機上,然后就可以開開心心看熱搜了?。鳟a(chǎn)了,遂記于此,并沒有后續(xù))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容