dy对接接口获取数据

1.需求方:

1.已知账户,获取该账户下每天的发视频数据,同时获取一个视频连续30天的点赞数,分享数,评论数。
2.需求方确定在标题中附带来源和作者相关信息,从标题中提取该部分信息,作为原创和作者绩效考核。

2.实现方:

1.在douyin开放平台中注册,并绑定该抖音账户,获取APPID和APP_SECRET。

3.实现过程:

3.1 授权步骤一 通过用户授权获取授权码code

dy对接接口获取数据_第1张图片

该接口只适用于抖音获取授权临时票据(code)。

注意:

抖音的 OAuth API 以https://open.douyin.com/开头。 该 URL 不是用来请求的,
需要展示给用户用于扫码,在抖音 APP 支持端内唤醒的版本内打开的话会弹出客户端原生授权页面。 获取的 code
可以用来调用 oauth/access_token/ 换取用户 acccess_token。
若需要授权多个 scope 需要把多个 scope 使用英文 “,” 拼接,例如 scope1,scope2,scope3 。
使用本接口前提:

首先你需要去官网申请,使你的应用可以使用特定的 Scope,具体需要哪些 Scope,请查看各接口定义。 其次你需要在本 URL 的
scope 字段中填上用户需要授权给你的 Scope。 用户授权通过后,你才可以调用相应的接口。

       /**
     * 抖音授权
     *
     * @param request
     * @param response
     */
    @GetMapping("/videoConfig/douYin/getCode")
    @ResponseBody
    public R getDouYinCode(HttpServletRequest request, HttpServletResponse response) {
        VideoConfig dyInfo = videoConfigMapper.selectByType("dy");

        if (dyInfo == null) {
            logger.error("=============无抖音平台数据==============");
            throw new RuntimeException("无抖音平台数据");
        }
        // https://open.douyin.com/platform/oauth/connect/?client_key=xxxxxxx&response_type=code&scope=video.list&redirect_uri=https://baidu.com/login
        //需要用户开放的权限
        String scope = "trial.whitelist,video.list,renew_refresh_token";
        //回调地址 https://baidu.com/login 为临时回调地址的,正式上线要用线上的域名
        //VIDEO_AUTH_CALLBACK_URL 为网站的回调域名
        String redirect_uri = VIDEO_AUTH_CALLBACK_URL + "/douYin/authCallback";
        String requestUrl = "https://open.douyin.com/platform/oauth/connect/?client_key=" + dyInfo.getAppId()
                + "&response_type=code" + "&scope=" + scope + "&redirect_uri=" + redirect_uri;
        return R.ok().data("url", requestUrl);

    }
    }

3.2 授权步骤二 获取access_token

该接口用于获取用户授权第三方接口调用的凭证 access_token;该接口适用于抖音/头条授权。

注意:

抖音的 OAuth API 以https://open.douyin.com/开头。 头条的 OAuth API
以https://open.snssdk.com/开头。 西瓜的 OAuth API
以https://open-api.ixigua.com/开头。 access_token
为用户授权第三方接口调用的凭证,存储在客户端,可能会被窃取,泄漏后可能会发生用户隐私数据泄漏的风险,建议存储在服务端。 获取到
access_token 后授权临时票据 (code) 不要再授权刷新,否则会导致上一次获取的 code 过期。

