Java爬虫——微博热搜

前言

自从写完关于Lifecycle的文章后就没有发现其他有兴趣的源码了,所以呢,我决定看看写写后台代码,尝试一波。经过大概一周的百度,SSM框架基本搭建完成。突发奇想,打算收集一下各种热搜。首先想到的那肯定是微博热搜了,so,我们来爬下微博热搜吧!

工具

Jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

之前使用过Jsoup来抓取公交车实时到站信息,并且自己做了个简单的公交车到站查询APP。关于Jsoup的使用后面会讲到。

分析网页结构

在抓取数据的时候,首先要做的就是分析这个网页的结构,哪里是我们需要抓取的,哪些数据是我们需要的。我们先看下微博热搜,可以通过浏览器的开发者模式显示Html代码:

热搜html

我们可以看到,右边那<tbody>里面正是我们需要抓取的数据,话不多说,上码吧!

代码实现

先吐槽一波,微博在加载热搜的时候并没有直接用html加载,而是通过了一段js加载,如下图:


抓包结果

通过fiddler抓包可以看到,这里通过加载js来加载的热搜,而且里面有一段json,这段json就是我们需要的热搜数据(截图不好截图,后面再看吧)。
先写代码:
先根据json创建实体类:

// 微博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
}

正式抓取网页:

    public static List<WeiboHotEntity> parseWeiboHot() {

        try {
            long currentTime = System.currentTimeMillis();
            // 通过jsoup将对应url转为document
            Document doc = Jsoup.parse(new URL("http://s.weibo.com/top/summary?cate=realtimehot"), 10000);
            // 获取script标签对应的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转成WeiboJsonEntity实体,出错肯定不是我们需要的
                        WeiboJsonEntity weiboJsonEneity = new Gson().fromJson(substring, WeiboJsonEntity.class);
                        System.out.println(substring);
                        // 热搜对应的部分
                        if (weiboJsonEneity.getPid().equals("pl_top_realtimehot")) {
                            // 开始解析数据
                            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\">关键词<\/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里面的内容,这里面内容非为两部分:

  1. 中国特色主义推荐位,第一条:


    推荐
  2. 普通热搜
    剩下的那些
    private static List<WeiboHotEntity> parseSearchTop(WeiboJsonEntity weiboJsonEneity, long currentTime) {
        List<WeiboHotEntity> weiboHotEntities = new ArrayList<>();
        // 再次解析,将html代码转成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;
    }

通过浏览器可以获取到其结构,我们需要提取action-type=tr_hover对应的元素

结构

之后,我们只需要根据需求获取td_01、td_02等里面的数据即可:

    private static WeiboHotEntity parseDetail(Element element, long currentTime) {
        WeiboHotEntity weiboHotEntity = new WeiboHotEntity();

        Elements td01 = element.getElementsByClass("td_01");

        // 排序
        if (isListNotEmpty(td01)) {
            // 获取热度排名,如果没有的话,则是推荐设为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;
    }

这里面都是比较简单的获取数据即可,比如获取排序,通过获取td_01里面的<em>标签的值即可。

排序

链接和名称以及热度值也是同理。这里说个比较有意思的,普通的热搜都是可以直接通过微博的域名+对应的链接跳转到相应的热搜,但是有一种标签,就是为的标签需要配置特殊的跳转,这里已经处理了。
工具类:

    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);
    }

后记

写这个东西呢前面也说了,想做个热搜的整合,不过刚写没多少就没有了热度。最初是想爬取数据+百度搜索+爬取第一条图片+推送这种思路push到手机上,然后就可以开开心心看热搜了!(流产了,遂记于此,并没有后续)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 成长这个话题吧,我本不想去想,不想去谈,因为我对这种比较大的话题有一种抵触,总觉着我看到的只是冰山一角,在我没有看...
    Hammwi阅读 245评论 0 0
  • 风雨同舟369阅读 302评论 0 0
  • 这雨下到连心都是湿的 蔓延了整个灵魂直到眼角 窗外是灰暗,是淋漓 天空成了悲伤的载体 冷是雨的伴侣 无孔不入让你铭...
    安非他阅读 375评论 6 7