自己动手实现抖音高清无水印视频下载工具(2)

751 阅读4分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

书接上文,我们继续对上回说到的问题继续分析,本文目标针对url中的sec_uid参数进行分析。

1、sec_uid

思路及实现

调用接口是通过xhr异步调用的,那么一定会有前台js代码来生成或者通过其他接口来获取这个参数,那么我们可以在浏览器端先搜一下这个参数名称,看看都再哪里出现过,然后跟一下调用链,希望最终能找到源头。

首先,我们搜一下前端的代码,发现sec_uid只再一个名称为index.98ac6f5d.js文件中出现了几次,先挨个扫一眼,我们发现在1425和1474行的地方分别出现了以下2句代码:

url: '/web/api/v2/user/info/?sec_uid=' + config.sec_uid,

params.sec_uid = _utils2.default.getUrlParam(window.location.href, "sec_uid");

我们发现这个参数是从短链接跳转后的长连接里获取的,用这个参数调用了user/info接口,根据字面意思大概能知道这个接口是用户信息的接口

image.png 这里我们顺便看一下,怎么拿到这个长连接的url,这里用接口调试工具先试探看一下效果,我们可以看到,短链接是被302到具体地址了,那么我们后续可以通过HttpRequest来获取最终url以及携带的各项参数。

image.png

