├── .gitignore ├── utils ├── config.js ├── message.js └── help.js ├── views ├── error.ejs └── index.ejs ├── public └── stylesheets │ └── style.css ├── routes ├── index.js ├── users.js └── douyin.js ├── README.md ├── package.json ├── app.js └── bin └── dywww /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /dist/ 3 | /public/video/* -------------------------------------------------------------------------------- /utils/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'ipAddress': 'http://localhost:3039' 3 | }; -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() 2 | 3 | router.get('/', async (ctx, next) => { 4 | ctx.state = { 5 | title: 'dy project' 6 | }; 7 | 8 | await ctx.render('index', {}); 9 | }) 10 | 11 | module.exports = router -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

EJS Welcome to <%= title %>

10 | 11 | 12 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() 2 | 3 | router.prefix('/users') 4 | 5 | router.get('/', function (ctx, next) { 6 | ctx.body = 'this is a users response!' 7 | }) 8 | 9 | router.get('/bar', function (ctx, next) { 10 | ctx.body = 'this is a users/bar response' 11 | }) 12 | 13 | module.exports = router -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 抖音去水印node后端代码 2 | 3 | ## 背景: 之前用了第三方接口很快就挂了,收费之后也没理会了很长一段时间;最近发现市面又出现了很多去水印功能的小程序,第三方接口按调用次数收费很多,问了几个老哥说给钱就告知原理,所以自己重新研究了一下; 4 | 5 | ## 获取无水印的视频原理步骤如下:(ps:记得要伪造移动端UA) 6 | 7 | ### 1.获取视频网页版下的item_ids,dytk两个参数; 8 | ### 2.调用抖音的接口https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${item_ids}&dytk=${dytk},获取视频原链接; 9 | ### 3.replace('playwm','play')获取无水印视频链接; 10 | 11 | ##### 如果大家觉得有帮助可以star一下,多谢各位老哥 12 | -------------------------------------------------------------------------------- /utils/message.js: -------------------------------------------------------------------------------- 1 | function Message(status, message, data) { 2 | var _this = this; 3 | 4 | _this.status = status ? status : undefined; 5 | _this.message = message ? message : undefined; 6 | _this.data = data ? data : undefined; 7 | 8 | /** 9 | * 设置状态码 10 | * @param status 状态码 11 | * @returns {Message} 12 | */ 13 | _this.setStatus = function (status) { 14 | _this.status = status; 15 | return _this; 16 | } 17 | 18 | /** 19 | * 设置消息 20 | * @param message 信息内容 21 | * @returns {Message} 22 | */ 23 | _this.setMessage = function (message) { 24 | _this.message = message; 25 | return _this; 26 | } 27 | 28 | /** 29 | * 设置数据 30 | * @param data 数据 31 | * @returns {Message} 32 | */ 33 | _this.setData = function (data) { 34 | _this.data = data; 35 | return _this; 36 | } 37 | } 38 | 39 | module.exports = Message; -------------------------------------------------------------------------------- /utils/help.js: -------------------------------------------------------------------------------- 1 | function getString(str, firstStr, secondStr) { 2 | if (str == "" || str == null || str == undefined) { // "",null,undefined 3 | return ""; 4 | } 5 | if (str.indexOf(firstStr) < 0) { 6 | return ""; 7 | } 8 | var subFirstStr = str.substring(str.indexOf(firstStr) + firstStr.length, str.length); 9 | var subSecondStr = subFirstStr.substring(0, subFirstStr.indexOf(secondStr)); 10 | return subSecondStr; 11 | } 12 | 13 | function getIPAddress() { 14 | var interfaces = require('os').networkInterfaces(); 15 | for (var devName in interfaces) { 16 | var iface = interfaces[devName]; 17 | for (var i = 0; i < iface.length; i++) { 18 | var alias = iface[i]; 19 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 20 | return alias.address; 21 | } 22 | } 23 | } 24 | } 25 | 26 | module.exports = { 27 | getString, 28 | getIPAddress 29 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monitor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "nodemon bin/dywww", 7 | "dev": "./node_modules/.bin/nodemon bin/dywww", 8 | "prd": "pm2 start bin/dywww", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.19.2", 13 | "axios-jsonp": "^1.0.4", 14 | "cheerio": "^1.0.0-rc.3", 15 | "dayjs": "^1.8.25", 16 | "debug": "^4.1.1", 17 | "dingtalk-robot-sender": "^1.2.0", 18 | "ejs": "~2.3.3", 19 | "iconv-lite": "^0.5.1", 20 | "jquery": "^3.5.0", 21 | "jsonp": "^0.2.1", 22 | "koa": "^2.7.0", 23 | "koa-bodyparser": "^4.2.1", 24 | "koa-convert": "^1.2.0", 25 | "koa-json": "^2.0.2", 26 | "koa-logger": "^3.2.0", 27 | "koa-onerror": "^4.1.0", 28 | "koa-router": "^7.4.0", 29 | "koa-static": "^5.0.0", 30 | "koa-views": "^6.2.0", 31 | "mysql": "^2.18.1", 32 | "superagent": "^5.2.2" 33 | }, 34 | "devDependencies": { 35 | "nodemon": "^1.19.1" 36 | } 37 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const app = new Koa() 3 | const views = require('koa-views') 4 | const json = require('koa-json') 5 | const onerror = require('koa-onerror') 6 | const bodyparser = require('koa-bodyparser') 7 | const logger = require('koa-logger') 8 | 9 | const index = require('./routes/index') 10 | const users = require('./routes/users') 11 | const douyin = require('./routes/douyin') 12 | 13 | // error handler 14 | onerror(app) 15 | 16 | // middlewares 17 | app.use( 18 | bodyparser({ 19 | enableTypes: ['json', 'form', 'text'] 20 | }) 21 | ) 22 | app.use(json()) 23 | app.use(logger()) 24 | app.use(require('koa-static')(__dirname + '/public')) 25 | 26 | app.use( 27 | views(__dirname + '/views', { 28 | extension: 'ejs' 29 | }) 30 | ) 31 | 32 | // logger 33 | app.use(async (ctx, next) => { 34 | const start = new Date() 35 | await next() 36 | const ms = new Date() - start 37 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) 38 | }) 39 | 40 | // routes 41 | app.use(index.routes(), index.allowedMethods()) 42 | app.use(users.routes(), users.allowedMethods()) 43 | app.use(douyin.routes(), douyin.allowedMethods()) 44 | 45 | // error-handling 46 | app.on('error', (err, ctx) => { 47 | console.error('server error', err, ctx) 48 | }) 49 | 50 | module.exports = app -------------------------------------------------------------------------------- /bin/dywww: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3039'); 16 | // app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app.callback()); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' ? 62 | 'Pipe ' + port : 63 | 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' ? 87 | 'pipe ' + addr : 88 | 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } -------------------------------------------------------------------------------- /routes/douyin.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() 2 | const axios = require('axios') 3 | const fs = require('fs') 4 | var path = require('path') 5 | const dayjs = require('dayjs') 6 | const cheerio = require('cheerio') 7 | const { 8 | getString, 9 | getIPAddress 10 | } = require('../utils/help') 11 | const { 12 | ipAddress 13 | } = require('../utils/config') 14 | 15 | let requestUrl = `https://v.douyin.com/KWuu31/` 16 | 17 | var renderPage = function (requestUrl) { 18 | return new Promise((resolve, reject) => { 19 | axios 20 | .get(requestUrl, { 21 | headers: { 22 | 'user-agent': ' Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 23 | } 24 | }) 25 | .then(function (response) { 26 | const $ = cheerio.load(response, { 27 | decodeEntities: false 28 | }) 29 | let item_ids = getString($.html(), 'itemId: "', '",') 30 | let dytk = getString($.html(), 'dytk: "', '" }') 31 | console.log('response', item_ids, dytk) 32 | resolve({ 33 | item_ids, 34 | dytk 35 | }) 36 | }) 37 | .catch(function (error) { 38 | console.log(error) 39 | reject(error) 40 | }) 41 | }) 42 | } 43 | 44 | var getDYVideoUrl = function ({ 45 | item_ids, 46 | dytk 47 | }) { 48 | return new Promise((resolve, reject) => { 49 | axios 50 | .get( 51 | `https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=${item_ids}&dytk=${dytk}`, { 52 | headers: { 53 | 'user-agent': ' Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 54 | } 55 | } 56 | ) 57 | .then(function (response) { 58 | // console.log("response", response.data) 59 | let { 60 | status_code, 61 | item_list 62 | } = response.data 63 | if (status_code === 0) { 64 | let url = item_list[0].video.play_addr.url_list[0].replace( 65 | 'playwm', 66 | 'play' 67 | ) 68 | console.log('url', url) 69 | resolve(url) 70 | } else { 71 | reject(status_code) 72 | } 73 | }) 74 | .catch(function (error) { 75 | console.log(error) 76 | reject(error) 77 | }) 78 | }) 79 | } 80 | 81 | var getCurrentVideo = function (url) { 82 | return new Promise((resolve, reject) => { 83 | axios 84 | .get(url, { 85 | headers: { 86 | 'user-agent': ' Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 87 | } 88 | }) 89 | .then(function (response) { 90 | console.log('response: getCurrentVideo', response.data) 91 | }) 92 | .catch(function (error) { 93 | console.log(error) 94 | reject(error) 95 | }) 96 | }) 97 | } 98 | async function downloadFile(url, filepath, name) { 99 | if (!fs.existsSync(filepath)) { 100 | fs.mkdirSync(filepath) 101 | } 102 | const mypath = path.resolve(filepath, name) 103 | const writer = fs.createWriteStream(mypath) 104 | const response = await axios({ 105 | url, 106 | method: 'GET', 107 | responseType: 'stream' 108 | }) 109 | response.data.pipe(writer) 110 | return new Promise((resolve, reject) => { 111 | writer.on('finish', resolve) 112 | writer.on('error', reject) 113 | resolve() 114 | }) 115 | } 116 | 117 | router.prefix('/douyin') 118 | 119 | var buildVideoFilesToUrl = function (url) { 120 | return new Promise((resolve, reject) => { 121 | renderPage(url) 122 | .then(data => { 123 | getDYVideoUrl(data) 124 | .then(currentUrl => { 125 | let fileName = `${dayjs().unix()}.mp4` 126 | downloadFile( 127 | currentUrl, 128 | path.join(__dirname, '../public/video'), 129 | fileName 130 | ) 131 | .then(() => { 132 | resolve(`${ipAddress}/video/${fileName}`) 133 | }) 134 | .catch(() => { 135 | reject('下载出错') 136 | }) 137 | }) 138 | .catch(() => { 139 | reject('获取无水印链接出错') 140 | }) 141 | }) 142 | .catch(() => { 143 | reject('爬虫页面出错') 144 | }) 145 | }) 146 | } 147 | 148 | router.post('/getCurrentUrl', async function (ctx, next) { 149 | let { 150 | url 151 | } = ctx.request.body 152 | await buildVideoFilesToUrl(url).then(cuurentUrl => { 153 | ctx.body = { 154 | status: true, 155 | result: { 156 | url: cuurentUrl 157 | }, 158 | msg: '转换成功' 159 | } 160 | }).catch((err) => { 161 | ctx.body = { 162 | status: false, 163 | result: { 164 | msg: err 165 | }, 166 | msg: '转换失败' 167 | } 168 | }) 169 | }) 170 | 171 | router.get('/getCurrentUrl', async function (ctx, next) { 172 | let { 173 | url 174 | } = ctx.query 175 | await buildVideoFilesToUrl(url).then(cuurentUrl => { 176 | ctx.body = { 177 | status: true, 178 | result: { 179 | url: cuurentUrl 180 | }, 181 | msg: '转换成功' 182 | } 183 | }).catch((err) => { 184 | ctx.body = { 185 | status: false, 186 | result: { 187 | msg: err 188 | }, 189 | msg: '转换失败' 190 | } 191 | }) 192 | }) 193 | 194 | module.exports = router --------------------------------------------------------------------------------