Skip to content

Commit

Permalink
CLI download single video
Browse files Browse the repository at this point in the history
  • Loading branch information
drawrowfly committed Mar 30, 2020
1 parent a200f3a commit 58cea3c
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 11 deletions.
82 changes: 79 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ This is not an official API support and etc. This is just a scraper that is usin
- Download **unlimited** post metadata from the User, Hashtag, Trends, or Music-Id pages
- Save post metadata to the JSON/CSV files
- Download media **with and without the watermark** and save to the ZIP file
- Download single video without the watermark from the CLI
- Sign URL to make custom request to the TIkTok API
- Extract metadata from the User, Hashtag and Sginel Video pages
- Extract metadata from the User, Hashtag and Single Video pages
- **Save previous progress and download only new videos that weren't downloaded before**. This feature only works from the CLI and only if **download** flag is on.
- **View and manage previously downloaded posts history in the CLI**

Expand Down Expand Up @@ -153,6 +154,7 @@ Commands:
tiktok-scraper hashtag [id] Scrape videos from hashtag. Enter hashtag without #
tiktok-scraper trend Scrape posts from current trends
tiktok-scraper music [id] Scrape posts from a music id number
tiktok-scraper video [id] Download single video without the watermark
tiktok-scraper history View previous download history

Options:
Expand All @@ -164,7 +166,7 @@ Options:
--download, -d Download and archive all scraped videos to a ZIP file
[boolean] [default: false]
--filepath Directory to save all output files.
[default: "/Users/karl.wint/Documents/projects/javascript/tiktok-scraper"]
[default: "/Users/USER/Downloads"]
--filetype, --type, -t Type of output file where post information will be
saved. 'all' - save information about all posts to a
'json' and 'csv'
Expand All @@ -184,6 +186,7 @@ Examples:
tiktok-scraper trend -d -n 100
tiktok-scraper music MUSICID -n 100
tiktok-scraper music MUSIC_ID -d -n 50
tiktok-scraper video https://www.tiktok.com/@tiktok/video/6807491984882765062
tiktok-scraper history
tiktok-scraper history -r user:bob
tiktok-scraper history -r all
Expand Down Expand Up @@ -262,6 +265,16 @@ CSV path: /{CURRENT_PATH}/tend_1552945659138.csv
```

**Example 7:**
Download single video without the watermark from the CLI

```sh
tiktok-scraper video https://www.tiktok.com/@tiktok/video/6807491984882765062

