赞
踩
1.已知账户,获取该账户下每天的发视频数据,同时获取一个视频连续30天的点赞数,分享数,评论数。
2.需求方确定在标题中附带来源和作者相关信息,从标题中提取该部分信息,作为原创和作者绩效考核。
1.在douyin开放平台中注册,并绑定该抖音账户,获取APPID和APP_SECRET。
该接口只适用于抖音获取授权临时票据(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); } }
该接口用于获取用户授权第三方接口调用的凭证 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 "授权失败,请联系管理人员"; }
/** * 刷新 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操作 }
/** * 该接口用于刷新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 给管理员发一个短信需要他认证。 } }
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(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。