上面/videoConfig/douYin/getCode请求将回调下面请求地址,并在request中返回code,然后请求获取token,并记录access_token,refresh_token ,expires_in,refresh_expires_in,open_id

 @GetMapping("/douYin/authCallback")
    @ResponseBody
    public String dyAuthCallback(HttpServletRequest request) throws ParseException {
        // 请求参数
        VideoConfig dyInfo = videoConfigMapper.selectByType("dy");
        //请求参数
        String code = request.getParameter("code");
//        String code = "xxxxxxxxxxxxxxxxxx";
        Map<String, String> map = new HashMap<>();
        map.put("client_key", dyInfo.getAppId());
        map.put("client_secret", dyInfo.getAppSecret());
        map.put("code", code);
        map.put("grant_type", "authorization_code");

        //请求地址
        String url = dyInfo.getGetTokenUrl();
        HttpRequest httpRequest = HttpRequest.get(url, map, Boolean.TRUE);
        String result = httpRequest.body();
        JSONObject jasonObject = JSONObject.parseObject(result);
        JSONObject data = (JSONObject) jasonObject.get("data");
        System.out.println("jasonObject = " + jasonObject);
        int errorCode = Integer.parseInt(data.get("error_code").toString());
        if (errorCode == 0) {
            //查询数据库里面的内容
            String open_id = data.get("open_id").toString();
            String access_token = data.get("access_token").toString();
            String refresh_token = data.get("refresh_token").toString();
            //记录token,将他的过期时间也记录下来,查询时过期,就去更新
            long expires_in = Long.parseLong(data.get("expires_in").toString());
            long refresh_expires_in = Long.parseLong(data.get("refresh_expires_in").toString());
            long currentTimeMillis = System.currentTimeMillis();
            long tokenExpiresInLong = currentTimeMillis + expires_in * 1000;
            long refreshExpiresInLong = currentTimeMillis + refresh_expires_in * 1000;
            String tokenExpiresIn = TimeUtil.transferLongToDate("yyyy-MM-dd HH:mm:ss", tokenExpiresInLong);
            String refreshExpiresIn = TimeUtil.transferLongToDate("yyyy-MM-dd HH:mm:ss", refreshExpiresInLong);
            dyInfo.setOpenId(open_id);
            dyInfo.setToken(access_token);
            dyInfo.setRefreshToken(refresh_token);
            dyInfo.setTokenExpiresIn(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(tokenExpiresIn));
            dyInfo.setRefreshExpiresIn(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(refreshExpiresIn));
            videoConfigMapper.updateByPrimaryKeySelective(dyInfo);
            logger.info("accessToken记录成功");
            return "授权成功,请关闭当前窗口!";
        }
        return "授权失败,请联系管理人员";
    }

3.3 授权步骤三 刷新access_token

 /**
     * 刷新 access_token 的有效期
     */
    private void refresh_token() throws ParseException {
        logger.info("=================抖音刷新 access_token 的有效期=================");
        VideoConfig dyInfo = videoConfigMapper.selectByType("dy");
        String client_key = dyInfo.getAppId();
        Map<String, String> map = new HashMap<>();
        map.put("client_key", client_key);
        map.put("grant_type", "refresh_token");
        map.put("refresh_token", dyInfo.getRefreshToken());
        //请求地址
        String url = dyInfo.getGetRefreshTokenUrl();
        HttpRequest httpRequest = HttpRequest.post(url, map, Boolean.TRUE).header("Content-Type", "multipart/form-data");
        String result = httpRequest.body();
        JSONObject jasonObject = JSONObject.parseObject(result);
        JSONObject data = (JSONObject) jasonObject.get("data");
        logger.info("=================抖音刷新刷新access_token 的有效期:{}=================", jasonObject);

        String errorCode = data.get("error_code").toString();
        if (errorCode.equals("10008") || errorCode.equals("2190008")) {
            //这表示access_token过期 需要重新获取refresh_token 后会获取一个新的 access_token 以及新的超时时间。
            //说明要刷新重新获取refresh_token
            logger.info("=================抖音刷新刷新重新获取refresh_token=================");
            renew_refresh_token();
        } else {
            //未过期,提取并记录刷新后的access_token
            if (errorCode.equals("0")) {
                logger.info("=================抖音 未过期,提取并记录刷新后的access_token=================");
                String open_id = data.get("open_id").toString();
                String access_token = data.get("access_token").toString();
                //记录token,将他的过期时间也记录下来,查询时过期,就去更新
                long expires_in = Long.parseLong(data.get("expires_in").toString());
                long refresh_expires_in = Long.parseLong(data.get("refresh_expires_in").toString());
                long currentTimeMillis = System.currentTimeMillis();

                long tokenExpiresInLong = currentTimeMillis + expires_in * 1000;
                long refreshExpiresInLong = currentTimeMillis + refresh_expires_in * 1000;

                String tokenExpiresIn = TimeUtil.transferLongToDate("yyyy-MM-dd HH:mm:ss", tokenExpiresInLong);
                String refreshExpiresIn = TimeUtil.transferLongToDate("yyyy-MM-dd HH:mm:ss", refreshExpiresInLong);
                dyInfo.setOpenId(open_id);
                dyInfo.setToken(access_token);
                dyInfo.setRefreshToken(dyInfo.getRefreshToken());
                dyInfo.setTokenExpiresIn(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(tokenExpiresIn));
                dyInfo.setRefreshExpiresIn(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(refreshExpiresIn));
                videoConfigMapper.updateByPrimaryKeySelective(dyInfo);
                logger.info("accessToken记录成功");

            }
        }
        // 提取并记录刷新后的access_token 获取成功后再执行一次获取视频列表数据
        // 如果access_token刷新后则进行renew_refresh_token操作
    }

