基于Webmagic的爬取B站用户数据的爬虫

基于Webmagic的爬取B站用户数据的爬虫


github: https://github.com/Al-assad/Spider-bilibiliUser-active

数据示例样本:http://pan.baidu.com/s/1dFchDZj  验证码:b2fi


学校数据挖掘作业要弄一个聚类分析,我想不如到我大B站搞搞事情吧,于是开始研究B站用户数据的获取接口;

B站现在大概有1亿左右的有效用户,由于时间的限制,爬取全部的用户对于作业的期限是肯定来不及,于是我选择爬取活跃用户(高关注数和高被关注数用户),B站的用户主页如下:

基于Webmagic的爬取B站用户数据的爬虫_第1张图片



爬虫设计思路

打开F12调试器后,发现该页面是一个前端渲染页面,通过css或xpath无法获取到动态渲染的节点的数据,于是选择通过JsonPath获取json文件的数据,使用的时FrieFox自带的调试工具该页面的是主要用户数据接口为: http://space.bilibili.com/ajax/member/GetInfo,发送方式为POST,参数为  mid(用户ID);

这里推荐一个很不错的Web调试工具:Fiddler 4;

基于Webmagic的爬取B站用户数据的爬虫_第2张图片


结果我写得的前嗅程序发现,该接口的反爬虫机制为对IP的接入频率,上限大概为 150~200 次/分钟,解决方式大概有2种:

1.  减低爬虫爬取频率;

2.  使用IP代理池,更具请求频率调整更换IP频率;

出于公共道德考虑,我选择了第一种方式(其实是因为我之前爬取的公共IP池里面大多数IP已经失效,懒得再爬取,况且这也只是一个作业而已就随便应付了);


接下来是解决请求跳转,通过调试工具的抓包,发现用户的关注对象、被关注对象的数据接口为:http://space.bilibili.com/ajax/friend/GetAttentionList?mid=633003&page=1, http://space.bilibili.com/ajax/friend/GetFansList?mid=633003&page=1,请求方式为GET,参数mid为用户ID,page为页数;

在这两个数据接口B站的反爬机制为限制页面访问,客户端最多只能访问前5个page,也就是说page取值为1~5,每page返回的json有20个用户数据对象,也就是一个用户最多只能跳转200个用户(估计B站被爬怕了),这我没想到其他的解决方法,只能暂时这样;


解决了爬取逻辑后,接下来解决数据持久化的问题,由于之后要使用rapidminer进行数据挖掘,于是使用MySQL储存数据,也方便之后对数据进行筛选,ADO层引擎使用JDBC;


爬虫引擎选用黄亿华前辈写的 webmagic  ,Webmagic是一个小巧强大、支持多线程,可定制的垂直爬虫引擎,这里给大家安利一个;



后续拓展

以上的代码基于深度爬取的爬虫,其实不能保证爬取到所有用户的数据,针对爬取B站的所有用户数据,其实也很简单,B站的用户id排序是很规律的,从1~10500000(大概),增长步幅为1,大概是后台数据库储存MID字段使用了Auto_Increment设置,当然这其中有大概1/5的mid是空的,我觉得可能数据库的同步回滚问题造成的(据我所知B站用户客户端并没有注销用户的功能);

于是爬取所有用户的url请求逻辑就很明了了,对数据接口 http://space.bilibili.com/ajax/member/GetInfo,参数mid由0开始,不断自增后发送请求,返回404响应就跳过该请求,到达预计上限假设10500000后,连续一定步增mid的请求(如mid自增次数100)返回404响应就判断运行结束,这样就可以遍历到B站几乎所有的用户数据;

简单实现的PageProcessor如下:

public class BiliPageProcessor implements PageProcessor{

    //构建Site对象,指定请求头键值字段
    private Site site = Site.me()
            .setRetryTimes(3)
            .setTimeOut(30000)
            .setSleepTime(1500)     //跟据试验,http://space.bilibili.com/ajax/member/GetInfo接口有IP接入限制,估计是60s内上限150次
            .setCycleRetryTimes(3)
            .setUseGzip(true)
            .addHeader("Host","space.bilibili.com")
            .addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0")
            .addHeader("Accept","application/json, text/plain, */*")
            .addHeader("Accept-Language","zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
            .addHeader("Accept-Encoding","gzip, deflate, br")
            .addHeader("X-Requested-With","XMLHttpRequest")
            .addHeader("Content-Type","application/x-www-form-urlencoded")
            .addHeader("Referer","http://space.bilibili.com/10513807/");

    private static final long BEGIN_MID = 1;    //开始用户mid
    private static final long END_MID = 100300000;      //结束用户mid,(2017-04的估计注册用户数)

    private BiliUserAdo biliUserDao = new BiliUserAdo();   //持久化对象


    @Override
    public void process(Page page) {

        String pageRawText = page.getRawText();
        //跳过连接失败页
        if(new JsonPathSelector("$.status").select(pageRawText).equals("false"))
            page.setSkip(true);

        //使用jsonPath获取json中的有效数据,并装载入BiliUser对象
        BiliUser user = new BiliUser();

        user.setMid(Long.parseLong(new JsonPathSelector("$.data.mid").select(pageRawText)));
        user.setName(new JsonPathSelector("$.data.name").select(pageRawText));
        user.setSex(new JsonPathSelector("$.data.sex").select(pageRawText));
        user.setLevel(Integer.parseInt(new JsonPathSelector("$.data.level_info.current_level").select(pageRawText)));
        user.setSign(new JsonPathSelector("$.data.sign").select(pageRawText));
        user.setFaceUrl( new JsonPathSelector("$.data.face").select(pageRawText));
        user.setFriends(Integer.parseInt(new JsonPathSelector("$.data.friend").select(pageRawText)));
        user.setFans(Integer.parseInt(new JsonPathSelector("$.data.fans").select(pageRawText)));
        user.setPlayNum(Integer.parseInt(new JsonPathSelector("$.data.playNum").select(pageRawText)));
        user.setBirthday(new JsonPathSelector("$.data.birthday").select(pageRawText));
        user.setPlace(new JsonPathSelector("$.data.place").select(pageRawText));

        System.out.println("\n"+user);

        biliUserDao.saveUser(user);    //保存BiliUser对象到数据库

    }

    @Override
    public Site getSite() {
        return site;
    }


    //运行主方法
    public static void main(String[] args){

        Spider spider = Spider.create(new BiliPageProcessor());
        //添加请求对象序列
        long mid;
        for(mid = BEGIN_MID; mid < END_MID; mid++){

                //构造post请求数据组和url
                Map nameValuePair = new HashMap();
                NameValuePair[] values = new NameValuePair[1];
                values[0] = new BasicNameValuePair("mid", String.valueOf(mid));
                nameValuePair.put("nameValuePair", values);
                String url = "http://space.bilibili.com/ajax/member/GetInfo?mid="+mid;   //bilibili用户信息获取接口
                //构造Request请求对象
                Request request = new Request(url);
                request.setExtras(nameValuePair);
                request.setMethod(HttpConstant.Method.POST);
                //向Spider对象添加Request对象
                spider.addRequest(request);

        }

        spider.thread(2).run();  //启动60个线程



    }
}

Github:https://github.com/Al-assad/Spider-bilibiliuser-full

当然如果使用IP代理池后,URL请求不再是运行效率瓶颈,此时有很多个地方可以进行相应的优化;




你可能感兴趣的:(Java,爬虫)