Output:
Video was saved in: /Users/USER/Downloads/6807491984882765062.mp4
```

**Example 8:**
View previous download history

```sh
Expand Down Expand Up @@ -380,7 +393,9 @@ const rp = require('request-promise');
})();
```

**Promise will return current result**
### Result

##### user, hashtag, trend, music

```javascript
{
Expand All @@ -392,6 +407,67 @@ const rp = require('request-promise');
}
```

##### getUserProfileInfo

```javascript
{
secUid: 'MS4wLjABAAAAv7iSuuXDJGDvJkmH_vz1qkDZYo1apxgzaxdBSeIuPiM',
userId: '107955',
isSecret: false,
uniqueId: 'tiktok',
nickName: 'TikTok',
signature: 'Make Your Day',
covers: ['COVER_URL'],
coversMedium: ['COVER_URL'],
following: 490,
fans: 38040567,
heart: '211522962',
video: 93,
verified: true,
digg: 29,
}
```

##### getHashtagInfo

```javascript
{
challengeId: '4231',
challengeName: 'love',
text: '',
covers: [],
coversMedium: [],
posts: 66904972,
views: '194557706433',
isCommerce: false,
splitTitle: ''
}
```

##### getVideoMeta

```javascript
{
id: '6807491984882765062',
text: 'We’re kicking off the #happyathome live stream series today at 5pm PT!',
createTime: '1584992742',
authorId: '107955',
authorName: 'tiktok',
musicId: '6807487887634909957',
musicName: 'original sound',
musicAuthor: 'tiktok',
imageUrl: 'IMAGE_URL',
videoUrl: 'VIDEO_URL',
videoUrlNoWaterMark: 'VIDEO_URL_WITHOUT_THE_WATERMARK',
diggCount: 49292,
shareCount: 339,
playCount: 614678,
commentCount: 4023,
downloaded: false,
hashtags: [],
}
```

### Event

```javascript
Expand Down
20 changes: 15 additions & 5 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,26 @@ yargs
.example(`$0 trend -d -n 100`)
.example(`$0 hashtag HASHTAG_NAME -d -n 100`)
.example(`$0 music MUSIC_ID -d -n 50`)
.example(`$0 video https://www.tiktok.com/@tiktok/video/6807491984882765062`)
.example(`$0 history`)
.example(`$0 history -r user:bob`)
.example(`$0 history -r all`)
.command('user [id]', 'Scrape videos from username. Enter only username', {}, argv => {
.command('user [id]', 'Scrape videos from the User Feed. Enter only the username', {}, argv => {
startScraper(argv);
})
.command('hashtag [id]', 'Scrape videos from hashtag. Enter hashtag without #', {}, argv => {
.command('hashtag [id]', 'Scrape videos from the Hashtag Feed. Enter hashtag without the #', {}, argv => {
startScraper(argv);
})
.command('trend', 'Scrape posts from current trends', {}, argv => {
.command('trend', 'Scrape posts from the Trend Feed', {}, argv => {
startScraper(argv);
})
.command('music [id]', 'Scrape videos from music id. Enter only music id', {}, argv => {
.command('music [id]', 'Scrape videos from the Music Feed. Enter only the music id', {}, argv => {
startScraper(argv);
})
.command('history', 'View previous post download history', {}, argv => {
.command('video [id]', 'Download single video without the watermark', {}, argv => {
startScraper(argv);
})
.command('history', 'View previous download history', {}, argv => {
startScraper(argv);
})
.options({
Expand Down Expand Up @@ -137,6 +141,12 @@ yargs
}
}

if (argv._[0] === 'video') {
if (!/^https:\/\/www\.tiktok\.com\/@(\w+)\/video\/(\d+)$/.test(argv.id)) {
throw new Error('Enter a valid TikTok video URL. For example: https://www.tiktok.com/@tiktok/video/6807491984882765062');
}
}

return true;
})
.demandCommand()
Expand Down
2 changes: 1 addition & 1 deletion src/constant/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export = {
scrape: ['user', 'hashtag', 'trend', 'music', 'discover_user', 'discover_hashtag', 'discover_music', 'history'],
scrape: ['user', 'hashtag', 'trend', 'music', 'discover_user', 'discover_hashtag', 'discover_music', 'history', 'video'],
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0',
history: ['user', 'hashtag', 'trend', 'music'],
};
21 changes: 20 additions & 1 deletion src/core/Downloader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable no-param-reassign */
/* eslint-disable consistent-return */
import request from 'request';
import rp from 'request-promise';
import { Agent } from 'http';
import { createWriteStream } from 'fs';
import { createWriteStream, writeFile } from 'fs';
import { fromCallback } from 'bluebird';
import archiver from 'archiver';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { forEachLimit } from 'async';
Expand Down Expand Up @@ -135,4 +137,21 @@ export class Downloader {
);
});
}

public async downloadSingleVideo(post: PostCollector) {
const query = {
uri: post.videoUrlNoWaterMark,
headers: {
'user-agent': this.userAgent,
},
encoding: null,
};
try {
const result = await rp(query);

await fromCallback(cb => writeFile(`${post.id}.mp4`, result, cb));
} catch (error) {
throw error.message;
}
}
}
5 changes: 4 additions & 1 deletion src/core/TikTok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class TikTokScraper extends EventEmitter {

private idStore: string;

private Downloader: Downloader;
public Downloader: Downloader;

private tacValue: string = '';

Expand Down Expand Up @@ -791,6 +791,9 @@ export class TikTokScraper extends EventEmitter {
if (regex) {
const videoProps = JSON.parse(regex[1]);
let videoItem = {} as PostCollector;
if (videoProps.props.pageProps.statusCode) {
throw new Error(`Can't find video: ${this.input}`);
}
videoItem = {
id: videoProps.props.pageProps.videoData.itemInfos.id,
text: videoProps.props.pageProps.videoData.itemInfos.text,
Expand Down
11 changes: 11 additions & 0 deletions src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ export const getVideoMeta = async (input: string, options?: Options): Promise<Po
return result;
};

export const video = async (input: string, options?: Options): Promise<any> => {
const contructor: TikTokConstructor = { ...INIT_OPTIONS, ...{ type: 'video' as ScrapeType, input }, ...options };
const scraper = new TikTokScraper(contructor);

const result: PostCollector = await scraper.getVideoMeta();

await scraper.Downloader.downloadSingleVideo(result);

return { message: `Video was saved in: ${process.cwd()}/${result.id}.mp4` };
};

// eslint-disable-next-line no-unused-vars
export const history = async (input: string, options?: Options) => {
const store = (await fromCallback(cb => readFile(`${tmpdir()}/tiktok_history.json`, { encoding: 'utf-8' }, cb))) as string;
Expand Down

0 comments on commit 58cea3c

Please sign in to comment.