3.4 授权步骤四 刷新refresh_token

 /**
     * 该接口用于刷新refresh_token的有效期
     */
    private void renew_refresh_token() throws ParseException {
        VideoConfig dyInfo = videoConfigMapper.selectByType("dy");
        String client_key = dyInfo.getAppId();
        Map<String, String> map = new HashMap<>();
        map.put("client_key", client_key);
        map.put("refresh_token", dyInfo.getRefreshToken());
        //请求地址
        String url = dyInfo.getGetRenewRefreshTokenUrl();
        HttpRequest httpRequest = HttpRequest.post(url, map, Boolean.TRUE).header("Content-Type", "multipart/form-data");
        String result = httpRequest.body();
        logger.info("=================刷新refresh_token的有效期,数据获取result:{}=================", result);

        JSONObject jasonObject = JSONObject.parseObject(result);
        JSONObject data = (JSONObject) jasonObject.get("data");
        logger.info("=================刷新refresh_token的有效期,数据获取jasonObject:{}=================", jasonObject);

        String errorCode = data.get("error_code").toString();
        //未过期,提取并记录刷新后的access_token
        if (errorCode.equals("0")) {
            //过期时间 30天
            long expires_in = Long.parseLong(data.get("expires_in").toString());
            String refresh_token = data.get("refresh_token").toString();

            long currentTimeMillis = System.currentTimeMillis();
            System.out.println("currentTimeMillis = " + currentTimeMillis);
            long refreshExpiresInLong = currentTimeMillis + expires_in * 1000;
            String refreshExpiresIn = TimeUtil.transferLongToDate("yyyy-MM-dd HH:mm:ss", refreshExpiresInLong);

            dyInfo.setRefreshToken(refresh_token);
            dyInfo.setRefreshExpiresIn(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(refreshExpiresIn));
            videoConfigMapper.updateByPrimaryKeySelective(dyInfo);
            logger.info("刷新refresh_token记录成功");
        }else{
			//TODO 给管理员发一个短信需要他认证。
		}
    }

3.5 获取视频列表

dy对接接口获取数据_第2张图片