我们先来实现获取302地址的代码:

	public static String get302Location(String requestUrl) {
		String res = "";
		HttpURLConnection httpUrlConn = null;
		try {
			URL url = new URL(requestUrl);
			httpUrlConn = (HttpURLConnection) url.openConnection();
			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
			httpUrlConn.setRequestProperty("Accept", "text/plain");
			httpUrlConn.setRequestProperty("Content-Type", "application/json");
			httpUrlConn.setRequestMethod("POST");
			httpUrlConn.setInstanceFollowRedirects(false);//这里是重点,必须设置为false,时请求不进行重定向
			String location=httpUrlConn.getHeaderField("Location");
			httpUrlConn.disconnect();
			return location;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				httpUrlConn.disconnect();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return res;
	}

运行结果:

image.png

我们可以看到,这里已经成功拿到了302的目标地址,接下来,我们再写一个获取对应参数的方法。

	public void getLocationParam(){
		String url = "https://v.douyin.com/eCJaMPX";
		String location = JwtHttpUtil.get302Location(url);
		 try {
			URL urlStr = new URL(location);
			String urlParam=urlStr.getQuery();
			Map<String,String> params=new HashMap<String,String>();
			for(String paramStr:urlParam.split("&")){
				String paramName=paramStr.split("=")[0];
				String paramVal=paramStr.split("=")[1];
				params.put(paramName, paramVal);
			}
			String sec_uid="sec_uid的值="+params.get("sec_uid");
			System.out.println(sec_uid);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

运行结果如下:

image.png 接下来,我们可以实现从首页来获取用户信息的代码,主体代码如下:

package com.lottery.job;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.jeecgframework.jwt.util.JwtHttpUtil;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class DyTest implements Job{
	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {}

	public String getLocationParam(String homeUrl){
		String location = JwtHttpUtil.get302Location(homeUrl);
		 try {
			URL urlStr = new URL(location);
			String urlParam=urlStr.getQuery();
			Map<String,String> params=new HashMap<String,String>();
			for(String paramStr:urlParam.split("&")){
				String paramName=paramStr.split("=")[0];
				String paramVal=paramStr.split("=")[1];
				params.put(paramName, paramVal);
			}
			String sec_uid=params.get("sec_uid");
			System.out.println(sec_uid);
			return sec_uid;
		} catch (MalformedURLException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * 
	 * @param homeUrl 首页地址
	 * @param url 接口地址
	 */
	public void getUserInfo(String homeUrl,String url){
		String sec_uid=getLocationParam(homeUrl);
		String apiUrl=url+"?sec_uid="+sec_uid;
		System.out.println(apiUrl);
		String jsonString = JwtHttpUtil.httpRequest(apiUrl, "POST", null);
		System.out.println(jsonString);
	}
	public static void main(String[] args) throws JobExecutionException{
		DyTest d=new DyTest();
		d.getUserInfo("https://v.douyin.com/eCJaMPX/","https://www.iesdouyin.com/web/api/v2/user/info/");
	}
}

运行结果如下:

image.png 这里出现了一些小问题,接口对请求进行了拦截,没有看到预期的效果,但是我在浏览器中直接访问,是有返回结果的

image.png

初步判断,这里可能是对请求头中的user-agent做了校验,由于时间问题,这个问题待下期更文进行再进行解决。

文末附上JwtHttpUtil的源码,大家有空也可以调试一下,看看具体是哪里的问题。

package org.jeecgframework.jwt.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;

import org.apache.log4j.Logger;
import org.jeecgframework.core.util.LogUtil;

import com.alibaba.fastjson.JSONObject;

/**
 * JWT 客户端
 * @author qinfeng
 *
 */
public class JwtHttpUtil {
	private static Logger log = Logger.getLogger(JwtHttpUtil.class);

	/**
	 * 发起https请求并获取结果
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET、POST)
	 * @param outputStr
	 *            提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 */
	public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr, String sign) {
		JSONObject jsonObject = null;
		StringBuffer buffer = new StringBuffer();
		HttpURLConnection httpUrlConn = null;
		try {
			// 创建SSLContext对象,并使用我们指定的信任管理器初始化
			URL url = new URL(requestUrl);
			httpUrlConn = (HttpURLConnection) url.openConnection();
			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
			httpUrlConn.setRequestProperty("X-AUTH-TOKEN", sign);
			httpUrlConn.setRequestProperty("Accept", "*/*");
			httpUrlConn.setRequestProperty("Content-Type", "application/json");
			// 设置请求方式(GET/POST)
			httpUrlConn.setRequestMethod(requestMethod);
			if ("GET".equalsIgnoreCase(requestMethod))
				httpUrlConn.connect();

			// 当有数据需要提交时
			if (null != outputStr) {
				OutputStream outputStream = httpUrlConn.getOutputStream();
				// 注意编码格式,防止中文乱码
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			InputStream inputStream = httpUrlConn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			// 释放资源
			inputStream.close();
			inputStream = null;
			httpUrlConn.disconnect();
			log.debug(buffer.toString());
			jsonObject = JSONObject.parseObject(buffer.toString());
		} catch (ConnectException ce) {
			LogUtil.info("Weixin server connection timed out.");
		} catch (Exception e) {
			e.printStackTrace();
			org.jeecgframework.core.util.LogUtil.info("https request error:{}" + e.getMessage());
		} finally {
			try {
				httpUrlConn.disconnect();
			} catch (Exception e) {
				e.printStackTrace();
				org.jeecgframework.core.util.LogUtil.info("http close error:{}" + e.getMessage());
			}
		}
		return jsonObject;
	}
	
	
	/**
	 * 发起https请求并获取结果
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET、POST)
	 * @param outputStr
	 *            提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 */
	public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
		String res = "";
		StringBuffer buffer = new StringBuffer();
		HttpURLConnection httpUrlConn = null;
		try {
			// 创建SSLContext对象,并使用我们指定的信任管理器初始化
			URL url = new URL(requestUrl);
			httpUrlConn = (HttpURLConnection) url.openConnection();
			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
//			httpUrlConn.setRequestProperty("Accept", "text/plain");
//			 httpUrlConn.setRequestProperty("Content-Type", "application/json");
			// 设置请求方式(GET/POST)
			httpUrlConn.setRequestProperty("User-Agent","MSIE");  
			httpUrlConn.setRequestMethod(requestMethod);
			if ("GET".equalsIgnoreCase(requestMethod))
				httpUrlConn.connect();

			// 当有数据需要提交时
			if (null != outputStr) {
				OutputStream outputStream = httpUrlConn.getOutputStream();
				// 注意编码格式,防止中文乱码
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			InputStream inputStream = httpUrlConn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			// 释放资源
			inputStream.close();
			inputStream = null;
			httpUrlConn.disconnect();
			res = buffer.toString();
			log.debug(res);
		} catch (ConnectException ce) {
			LogUtil.info("Weixin server connection timed out.");
		} catch (Exception e) {
			e.printStackTrace();
			org.jeecgframework.core.util.LogUtil.info("https request error:{}" + e.getMessage());
		} finally {
			try {
				httpUrlConn.disconnect();
			} catch (Exception e) {
				e.printStackTrace();
				org.jeecgframework.core.util.LogUtil.info("http close error:{}" + e.getMessage());
			}
		}
		return res;
	}
	
	
	public static String get302Location(String requestUrl) {
		String res = "";
		HttpURLConnection httpUrlConn = null;
		try {
			URL url = new URL(requestUrl);
			httpUrlConn = (HttpURLConnection) url.openConnection();
			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);
			httpUrlConn.setRequestProperty("Accept", "text/plain");
			httpUrlConn.setRequestProperty("Content-Type", "application/json");
			httpUrlConn.setRequestMethod("POST");
			httpUrlConn.setInstanceFollowRedirects(false);//这里是重点,必须设置为false,时请求不进行重定向
			// 当有数据需要提交时
			String location=httpUrlConn.getHeaderField("Location");
			httpUrlConn.disconnect();
			return location;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				httpUrlConn.disconnect();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return res;
	}
}

欲知后事如何,且听下回分解。