forked from Evil0ctal/Douyin_TikTok_Download_API
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscraper.py
672 lines (622 loc) · 35.3 KB
/
scraper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author: https://github.com/Evil0ctal/
# @Time: 2021/11/06
# @Update: 2023/03/08
# @Version: 3.3.0
# @Function:
# 核心代码,估值1块(๑•̀ㅂ•́)و✧
# 用于爬取Douyin/TikTok数据并以字典形式返回。
# input link, output dictionary.
import re
import os
import time
import execjs
import aiohttp
import platform
import asyncio
import traceback
import configparser
import urllib.parse
from typing import Union
from tenacity import *
class Scraper:
"""
简介/Introduction
Scraper.get_url(text: str) -> Union[str, None]
用于检索出文本中的链接并返回/Used to retrieve the link in the text and return it.
Scraper.convert_share_urls(self, url: str) -> Union[str, None]\n
用于转换分享链接为原始链接/Convert share links to original links
Scraper.get_douyin_video_id(self, original_url: str) -> Union[str, None]\n
用于获取抖音视频ID/Get Douyin video ID
Scraper.get_douyin_video_data(self, video_id: str) -> Union[dict, None]\n
用于获取抖音视频数据/Get Douyin video data
Scraper.get_douyin_live_video_data(self, original_url: str) -> Union[str, None]\n
用于获取抖音直播视频数据/Get Douyin live video data
Scraper.get_tiktok_video_id(self, original_url: str) -> Union[str, None]\n
用于获取TikTok视频ID/Get TikTok video ID
Scraper.get_tiktok_video_data(self, video_id: str) -> Union[dict, None]\n
用于获取TikTok视频数据/Get TikTok video data
Scraper.hybrid_parsing(self, video_url: str) -> dict\n
用于混合解析/ Hybrid parsing
Scraper.hybrid_parsing_minimal(data: dict) -> dict\n
用于混合解析最小化/Hybrid parsing minimal
"""
"""__________________________________________⬇️initialization(初始化)⬇️______________________________________"""
# 初始化/initialization
def __init__(self):
self.headers = {
'User-Agent': "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66"
}
self.douyin_api_headers = {
'accept-encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
'referer': 'https://www.douyin.com/',
'cookie': "s_v_web_id=verify_leytkxgn_kvO5kOmO_SdMs_4t1o_B5ml_BUqtWM1mP6BF;"
}
self.tiktok_api_headers = {
'User-Agent': 'com.ss.android.ugc.trill/494+Mozilla/5.0+(Linux;+Android+12;+2112123G+Build/SKQ1.211006.001;+wv)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Version/4.0+Chrome/107.0.5304.105+Mobile+Safari/537.36'
}
# 判断配置文件是否存在/Check if the configuration file exists
if os.path.exists('config.ini'):
self.config = configparser.ConfigParser()
self.config.read('config.ini', encoding='utf-8')
# 判断是否使用代理
if self.config['Scraper']['Proxy_switch'] == 'True':
# 判断是否区别协议选择代理
if self.config['Scraper']['Use_different_protocols'] == 'False':
self.proxies = {
'all': self.config['Scraper']['All']
}
else:
self.proxies = {
'http': self.config['Scraper']['Http_proxy'],
'https': self.config['Scraper']['Https_proxy'],
}
else:
self.proxies = None
# 配置文件不存在则不使用代理/If the configuration file does not exist, do not use the proxy
else:
self.proxies = None
# 针对Windows系统的异步事件规则/Asynchronous event rules for Windows systems
if platform.system() == 'Windows':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
"""__________________________________________⬇️utils(实用程序)⬇️______________________________________"""
# 检索字符串中的链接
@staticmethod
def get_url(text: str) -> Union[str, None]:
try:
# 从输入文字中提取索引链接存入列表/Extract index links from input text and store in list
url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', text)
# 判断是否有链接/Check if there is a link
if len(url) > 0:
return url[0]
except Exception as e:
print('Error in get_url:', e)
return None
# 转换链接/convert url
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def convert_share_urls(self, url: str) -> Union[str, None]:
"""
用于将分享链接(短链接)转换为原始链接/Convert share links (short links) to original links
:return: 原始链接/Original link
"""
# 检索字符串中的链接/Retrieve links from string
url = self.get_url(url)
# 判断是否有链接/Check if there is a link
if url is None:
print('无法检索到链接/Unable to retrieve link')
return None
# 判断是否为抖音分享链接/judge if it is a douyin share link
if 'douyin' in url:
"""
抖音视频链接类型(不全):
1. https://v.douyin.com/MuKhKn3/
2. https://www.douyin.com/video/7157519152863890719
3. https://www.iesdouyin.com/share/video/7157519152863890719/?region=CN&mid=7157519152863890719&u_code=ffe6jgjg&titleType=title×tamp=1600000000&utm_source=copy_link&utm_campaign=client_share&utm_medium=android&app=aweme&iid=123456789&share_id=123456789
抖音用户链接类型(不全):
1. https://www.douyin.com/user/MS4wLjABAAAAbLMPpOhVk441et7z7ECGcmGrK42KtoWOuR0_7pLZCcyFheA9__asY-kGfNAtYqXR?relation=0&vid=7157519152863890719
2. https://v.douyin.com/MuKoFP4/
抖音直播链接类型(不全):
1. https://live.douyin.com/88815422890
"""
if 'v.douyin' in url:
# 转换链接/convert url
# 例子/Example: https://v.douyin.com/rLyAJgf/8.74
url = re.compile(r'(https://v.douyin.com/)\w+', re.I).match(url).group()
print('正在通过抖音分享链接获取原始链接...')
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=self.headers, proxy=self.proxies, allow_redirects=False,
timeout=10) as response:
if response.status == 302:
url = response.headers['Location'].split('?')[0] if '?' in response.headers[
'Location'] else \
response.headers['Location']
print('获取原始链接成功, 原始链接为: {}'.format(url))
return url
except Exception as e:
print('获取原始链接失败!')
print(e)
# return None
raise e
else:
print('该链接为原始链接,无需转换,原始链接为: {}'.format(url))
return url
# 判断是否为TikTok分享链接/judge if it is a TikTok share link
elif 'tiktok' in url:
"""
TikTok视频链接类型(不全):
1. https://www.tiktok.com/@tiktok/video/6950000000000000000
2. https://www.tiktok.com/t/ZTRHcXS2C/
TikTok用户链接类型(不全):
1. https://www.tiktok.com/@tiktok
"""
if '@' in url:
print('该链接为原始链接,无需转换,原始链接为: {}'.format(url))
return url
else:
print('正在通过TikTok分享链接获取原始链接...')
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=self.headers, proxy=self.proxies, allow_redirects=False,
timeout=10) as response:
if response.status == 301:
url = response.headers['Location'].split('?')[0] if '?' in response.headers[
'Location'] else \
response.headers['Location']
print('获取原始链接成功, 原始链接为: {}'.format(url))
return url
except Exception as e:
print('获取原始链接失败!')
print(e)
return None
"""__________________________________________⬇️Douyin methods(抖音方法)⬇️______________________________________"""
"""
Credits: https://github.com/Johnserf-Seed
[中文]
感谢John为本项目提供了非常多的帮助
大家可以去他的仓库点个star :)
顺便打个广告, 如果需要更稳定、快速、长期维护的抖音/TikTok API, 或者需要更多的数据(APP端),
请移步: https://api.tikhub.io
[English]
Thanks to John for providing a lot of help to this project
You can go to his repository and give him a star :)
By the way, if you need a more stable, fast and long-term maintenance Douyin/TikTok API, or need more data (APP side),
Please go to: https://api.tikhub.io
"""
# 生成抖音X-Bogus签名/Generate Douyin X-Bogus signature
# 下面的代码不能保证稳定性,随时可能失效/ The code below cannot guarantee stability and may fail at any time
def generate_x_bogus_url(self, url: str) -> str:
"""
生成抖音X-Bogus签名
:param url: 视频链接
:return: 包含X-Bogus签名的URL
"""
# 调用JavaScript函数
query = urllib.parse.urlparse(url).query
xbogus = execjs.compile(open('./X-Bogus.js').read()).call('sign', query, self.headers['User-Agent'])
print('生成的X-Bogus签名为: {}'.format(xbogus))
new_url = url + "&X-Bogus=" + xbogus
return new_url
# 获取抖音视频ID/Get Douyin video ID
async def get_douyin_video_id(self, original_url: str) -> Union[str, None]:
"""
获取视频id
:param original_url: 视频链接
:return: 视频id
"""
# 正则匹配出视频ID
try:
video_url = await self.convert_share_urls(original_url)
# 链接类型:
# 视频页 https://www.douyin.com/video/7086770907674348841
if '/video/' in video_url:
key = re.findall('/video/(\d+)?', video_url)[0]
print('获取到的抖音视频ID为: {}'.format(key))
return key
# 发现页 https://www.douyin.com/discover?modal_id=7086770907674348841
elif 'discover?' in video_url:
key = re.findall('modal_id=(\d+)', video_url)[0]
print('获取到的抖音视频ID为: {}'.format(key))
return key
# 直播页
elif 'live.douyin' in video_url:
# https://live.douyin.com/1000000000000000000
video_url = video_url.split('?')[0] if '?' in video_url else video_url
key = video_url.replace('https://live.douyin.com/', '')
print('获取到的抖音直播ID为: {}'.format(key))
return key
# note
elif 'note' in video_url:
# https://www.douyin.com/note/7086770907674348841
key = re.findall('/note/(\d+)?', video_url)[0]
print('获取到的抖音笔记ID为: {}'.format(key))
return key
except Exception as e:
print('获取抖音视频ID出错了:{}'.format(e))
return None
# 获取单个抖音视频数据/Get single Douyin video data
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_douyin_video_data(self, video_id: str) -> Union[dict, None]:
"""
:param video_id: str - 抖音视频id
:return:dict - 包含信息的字典
"""
print('正在获取抖音视频数据...')
try:
# 构造访问链接/Construct the access link
api_url = f"https://www.douyin.com/aweme/v1/web/aweme/detail/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id={video_id}&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1344&screen_height=756&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=110.0&browser_online=true&engine_name=Gecko&engine_version=109.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=&platform=PC&webid=7158288523463362079&msToken=abL8SeUTPa9-EToD8qfC7toScSADxpg6yLh2dbNcpWHzE0bT04txM_4UwquIcRvkRb9IU8sifwgM1Kwf1Lsld81o9Irt2_yNyUbbQPSUO8EfVlZJ_78FckDFnwVBVUVK"
api_url = self.generate_x_bogus_url(api_url)
# 访问API/Access API
print("正在获取视频数据API: {}".format(api_url))
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=self.douyin_api_headers, proxy=self.proxies,
timeout=10) as response:
response = await response.json()
# 获取视频数据/Get video data
video_data = response['aweme_detail']
print('获取视频数据成功!')
# print("抖音API返回数据: {}".format(video_data))
return video_data
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
# 获取单个抖音直播视频数据/Get single Douyin Live video data
# 暂时不可用,待修复。
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_douyin_live_video_data(self, web_rid: str) -> Union[dict, None]:
print('正在获取抖音视频数据...')
try:
# 构造访问链接/Construct the access link
api_url = f"https://live.douyin.com/webcast/web/enter/?aid=6383&web_rid={web_rid}"
# 访问API/Access API
print("正在获取视频数据API: {}".format(api_url))
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=self.douyin_api_headers, proxy=self.proxies,
timeout=10) as response:
response = await response.json()
# 获取视频数据/Get video data
video_data = response['data']
print(video_data)
print('获取视频数据成功!')
# print("抖音API返回数据: {}".format(video_data))
return video_data
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
# 获取单个抖音视频数据/Get single Douyin video data
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_douyin_user_profile_videos(self, profile_url: str, tikhub_token: str) -> Union[dict, None]:
try:
api_url = f"https://api.tikhub.io/douyin_profile_videos/?douyin_profile_url={profile_url}&cursor=0&count=20"
_headers = {"Authorization": f"Bearer {tikhub_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=_headers, proxy=self.proxies, timeout=10) as response:
response = await response.json()
return response
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
# 获取抖音主页点赞视频数据/Get Douyin profile like video data
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_douyin_profile_liked_data(self, profile_url: str, tikhub_token: str) -> Union[dict, None]:
try:
api_url = f"https://api.tikhub.io/douyin_profile_liked_videos/?douyin_profile_url={profile_url}&cursor=0&count=20"
_headers = {"Authorization": f"Bearer {tikhub_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=_headers, proxy=self.proxies, timeout=10) as response:
response = await response.json()
return response
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
# 获取抖音视频评论数据/Get Douyin video comment data
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_douyin_video_comments(self, video_url: str, tikhub_token: str) -> Union[dict, None]:
try:
api_url = f"https://api.tikhub.io/douyin_video_comments/?douyin_video_url={video_url}&cursor=0&count=20"
_headers = {"Authorization": f"Bearer {tikhub_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=_headers, proxy=self.proxies, timeout=10) as response:
response = await response.json()
return response
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
"""__________________________________________⬇️TikTok methods(TikTok方法)⬇️______________________________________"""
# 获取TikTok视频ID/Get TikTok video ID
async def get_tiktok_video_id(self, original_url: str) -> Union[str, None]:
"""
获取视频id
:param original_url: 视频链接
:return: 视频id
"""
try:
# 转换链接/Convert link
original_url = await self.convert_share_urls(original_url)
# 获取视频ID/Get video ID
if '/video/' in original_url:
video_id = re.findall('/video/(\d+)', original_url)[0]
elif '/v/' in original_url:
video_id = re.findall('/v/(\d+)', original_url)[0]
print('获取到的TikTok视频ID是{}'.format(video_id))
# 返回视频ID/Return video ID
return video_id
except Exception as e:
print('获取TikTok视频ID出错了:{}'.format(e))
return None
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_tiktok_video_data(self, video_id: str) -> Union[dict, None]:
"""
获取单个视频信息
:param video_id: 视频id
:return: 视频信息
"""
print('正在获取TikTok视频数据...')
try:
# 构造访问链接/Construct the access link
api_url = f'https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id={video_id}'
print("正在获取视频数据API: {}".format(api_url))
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=self.tiktok_api_headers, proxy=self.proxies,
timeout=10) as response:
response = await response.json()
video_data = response['aweme_list'][0]
print('获取视频信息成功!')
return video_data
except Exception as e:
print('获取视频信息失败!原因:{}'.format(e))
# return None
raise e
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_tiktok_user_profile_videos(self, tiktok_video_url: str, tikhub_token: str) -> Union[dict, None]:
try:
api_url = f"https://api.tikhub.io/tiktok_profile_videos/?tiktok_video_url={tiktok_video_url}&cursor=0&count=20"
_headers = {"Authorization": f"Bearer {tikhub_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=_headers, proxy=self.proxies, timeout=10) as response:
response = await response.json()
return response
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
@retry(stop=stop_after_attempt(4), wait=wait_fixed(7))
async def get_tiktok_user_profile_liked_videos(self, tiktok_video_url: str, tikhub_token: str) -> Union[dict, None]:
try:
api_url = f"https://api.tikhub.io/tiktok_profile_liked_videos/?tiktok_video_url={tiktok_video_url}&cursor=0&count=20"
_headers = {"Authorization": f"Bearer {tikhub_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(api_url, headers=_headers, proxy=self.proxies, timeout=10) as response:
response = await response.json()
return response
except Exception as e:
print('获取抖音视频数据失败!原因:{}'.format(e))
# return None
raise e
"""__________________________________________⬇️Hybrid methods(混合方法)⬇️______________________________________"""
# 自定义获取数据/Custom data acquisition
async def hybrid_parsing(self, video_url: str) -> dict:
# URL平台判断/Judge URL platform
url_platform = 'douyin' if 'douyin' in video_url else 'tiktok'
print('当前链接平台为:{}'.format(url_platform))
# 获取视频ID/Get video ID
print("正在获取视频ID...")
video_id = await self.get_douyin_video_id(
video_url) if url_platform == 'douyin' else await self.get_tiktok_video_id(
video_url)
if video_id:
print("获取视频ID成功,视频ID为:{}".format(video_id))
# 获取视频数据/Get video data
print("正在获取视频数据...")
data = await self.get_douyin_video_data(
video_id) if url_platform == 'douyin' else await self.get_tiktok_video_data(
video_id)
if data:
print("获取视频数据成功,正在判断数据类型...")
url_type_code = data['aweme_type']
"""以下为抖音/TikTok类型代码/Type code for Douyin/TikTok"""
url_type_code_dict = {
# 抖音/Douyin
2: 'image',
4: 'video',
68: 'image',
# TikTok
0: 'video',
51: 'video',
55: 'video',
58: 'video',
61: 'video',
150: 'image'
}
# 获取视频类型/Get video type
# 如果类型代码不存在,则默认为视频类型/If the type code does not exist, it is assumed to be a video type
print("数据类型代码: {}".format(url_type_code))
# 判断链接类型/Judge link type
url_type = url_type_code_dict.get(url_type_code, 'video')
print("数据类型: {}".format(url_type))
print("准备开始判断并处理数据...")
"""
以下为(视频||图片)数据处理的四个方法,如果你需要自定义数据处理请在这里修改.
The following are four methods of (video || image) data processing.
If you need to customize data processing, please modify it here.
"""
"""
创建已知数据字典(索引相同),稍后使用.update()方法更新数据
Create a known data dictionary (index the same),
and then use the .update() method to update the data
"""
result_data = {
'status': 'success',
'message': "更多接口请查看(More API see): https://api.tikhub.io/docs",
'type': url_type,
'platform': url_platform,
'aweme_id': video_id,
'official_api_url':
{
"User-Agent": self.headers["User-Agent"],
"api_url": f"https://www.iesdouyin.com/aweme/v1/web/aweme/detail/?aweme_id={video_id}&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333&Github=Evil0ctal&words=FXXK_U_ByteDance"
} if url_platform == 'douyin'
else
{
"User-Agent": self.tiktok_api_headers["User-Agent"],
"api_url": f'https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id={video_id}'
},
'desc': data.get("desc"),
'create_time': data.get("create_time"),
'author': data.get("author"),
'music': data.get("music"),
'statistics': data.get("statistics"),
'cover_data': {
'cover': data.get("video").get("cover"),
'origin_cover': data.get("video").get("origin_cover"),
'dynamic_cover': data.get("video").get("dynamic_cover")
},
'hashtags': data.get('text_extra'),
}
# 创建一个空变量,稍后使用.update()方法更新数据/Create an empty variable and use the .update() method to update the data
api_data = None
# 判断链接类型并处理数据/Judge link type and process data
try:
# 抖音数据处理/Douyin data processing
if url_platform == 'douyin':
# 抖音视频数据处理/Douyin video data processing
if url_type == 'video':
print("正在处理抖音视频数据...")
# 将信息储存在字典中/Store information in a dictionary
uri = data['video']['play_addr']['uri']
wm_video_url = data['video']['play_addr']['url_list'][0]
wm_video_url_HQ = f"https://aweme.snssdk.com/aweme/v1/playwm/?video_id={uri}&radio=1080p&line=0"
nwm_video_url = wm_video_url.replace('playwm', 'play')
nwm_video_url_HQ = f"https://aweme.snssdk.com/aweme/v1/play/?video_id={uri}&ratio=1080p&line=0"
api_data = {
'video_data':
{
'wm_video_url': wm_video_url,
'wm_video_url_HQ': wm_video_url_HQ,
'nwm_video_url': nwm_video_url,
'nwm_video_url_HQ': nwm_video_url_HQ
}
}
# 抖音图片数据处理/Douyin image data processing
elif url_type == 'image':
print("正在处理抖音图片数据...")
# 无水印图片列表/No watermark image list
no_watermark_image_list = []
# 有水印图片列表/With watermark image list
watermark_image_list = []
# 遍历图片列表/Traverse image list
for i in data['images']:
no_watermark_image_list.append(i['url_list'][0])
watermark_image_list.append(i['download_url_list'][0])
api_data = {
'image_data':
{
'no_watermark_image_list': no_watermark_image_list,
'watermark_image_list': watermark_image_list
}
}
# TikTok数据处理/TikTok data processing
elif url_platform == 'tiktok':
# TikTok视频数据处理/TikTok video data processing
if url_type == 'video':
print("正在处理TikTok视频数据...")
# 将信息储存在字典中/Store information in a dictionary
wm_video = data['video']['download_addr']['url_list'][0]
api_data = {
'video_data':
{
'wm_video_url': wm_video,
'wm_video_url_HQ': wm_video,
'nwm_video_url': data['video']['play_addr']['url_list'][0],
'nwm_video_url_HQ': data['video']['bit_rate'][0]['play_addr']['url_list'][0]
}
}
# TikTok图片数据处理/TikTok image data processing
elif url_type == 'image':
print("正在处理TikTok图片数据...")
# 无水印图片列表/No watermark image list
no_watermark_image_list = []
# 有水印图片列表/With watermark image list
watermark_image_list = []
for i in data['image_post_info']['images']:
no_watermark_image_list.append(i['display_image']['url_list'][0])
watermark_image_list.append(i['owner_watermark_image']['url_list'][0])
api_data = {
'image_data':
{
'no_watermark_image_list': no_watermark_image_list,
'watermark_image_list': watermark_image_list
}
}
# 更新数据/Update data
result_data.update(api_data)
# print("数据处理完成,最终数据: \n{}".format(result_data))
# 返回数据/Return data
return result_data
except Exception as e:
traceback.print_exc()
print("数据处理失败!")
return {'status': 'failed', 'message': '数据处理失败!/Data processing failed!'}
else:
print("[抖音|TikTok方法]返回数据为空,无法处理!")
return {'status': 'failed',
'message': '返回数据为空,无法处理!/Return data is empty and cannot be processed!'}
else:
print('获取视频ID失败!')
return {'status': 'failed', 'message': '获取视频ID失败!/Failed to get video ID!'}
# 处理数据方便快捷指令使用/Process data for easy-to-use shortcuts
@staticmethod
def hybrid_parsing_minimal(data: dict) -> dict:
# 如果数据获取成功/If the data is successfully obtained
if data['status'] == 'success':
result = {
'status': 'success',
'message': data.get('message'),
'platform': data.get('platform'),
'type': data.get('type'),
'desc': data.get('desc'),
'wm_video_url': data['video_data']['wm_video_url'] if data['type'] == 'video' else None,
'wm_video_url_HQ': data['video_data']['wm_video_url_HQ'] if data['type'] == 'video' else None,
'nwm_video_url': data['video_data']['nwm_video_url'] if data['type'] == 'video' else None,
'nwm_video_url_HQ': data['video_data']['nwm_video_url_HQ'] if data['type'] == 'video' else None,
'no_watermark_image_list': data['image_data']['no_watermark_image_list'] if data[
'type'] == 'image' else None,
'watermark_image_list': data['image_data']['watermark_image_list'] if data['type'] == 'image' else None
}
return result
else:
return data
"""__________________________________________⬇️Test methods(测试方法)⬇️______________________________________"""
async def async_test(_douyin_url: str = None, _tiktok_url: str = None) -> None:
# 异步测试/Async test
start_time = time.time()
print("正在进行异步测试...")
print("正在测试异步获取抖音视频ID方法...")
douyin_id = await api.get_douyin_video_id(_douyin_url)
print("正在测试异步获取抖音视频数据方法...")
douyin_data = await api.get_douyin_video_data(douyin_id)
print("正在测试异步获取TikTok视频ID方法...")
tiktok_id = await api.get_tiktok_video_id(_tiktok_url)
print("正在测试异步获取TikTok视频数据方法...")
tiktok_data = await api.get_tiktok_video_data(tiktok_id)
print("正在测试异步混合解析方法...")
douyin_hybrid_data = await api.hybrid_parsing(_douyin_url)
tiktok_hybrid_data = await api.hybrid_parsing(_tiktok_url)
# 总耗时/Total time
total_time = round(time.time() - start_time, 2)
print("异步测试完成,总耗时: {}s".format(total_time))
if __name__ == '__main__':
api = Scraper()
# 运行测试
# params = "device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=7153585499477757192&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1344&screen_height=756&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=110.0&browser_online=true&engine_name=Gecko&engine_version=109.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=&platform=PC&webid=7158288523463362079"
# api.generate_x_bogus(params)
douyin_url = 'https://v.douyin.com/rLyrQxA/6.66'
tiktok_url = 'https://vt.tiktok.com/ZSRwWXtdr/'
asyncio.run(async_test(_douyin_url=douyin_url, _tiktok_url=tiktok_url))