1.执行一个定时器任务,进行数据抓取,并记录数据库中。
2.抖音每次只支持十条数据返回,每次不能全部抓取,分批次读取数据,找到30天内的数据,30天内的数据是需要更新总点击数和总分享数等。
3.列表是有规律的,除指定的几个其余是按照当前时间逆序排序的。
作者可能有多个人,因此每个数据插入前要进行人员匹配哦

 /**
     * 获取视频列表并插入数据库    每天凌晨2点30分抓取
     */
    @Scheduled(cron = "${dy.task.cron}")
    public void getVideoList() throws ParseException {
        /**
         * open_id = 
         * access_token = 
         */
        VideoConfig dyInfo = videoConfigMapper.selectByType("dy");

        if (dyInfo == null) {
            throw new RuntimeException("无数据抖音平台数据");
        }
		//检查是否需要刷新access_token
        if (dyInfo.getTokenExpiresIn().getTime() < System.currentTimeMillis()) {
            //为空则说明需要去刷新 获取老版本然后更新
            refresh_token();
            //刷新后再次获取最新的access_token相关信息
            dyInfo = videoConfigMapper.selectByType("dy");
        }

        //获取列表
        String has_more = null;
        String cursor = "0";
        int count = 0;
        JSONArray jsonArray = new JSONArray();
        do {

            JSONObject list10 = getList10(dyInfo, cursor);
            if (list10 == null) return;
            has_more = list10.get("has_more").toString();
            cursor = list10.get("cursor").toString();
            JSONArray list = (JSONArray) list10.get("list");
            for (int i = 0; i < list.size(); i++) {
                JSONObject json = (JSONObject) list.get(i);
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                String date = json.get("create_time").toString();
                String createTime = sdf.format(new Date(Long.parseLong(String.valueOf(Long.parseLong(date) * 1000))));    //时间戳转化成时间

                boolean flag = TimeUtil.isPastNDays(createTime, 30);
                if (!flag) {
                    //不在该时间范围就移除数据.
                    list.remove(i);
                    System.out.println("移除时间为:" + TimeUtil.TimeStampToTime(date));
                    System.out.println("移除的标题为:" + json.get("title").toString());
                    //动态调整结束请求页数,当从第二页开始,如果有连续移除时,说明后面的页数据已经不是要取的了.
                    if (count >= 2) {
                        has_more = "false";
                    }
                }
            }
            jsonArray.addAll(list);

            //标记页数
            count++;

        } while (has_more.equals("true"));

        logger.info("本次翻页{}次,共采集到有效数据{}条", count, jsonArray.size());
        dataDeal(jsonArray);
        logger.info("=================抖音数据采集结束=================");
    }
 //数据处理过程
    private void dataDeal(JSONArray jsonArray) throws ParseException {
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            //视频创建时间戳
            String date = jsonObject.get("create_time").toString();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String createTime = sdf.format(new Date(Long.parseLong(String.valueOf(Long.parseLong(date) * 1000))));    //时间戳转化成时间
            //网页链接
            String share_url = jsonObject.get("share_url").toString();
            //标题
            String title = jsonObject.get("title").toString();

            String layout = "";
            String authors = "";
            String editor = "";
            String editor2 = "";
            String editor3 = "";
            String editor4 = "";

            if (title.indexOf("来源:") > 0||title.indexOf("来源:") > 0) { //判断是否有这个
                String str1 = "";
                if(title.indexOf("来源:")>0){
                    str1 = title.substring(0, title.indexOf("来源:"));
                }else{
                    str1 = title.substring(0, title.indexOf("来源:"));
                }
                //获取作者相关信息,处理格式问题
                String contactInfo = title.substring(str1.length()).replace(" ", "").replace("\n", "");
                logger.info("=================抖音数据获取contactInfo:{}=================", contactInfo);
                Pattern from = Pattern.compile("来源:.+作者|编辑");
                Matcher fromM = from.matcher(contactInfo);

                while (fromM.find()) {
                    //获取作者信息然后去匹配字段
                    layout = contactInfo.substring(fromM.start(), fromM.end()).replace("来源:", "").replace("编辑", "");
                    if (layout.contains("xxxxx")) { //xxxx表示来来源后的一段文字
                        //自己的媒体有作者信息
                        // 以 编辑:开头,以 编辑: 结束的字符串
                        Pattern p = Pattern.compile("作者:.+编辑:");
                        Matcher m = p.matcher(contactInfo);
                        while (m.find()) {
                            //获取作者信息然后去匹配字段
                            authors = contactInfo.substring(m.start(), m.end()).replace("作者:", "").replace("编辑", "");
                            logger.info("作者:" + authors);
                        }
                    }
                }
                //获取编辑
                Pattern ed = Pattern.compile("编辑:.+责编");
                Matcher em = ed.matcher(contactInfo);
                while (em.find()) {
                    //获取作者信息然后去匹配字段
                    editor = contactInfo.substring(em.start(), em.end()).replace("编辑:", "").replace("责编", "");
                    logger.info("编辑:" + editor);
                }
                //获取责编
                Pattern ed2 = Pattern.compile("责编:.+编审");
                Matcher em2 = ed2.matcher(contactInfo);

                while (em2.find()) {
                    //获取作者信息然后去匹配字段
                    editor2 = contactInfo.substring(em2.start(), em2.end()).replace("责编:", "").replace("编审", "");
                    logger.info("责编:" + editor2);
                }
                //获取编审
                Pattern ed3 = Pattern.compile("编审:.+监制");
                Matcher em3 = ed3.matcher(contactInfo);

                while (em3.find()) {
                    //获取作者信息然后去匹配字段
                    editor3 = contactInfo.substring(em3.start(), em3.end()).replace("编审:", "").replace("监制", "");
                    logger.info("编审:" + editor3);
                }
                //获取监制
                Pattern ed4 = Pattern.compile("监制:.+");
                Matcher em4 = ed4.matcher(contactInfo);

                while (em4.find()) {
                    //获取监制信息然后去匹配字段
                    editor4 = contactInfo.substring(em4.start(), em4.end()).replace("监制:", "");
                    logger.info("监制:" + editor4);
                }
            }

            //统计数据
            JSONObject statistics = (JSONObject) jsonObject.get("statistics");            //分享数
            //播放数
            int play_count = (int) statistics.get("play_count");

            //点赞数
            int digg_count = (int) statistics.get("digg_count");

            //分享数
            int share_count = (int) statistics.get("share_count");

            //首先根据时间和标题看是否已经插入了
            title = title.replace("\n", "");
            Audit audi = auditMapper.selectByTitleAndTime(title, createTime);
            //如果已经入库则更新总点击和总分享量
            if (audi == null) {
                Audit audit = new Audit();
                audit.setCreateTime(createTime);
                audit.setMediaName("抖音");
                audit.setType("video");
                audit.setTitle(title);
                audit.setLayout(layout);
                audit.setLink(share_url);
                audit.setReadCount(play_count);
                audit.setClickTotal(digg_count);
                audit.setShareCount(share_count);
                audit.setReadTotal(play_count);
                audit.setClickTotal(digg_count);
                audit.setShareTotal(share_count);
                insertAudit(authors, editor, editor2, editor3, editor4, audit);

            } else {
                logger.info("================= 更新数据 =================");
                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
                String currentDate = df.format(new Date());
                int dayNum = TimeUtil.daysBetween(createTime, currentDate);

                //未审核并且时间再30天内
                if (audi.getIsAudit() < 2 && dayNum <= 30) {
                    audi.setReadTotal(audi.getReadTotal() + play_count);
                    audi.setClickTotal(audi.getClickTotal() + digg_count);
                    audi.setShareTotal(audi.getShareTotal() + share_count);
                    //更新
                    auditMapper.updateByPrimaryKeySelective(audi);
                    logger.info("更新一条数据成功,{},{}", audi.getTitle(), audi.getCreateTime());
                }


            }
        }
    }

    //获取数据
    private JSONObject getList10(VideoConfig dyInfo, String cursor) {
        //数据读取
        String open_id = dyInfo.getOpenId();
        String access_token = dyInfo.getToken();

        Map<String, String> map = new HashMap<>();
        map.put("open_id", open_id);
        map.put("cursor", cursor);
        map.put("count", "10");
        logger.info("=================抖音数据采集开始=================");
        //请求地址
        String url = dyInfo.getGetVideoListUrl();
        HttpRequest httpRequest = HttpRequest.get(url, map, Boolean.TRUE).header("access-token", access_token);
        String result = httpRequest.body();
        JSONObject jasonObject = JSONObject.parseObject(result);

        JSONObject data = (JSONObject) jasonObject.get("data");
        if (!data.get("error_code").toString().equals("0")) {
            logger.error("抖音数据获取失败:{}", jasonObject);
            return null;
        }
        JSONArray jsonArray = (JSONArray) data.get("list");
        logger.info("=================抖音数据获取,接口响应数据:{}=================", jasonObject);
        logger.info("数据条数为:{}", jsonArray.size());
        return data;
    }

	//开启一个事务,要么都成功插入数据库,要么都失败会滚
    @Transactional(rollbackFor = Exception.class)
    public void insertAudit(String authors, String editor, String editor2, String editor3, String editor4, Audit audit) {
        try {
            auditMapper.insertSelective(audit);
            logger.info("新加一条数据成功,{},{}", audit.getTitle(), audit.getCreateTime());
            //执行插入作者信息
            authorDoUtil.insertAuthorInfoPlus(audit.getAuditId(), authors, AuthorType.AUTHOR);
            //执行编辑作者信息
            authorDoUtil.insertAuthorInfoPlus(audit.getAuditId(), editor, AuthorType.EDITOR);
            //执行插入责编信息
            authorDoUtil.insertAuthorInfoPlus(audit.getAuditId(), editor2, AuthorType.EX_EDITOR);
            //执行插入编审信息
            authorDoUtil.insertAuthorInfoPlus(audit.getAuditId(), editor3, AuthorType.EDITORIAL);
            //执行插入监制信息
            authorDoUtil.insertAuthorInfoPlus(audit.getAuditId(), editor4, AuthorType.SUPERVISOR);

        } catch (Exception e) {
            logger.error("执行抖音数据采集失败,回滚成功!标题为:{},{},{}", audit.getTitle(), audit.getCreateTime(), e.getMessage());
            //手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),使得事务生效,出现异常回滚。
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        }
    }

你可能感兴趣的:(数据抓取,java,servlet,开发语言)