美文网首页Java 杂谈
Java爬虫——微博热搜

Java爬虫——微博热搜

作者: nick_young | 来源:发表于2018-07-17 20:40 被阅读105次

    前言

    自从写完关于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到手机上,然后就可以开开心心看热搜了!(流产了,遂记于此,并没有后续)

    相关文章

      网友评论

        本文标题:Java爬虫——微博热搜

        本文链接:https://www.haomeiwen.com/subject/kmigpftx.html