本项目来自程序汪背后的私活小团队,开发了一个多平台分发视频项目,给粉丝分享一下解决方案和具体项目分开情况付款情况等等细节,希望给想接私活的朋友一些经验参考
程序汪10万接的多平台视频分发项目,模拟人工发视频
视频版本 在 B站【我是程序汪】
目录
-
一、项目构成
-
二、开发人员
-
三、项目背景
-
四、APP端
-
五、后端系统
项目构成
- 系统基本构成:uniapp(app)+ java后台+selenium(蛇尼泥嗯)免费的分布式的自动化测试工具
selenium
Selenium 是一套 Web网站 的程序自动化操作 解决方案。
通过它,我们可以写出自动化程序,像真人一样在浏览器里操作web界面。比如点击界面按钮,在文本框中输入文字,在B站上发布视频 等操作。
而且还能从web界面获取信息。比如获取 12306火车、汽车票务信息,某招聘网站职位信息,财经网站股票价格信息 等等,然后用程序进行分析处理。
安装命
令
开发人员以及费用情况
-
开发周期60天
-
开发人数 2人 1前1后
-
工作量比例 20%前端 10%设计 70%后端
-
整体费用是10万(不包含维护费,一期的费用哦)
-
走的正规公司合同
-
云服务器3台 配置 8核16G(根据情况可增加)
-
维护费用:项目总款的10% 一年
-
付款方式 5+4+1 (预付+验收+尾款)
项目背景
现在自媒体视频平台非常火爆,某营销公司为了推广自己的产品,当然后期也可以自己卖会员,需要在多个视频平台一键分发(抖音,B站,今日头条,小红书,快手等等 十多个平台),这样就不用人工一个个平台登陆去发视频了,效率飞速提高,目前我们集成了16个视频平台,后期可能会继续增加
其中关键流程
APP端
本APP是用uniapp开发的,这种外包小项目肯定要选择性价比高的开发方式(程序汪知道原生的好,但开发成本也高哦),利用uniapp可以让多端开发成本降到最低
优点
uni-app是一套可以适用多端的开源框架,一套代码可以同时生成ios,Android,H5,微信小程序,支付宝小程序,百度小程序等。
缺点
uni-app问世的时间还比较短,有很多地方还不是完善,坑很多,如果不喜欢爬坑的朋友那就少用uni-app吧
B站的核心代码
由于是商业代码,所以放出来的是比较老的版本,最新版本不敢乱发啊
核心思路基本差不多,最新版本单机变集群 还使用到了线程池等性能手段,来提高并发量
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
java
package com.video.browserService;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import com.video.ThreadPool.task.workTask;
import com.video.bean.Task;
import com.video.constants.sys_constants;
import com.video.os.WindowsUtils;
import com.video.util.Base64Util;
import com.video.util.WebDriverUtil;
import com.video.util.codeUtil;
import ch.qos.logback.core.net.SyslogOutputStream;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import javax.imageio.ImageIO;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
public class bibi_browser extends workTask{
private WebDriver webDriver;
private String username="";
private String password="";
private String path="";
private String content="";
private Task task;
public bibi_browser(Task task,String username,
String password,
String path,
String content){
this.task=task;
this.username=username;
this.password=password;
this.path=path;
this.content=content;
}
@Override
public void excute() throws IOException {
try{
setSerid(task.getSerid());
webDriver=WebDriverUtil.getInstance(getSerid()).getDriver();
task.setTask_status(1);
Map<String,String> result=moblieLogin(username, password);
if(result!=null
&&result.get("result")!=null
&&result.get("result").toString().equals("success")){
result=UploadVideo(path,content);
task.setResult(result);
}else{
task.setResult(result);
}
task.setTask_status(3);
}catch(Exception e){
Map<String,String> result=new HashMap<String,String>();
result.put("result", "error");
result.put("msg", "上传视频异常");
task.setResult(result);
task.setTask_status(3);
}
sys_constants.taskMap.put(task.getSessionId(), task);
}
@Override
public String info() {
// TODO Auto-generated method stub
return null;
}
public Map<String,String> moblieLogin(String username,String password){
//webDriver.manage().deleteAllCookies();
Map<String,String> resultMap=new HashMap<String,String>();
try{
webDriver.get("https://www.bilibili.com/");
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10,
1,
By.xpath("//img[contains(@class,'bili-avatar-img bili-avatar-face bili-avatar-img-radius')]"));
WebElement head_img=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//img[contains(@class,'bili-avatar-img bili-avatar-face bili-avatar-img-radius')]"));
if(head_img!=null){
resultMap.put("result", "success");
resultMap.put("msg", "登录成功");
return resultMap;
}
WebElement login_btn=WebDriverUtil
.getInstance(getSerid()).findElement(
By.xpath("//*[@id='i_cecream']/div[1]/div[1]/ul[2]/li[8]/li/a/div/span"));
if(login_btn!=null){
login_btn.click();
}
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10,
1,
By.xpath("//div[@id='geetest-wrap']"));
*[@id="layout"]/div/div[2]/p[1]/a
WebElement sms_btn=WebDriverUtil
.getInstance(getSerid()).findElement(
By.xpath("//*[@id='geetest-wrap']/div/div[1]/span[2]"));
if(sms_btn!=null){
sms_btn.click();
}
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10,
1,
By.xpath("//a[contains(@class,'btn btn-login')]"));
//输入手机号码
WebElement userinput =WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//input[@name='tel']"));
if(userinput!=null){
userinput.sendKeys(username);
}
//点击发送短信按钮
WebElement sendMobile =WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//button[contains(@class,'el-button captcha-buttom el-button--primary')]"));
if(sendMobile!=null){
sendMobile.click();
try{
Thread.currentThread().sleep(2000);
WebElement backimg=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//img[@class='geetest_item_img']"));
WebElement clickimg=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[@class='geetest_tip_img']"));
if(backimg!=null){
String baseimg="";
if(clickimg!=null){
File src =clickimg.getScreenshotAs(OutputType.FILE);
baseimg=WebDriverUtil
.getInstance(getSerid())
.captureElement(src, clickimg);
}
File backsrc =backimg.getScreenshotAs(OutputType.FILE);
String back_img=WebDriverUtil
.getInstance(getSerid())
.captureElement(backsrc, backimg);
//String back_img=Base64Util.imgBase64(backimg.getAttribute("src"));
String result=codeUtil.loadCodeResult("27",back_img,null, null, baseimg);
if(result!=null&&!result.trim().equals("")){
String []xys=result.split("\\|");
if(xys!=null&&xys.length>0){
WebDriverUtil
.getInstance(getSerid()).moveAndClick(backimg,xys);
}
WebElement que_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//a[@class='geetest_commit']"));
if(que_btn!=null){
que_btn.click();
}
}else{
resultMap.put("result", "error");
resultMap.put("msg", "验证码识别错误,登录失败");
return resultMap;
}
}
}catch(Exception e){
e.printStackTrace();
}
}
//输入短信验证码
WebElement pwdinput =WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//input[contains(@class,'el-input__inner') and contains(@type,'text') and contains(@placeholder,'请输入短信验证码')]"));
if(pwdinput!=null){
String mobilemsg="";
long time=new Date().getTime();
task.setTask_status(2);
while(sys_constants.mobilemsg.get(task.getSessionId())==null
&&(new Date().getTime()-time<5*60*1000)){
Thread.currentThread().sleep(5000);
}
mobilemsg=sys_constants.mobilemsg.get(task.getSessionId());
if(mobilemsg!=null&&!mobilemsg.trim().equals("")){
pwdinput.sendKeys(mobilemsg);
}else{
resultMap.put("result", "error");
resultMap.put("msg", "请输入短信验证码,登录失败");
return resultMap;
}
}
WebElement submit_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//a[contains(@class,'btn btn-login') and contains(string(), '登录')]"));
if(submit_btn!=null){
submit_btn.click();
//error-- 为保证账号安全,请使用手机验证码登录
Thread.currentThread().sleep(1000);
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10,
1,
By.xpath("//a[@class='avatar el-popover__reference']"));
WebElement img_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//a[@class='avatar el-popover__reference']"));
if(img_btn!=null){
resultMap.put("result", "success");
resultMap.put("msg", "登录成功");
}else{
resultMap.put("result", "error");
resultMap.put("msg", "登录失败");
return resultMap;
}
}
}catch(Exception e){
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("msg", "登录异常,请求页面异常");
}
return resultMap;
}
/**
* 浏览器登录B站
* @return
*/
public Map<String,String> login(String username,String password){
//webDriver.manage().deleteAllCookies();
Map<String,String> resultMap=new HashMap<String,String>();
return resultMap;
}
public Map<String,String> UploadVideo(String path,String content){
Map<String,String> resultMap=new HashMap<String,String>();
try{
webDriver.get("https://member.bilibili.com/platform/upload/video/frame");
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
2,
1,
By.xpath("//div[@class='guide_left__wrap']/div[@class='tips_wrap']/img[@class='jump']"));
WebElement guide_left_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[@class='guide_left__wrap']/div[@class='tips_wrap']/img[@class='jump']"));
if(guide_left_btn!=null&&guide_left_btn.isDisplayed()){
guide_left_btn.click();
}
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
2,
1,
By.xpath("//div[@class='guide_right__wrap']/div[@class='tips_wrap']/img[@class='jump']"));
WebElement guide_right_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[@class='guide_right__wrap']/div[@class='tips_wrap']/img[@class='jump']"));
if(guide_right_btn!=null&&guide_right_btn.isDisplayed()){
guide_right_btn.click();
}
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10,
1,
By.id("video-up"));
WebDriverUtil
.getInstance(getSerid())
.getDriver()
.switchTo()
.frame("videoUpload");
((JavascriptExecutor)WebDriverUtil
.getInstance(getSerid()).getDriver()).executeScript("document.getElementsByTagName('input').style='display:block';");
WebElement upload_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//*[@id='video-up-app']/div/div[2]/div/div[1]/div/div/input"));
if(upload_btn!=null){
//upload_btn.setAttribute("value", "D:\\CRM生成代码\\VID_20211113_111758.mp4");
upload_btn.sendKeys(path);
// List<String> imglist=videoImage(path, 960, 600);
// WebElement upload_img_btn=WebDriverUtil
// .getInstance(getSerid())
// .findElement(By.xpath("//input[@type='file']"));
// for(String img:imglist){
// upload_img_btn.sendKeys(img);
// Thread.currentThread().sleep(5000);
// }
WebDriverUtil
.getInstance(getSerid()).WebDriverWaitElement(
10*60,
5,
By.xpath("//span[contains(@class, 'success') and contains(string(),'上传完成')]"));
WebElement finish_text=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//span[contains(@class, 'success') and contains(string(),'上传完成')]"));
if(finish_text.isDisplayed()){//上传完成
Thread.currentThread().sleep(1000);//等待5秒加载完
WebElement tag_text=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[@class='tag-list']/div[2]"));
if(tag_text!=null){
tag_text.click();
}
WebElement title_text=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//input[contains(@class, 'input-val') and contains(@placeholder, '请输入稿件标题')]"));
if(title_text!=null){
title_text.sendKeys(content);
}
WebElement desc_text=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[contains(@class, 'ql-editor ql-blank') and contains(@placeholder, '填写更全面的相关信息,让更多的人能找到你的视频吧')]"));
if(desc_text!=null){
desc_text.sendKeys(content);
}
WebElement submit_btn=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//span[contains(@class, 'submit-add') and contains(string(),'立即投稿')]"));
if(submit_btn!=null){
Thread.currentThread().sleep(1000);
submit_btn.click();
try{
WebElement success_text=WebDriverUtil
.getInstance(getSerid())
.findElement(By.xpath("//div[contains(@class, 'step-des') and contains(string(), '稿件投递成功')]"));
if(success_text!=null){
resultMap.put("result", "success");
resultMap.put("msg", "视频上传成功");
}
}catch(Exception e){
resultMap.put("result", "success");
resultMap.put("msg", "视频上传成功");
}
}
}
}
}catch(Exception e){
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("msg", "视频上传异常,检测是否视频文件");
}
return resultMap;
}
/**
* 截取视频第六帧的图片
*
* @param filePath 视频路径
* @param dir 文件存放的根目录
* @return 图片的相对路径 例:pic/1.png
*/
public List<String> videoImage(String filePath,int width,int height) throws Exception {
String pngPath = "";
// FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
File file=new File(filePath);
String dir=file.getParent()+"/";
System.out.println(dir);
FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(file);
List<String> list=new ArrayList<String>();
ff.start();
int ffLength = ff.getLengthInFrames();
Frame f;
int i = 0;
int m=0;
while (i < ffLength) {
f = ff.grabFrame();
//截取第6帧
if ((i > 5&&i%20==0) && (f.image != null)) {
//生成图片的相对路径 例如:pic/uuid.png
pngPath = getPngPath();
//执行截图并放入指定位置
System.out.println("存储图片 : " + (dir + pngPath));
doExecuteFrame(f, dir + pngPath,width,height);
list.add(dir + pngPath);
m++;
if(m>4)break;
}
i++;
}
ff.stop();
return list;
}
/**
* 生成图片的相对路径
*
* @return 图片的相对路径 例:pic/1.png
*/
private static String getPngPath() {
return getUUID() + ".png";
}
/**
* 生成唯一的uuid
*
* @return uuid
*/
private static String getUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 截取缩略图
*
* @param f Frame
* @param targerFilePath:封面图片存放路径
*/
private static void doExecuteFrame(Frame f, String targerFilePath,int width,int height) {
String imagemat = "png";
if (null == f || null == f.image) {
return;
}
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bi = converter.getBufferedImage(f);
BufferedImage thumbimg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
thumbimg.getGraphics().drawImage(bi.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
File output = new File(targerFilePath);
try {
ImageIO.write(thumbimg, imagemat, output);
} catch (IOException e) {
e.printStackTrace();
}
}
}
难点
- 适配各种视频平台
后端需要利用selenium模拟各种平台的视频上传操作,这块工作量是最大的,另外如果平台升级了,可能会影响开发的接口功能,需要跟着调整对应平台的功能接口,遇要验证还需要利用打码平台(机器验证码)
AI 训练集
下图是核心后端API,下面是利用Apifox的免费功能,输出的API文档
私活合作加VX:itwang007
参考文档:
某打码平台
免费的分布式的自动化测试工具
uniapp
APIFOX