feat:Added recording support for five new live streaming platforms

- Added recording support for five new live streaming platforms: winktv, flextv, look live, popkontv, and twitcasting.
- Implemented configuration for overseas platform accounts and passwords, enabling automatic login and cookie updates for overseas platforms.
- Added display name for overseas platforms as account nickname + account ID.
- Introduced custom configuration for platforms requiring proxy recording.
- Added option to only push live broadcast notifications without recording.
This commit is contained in:
ihmily
2024-03-09 14:34:14 +08:00
parent c26e3989a5
commit 903662def5
6 changed files with 1289 additions and 456 deletions

View File

@@ -6,9 +6,9 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/ihmily/douyin-live-recorder?label=Docker%20Pulls&color=blue&logo=docker)](https://hub.docker.com/r/ihmily/douyin-live-recorder/tags)
![GitHub issues](https://img.shields.io/github/issues/ihmily/DouyinLiveRecorder.svg)
[![Latest Release](https://img.shields.io/github/v/release/ihmily/DouyinLiveRecorder)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)
![Downloads](https://img.shields.io/github/downloads/ihmily/DouyinLiveRecorder/total)
[![Downloads](https://img.shields.io/github/downloads/ihmily/DouyinLiveRecorder/total)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)
一款简易的可循环值守的直播录制工具基于FFmpeg实现多平台直播源录制支持自定义配置录制以及直播状态推送。
一款**简易**的可循环值守的直播录制工具基于FFmpeg实现多平台直播源录制支持自定义配置录制以及直播状态推送。
</div>
@@ -27,8 +27,13 @@
- [x] AfreecaTV
- [x] 网易cc
- [x] 千度热播
- [x] pandaTV
- [x] PandaTV
- [x] 猫耳FM
- [x] Look直播
- [x] WinkTV
- [x] FlexTV
- [x] PopkonTV
- [x] TwitCasting
- [ ] 更多平台正在更新中
</div>
@@ -38,7 +43,6 @@
```
.
└── DouyinLiveRecorder/
├── /api -> (get live stream api )
├── /config -> (config record)
├── /logs -> (save runing log file)
├── /backup_config -> (backup file)
@@ -76,7 +80,7 @@
- 如果要长时间挂着软件循环监测直播最好循环时间设置长一点咱也不差没录制到的那几分钟避免因请求频繁导致被官方封禁IP 。
- 要停止直播录制,使用`Ctrl+C ` 或直接关闭程序即可。
- 最后欢迎右上角给本项目一个star同时也非常乐意大家提交pr(请先询问我,避免做无用功)
- 最后欢迎右上角给本项目一个star同时也非常乐意大家提交pr。
&emsp;
@@ -107,7 +111,7 @@ B站
https://live.bilibili.com/320
小红书:
https://www.redelight.cn/hina/livestream/569077534207413574/1707413727088?appuid=5f3f478a00000000010005b3&
https://www.xiaohongshu.com/hina/livestream/569077534207413574/1707413727088?appuid=5f3f478a00000000010005b3&
bigo直播
https://www.bigo.tv/cn/716418802
@@ -124,11 +128,26 @@ https://cc.163.com/583946984
千度热播:
https://qiandurebo.com/web/video.php?roomnumber=33333
pandaTV
PandaTV
https://www.pandalive.co.kr/live/play/bara0109
猫耳FM
https://fm.missevan.com/live/868895007
Look直播:
https://look.163.com/live?id=65108820&position=3
WinkTV:
https://www.winktv.co.kr/live/play/anjer1004
FlexTV:
https://www.flextv.co.kr/channels/593127/live
PopkonTV:
https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117
TwitCasting:
https://twitcasting.tv/c:uonq
```
直播间分享地址和网页端长地址都能正常进行录制抖音尽量用长链接避免因短链接转换失效导致不能正常录制而且需要有nodejs环境否则无法转换
@@ -152,7 +171,6 @@ https://fm.missevan.com/live/868895007
```bash
git clone https://github.com/ihmily/DouyinLiveRecorder.git
```
2.进入项目文件夹,安装依赖
@@ -256,10 +274,20 @@ docker-compose stop
[![iridescentGray](https://github.com/iridescentGray.png?size=50)](https://github.com/iridescentGray)
[![annidy](https://github.com/annidy.png?size=50)](https://github.com/annidy)
[![wwkk2580](https://github.com/wwkk2580.png?size=50)](https://github.com/wwkk2580)
[![missuo](https://github.com/missuo.png?size=50)](https://github.com/missuo)
&emsp;
## ⏳提交日志
- 20240309
- 修复虎牙直播、小红书直播和B站直播录制
- 新增5个直播平台录制包括winktv、flextv、look、popkontv、twitcasting
- 新增部分海外平台账号密码配置实现自动登录并更新配置文件中的cookie
- 新增自定义配置需要使用代理录制的平台
- 新增只推送开播消息不进行录制设置
- 修复了一些bug
- 20240209
- 优化AfreecaTV录制新增账号密码登录获取cookie以及持久保存
- 修复了小红书直播因官方更新直播域名,导致无法录制直播的问题

View File

@@ -1,45 +1,63 @@
[录制设置]
直播保存路径(不填则默认) =
视频保存格式ts|mkv|flv|mp4|ts音频|mkv音频 = mp4
直播保存路径(不填则默认) =
视频保存格式ts|mkv|flv|mp4|ts音频|mkv音频 = ts
原画|超清|高清|标清 = 原画
是否使用代理ip是/否) =
代理地址 =
代理地址 =
同一时间访问网络的线程数 = 3
循环时间(秒) = 120
排队读取网址时间(秒) = 0
是否显示循环秒数 =
分段录制是否开启 =
分段录制是否开启 =
视频分段时间(秒) = 1800
生成时间文件 =
TS录制完成后自动转为mp4格式 =
TS录制完成后自动增加生成m4a格式 =
ts录制完成后自动转为mp4格式 =
ts录制完成后自动增加生成m4a格式 =
追加格式后删除原文件 =
生成时间文件 =
使用代理录制的平台(逗号分隔) = tiktok, afreecatv, pandalive, winktv, flextv, popkontv
额外使用代理录制的平台(逗号分隔) =
[推送配置]
直播状态通知(可选微信|钉钉|TG或者都填) =
钉钉推送接口链接 =
微信推送接口链接 =
钉钉通知@对象(填手机号) =
TGAPI令牌 =
TG聊天ID(个人或者群组ID) =
直播状态通知(可选微信|钉钉|tg或者都填) =
钉钉推送接口链接 =
微信推送接口链接 =
钉钉通知@对象(填手机号) =
tgapi令牌 =
tg聊天id(个人或者群组id) =
只推送通知不录制(是/否) =
直播推送检测频率(秒) = 1800
[Cookie]
抖音cookie(录制抖音必须要有) = ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986
快手cookie =
tiktok_cookie =
虎牙cookie =
斗鱼cookie =
yy_cookie =
B站cookie =
小红书cookie =
bigo_cookie =
blued_cookie =
afreecatv_cookie =
netease_cookie =
千度热播_cookie =
快手cookie =
tiktok_cookie =
虎牙cookie =
斗鱼cookie =
yy_cookie =
b站cookie =
小红书cookie =
bigo_cookie =
blued_cookie =
afreecatv_cookie =
netease_cookie =
千度热播_cookie =
pandatv_cookie =
猫耳FM_cookie =
猫耳fm_cookie =
winktv_cookie =
flextv_cookie =
look_cookie =
twitcasting_cookie =
[Authorization]
popkontv_token =
[账号密码]
afreecatv账号 =
afreecatv密码 =
afreecatv密码 =
flextv账号 =
flextv密码 =
popkontv账号 =
partner_code = P-00001
popkontv密码 =
twitcasting账号 =
twitcasting密码 =

491
main.py
View File

@@ -4,26 +4,29 @@
Author: Hmily
GitHub: https://github.com/ihmily
Date: 2023-07-17 23:52:05
Update: 2024-02-09 02:41:18
Update: 2024-03-09 03:25:39
Copyright (c) 2023-2024 by Hmily, All Rights Reserved.
Function: Record live stream video.
"""
import random
import os
import sys
import urllib.parse
import urllib.request
import configparser
import subprocess
import signal
import threading
import datetime
import time
import datetime
import json
import re
import shutil
import signal
import random
import base64
import hashlib
import urllib.parse
import urllib.request
from urllib.error import URLError, HTTPError
from typing import Any, Union
import configparser
from spider import (
get_douyin_stream_data,
@@ -41,7 +44,12 @@ from spider import (
get_netease_stream_data,
get_qiandurebo_stream_data,
get_pandatv_stream_data,
get_maoerfm_stream_url
get_maoerfm_stream_url,
get_winktv_stream_data,
get_flextv_stream_data,
get_looklive_stream_url,
get_popkontv_stream_url,
get_twitcasting_stream_url
)
from web_rid import (
@@ -54,8 +62,10 @@ from utils import (
)
from msg_push import dingtalk, xizhi, tg_bot
version = "v3.0.1-beta"
platforms = "抖音|TikTok|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|AfreecaTV|网易CC|千度热播|pandaTV|猫耳FM"
version = "v3.0.2"
platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|网易CC|千度热播|猫耳FM|Look直播|TwitCasting" \
"\n海外站点TikTok|AfreecaTV|PandaTV|WinkTV|FlexTV|PopkonTV"
# --------------------------全局变量-------------------------------------
recording = set()
unrecording = set()
@@ -78,7 +88,7 @@ config_file = './config/config.ini'
url_config_file = './config/URL_config.ini'
backup_dir = './backup_config'
encoding = 'utf-8-sig'
rstr = r"[\/\\\:\*\?\"\<\>\|&u]"
rstr = r"[\/\\\:\*\?\"\<\>\|&.。]"
ffmpeg_path = "ffmpeg" # ffmpeg文件路径
default_path = os.getcwd() + '/downloads'
os.makedirs(default_path, exist_ok=True)
@@ -162,7 +172,7 @@ def update_file(file_path: str, old_str: str, new_str: str, start_str: str = Non
def converts_mp4(address: str):
if tsconvert_to_mp4:
if ts_to_mp4:
_output = subprocess.check_output([
"ffmpeg", "-i", address,
"-c:v", "copy",
@@ -176,7 +186,7 @@ def converts_mp4(address: str):
def converts_m4a(address: str):
if tsconvert_to_m4a:
if ts_to_m4a:
_output = subprocess.check_output([
"ffmpeg", "-i", address,
"-n", "-vn",
@@ -297,13 +307,14 @@ def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict:
result['flv_url'] = flv_url
result['is_live'] = True
result['record_url'] = m3u8_url # 使用 m3u8 链接进行录制
return result
@trace_error_decorator
def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict:
# TODO: 获取tiktok直播源地址
if not json_data:
return {"anchor_name": None, "is_live": False}
def get_video_quality_url(stream, q_key):
return {
@@ -313,7 +324,7 @@ def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict:
live_room = json_data['LiveRoom']['liveRoomUserInfo']
user = live_room['user']
anchor_name = user['nickname']
anchor_name = f"{user['nickname']}-{user['uniqueId']}"
status = user.get("status", 4)
result = {
@@ -394,16 +405,53 @@ def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:
if stream_info_list:
select_cdn = stream_info_list[0]
# flv_url = select_cdn.get('sFlvUrl')
flv_url = 'http://hw.flv.huya.com/src' # 能播放但无法录制,待修复
flv_url = select_cdn.get('sFlvUrl')
stream_name = select_cdn.get('sStreamName')
flv_url_suffix = select_cdn.get('sFlvUrlSuffix')
hls_url = select_cdn.get('sHlsUrl')
hls_url_suffix = select_cdn.get('sHlsUrlSuffix')
flv_anti_code = select_cdn.get('sFlvAntiCode')
flv_url = f'{flv_url}/{stream_name}.{flv_url_suffix}?{flv_anti_code}&ratio='
m3u8_url = f'{hls_url}/{stream_name}.{hls_url_suffix}?{flv_anti_code}&ratio='
def get_anti_code(old_anti_code):
# js地址https://hd.huya.com/cdn_libs/mobile/hysdk-m-202402211431.js
params_t = 100
sdk_version = 2403051612
# sdk_id是13位数毫秒级时间戳
t13 = int(time.time()) * 1000
sdk_sid = t13
# 计算uuid和uid参数值
init_uuid = (int(t13 % 10**10 * 1000) + int(1000 * random.random())) % 4294967295 # 直接初始化
uid = random.randint(1400000000000, 1400009999999) # 经过测试uid也可以使用init_uuid代替
seq_id = uid + sdk_sid # 移动端请求的直播流地址中包含seqId参数
# 计算ws_time参数值(16进制) 可以是当前毫秒时间戳当然也可以直接使用url_query['wsTime'][0]
# 原始最大误差不得慢240000毫秒
target_unix_time = (t13+110624) // 1000
ws_time = hex(target_unix_time)[2:].lower()
# fm参数值是经过url编码然后base64编码得到的解码结果类似 DWq8BcJ3h6DJt6TY_$0_$1_$2_$3
# 具体细节在上面js中查看大概在32657行代码开始有base64混淆代码请自行替换
url_query = urllib.parse.parse_qs(old_anti_code)
ws_secret_pf = base64.b64decode(urllib.parse.unquote(url_query['fm'][0]).encode()).decode().split("_")[0]
ws_secret_hash = hashlib.md5(f'{seq_id}|{url_query["ctype"][0]}|{params_t}'.encode()).hexdigest()
ws_secret = f'{ws_secret_pf}_{uid}_{stream_name}_{ws_secret_hash}_{ws_time}'
ws_secret_md5 = hashlib.md5(ws_secret.encode()).hexdigest()
anti_code = (
f'wsSecret={ws_secret_md5}&wsTime={ws_time}&seqid={seq_id}&ctype={url_query["ctype"][0]}&ver=1'
f'&fs={url_query["fs"][0]}&uuid={init_uuid}&u={uid}&t={params_t}&sv={sdk_version}'
f'&sphdcdn={url_query["sphdcdn"][0]}&sphdDC={url_query["sphdDC"][0]}&sphd={url_query["sphd"][0]}'
f'&exsphd={url_query["exsphd"][0]}&sdk_sid={sdk_sid}&codec=264'
)
return anti_code
new_anti_code = get_anti_code(flv_anti_code)
flv_url = f'{flv_url}/{stream_name}.{flv_url_suffix}?{new_anti_code}&ratio='
m3u8_url = f'{hls_url}/{stream_name}.{hls_url_suffix}?{new_anti_code}&ratio='
quality_list = flv_anti_code.split('&exsphd=')
if len(quality_list) > 1:
@@ -430,12 +478,12 @@ def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:
result['flv_url'] = flv_url
result['m3u8_url'] = m3u8_url
result['is_live'] = True
result['record_url'] = flv_url # 虎牙使用flv视频流录制
result['record_url'] = flv_url # m3u8经常会出现断流
return result
@trace_error_decorator
def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str) -> dict:
def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str, proxy_address: str) -> dict:
# TODO: 获取斗鱼直播源地址
video_quality_options = {
"原画": '0',
@@ -453,16 +501,16 @@ def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str) -> d
"is_live": False,
}
# 如果status值为1则正在直播
# 这边有个bug就是如果是直播回放状态也是在直播 待修复
# 这边有个bug就是如果是直播回放状态也是在直播 待优化
if status == 1:
rid = str(room_info['rid'])
rate = video_quality_options.get(video_quality, '0') # 默认为原画
flv_data = get_douyu_stream_data(rid, rate, cookies)
flv_data = get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_address)
flv_url = flv_data['data'].get('url', None)
if flv_url:
result['flv_url'] = flv_url
result['is_live'] = True
result['record_url'] = flv_url # 斗鱼目前只能使用flv视频流录制
result['record_url'] = flv_url
return result
@@ -509,12 +557,16 @@ def get_bilibili_stream_url(json_data: dict, video_quality: str) -> dict:
while len(accept_qn_list) < 4:
accept_qn_list.append(accept_qn_list[-1])
base_url = stream_data['base_url']
current_qn = stream_data['current_qn']
host = stream_data['url_info'][0]['host']
extra = stream_data['url_info'][0]['extra']
url_type = format_list[m]
qn = str(accept_qn_list[n])
select_quality = quality_list[qn]
base_url = re.sub(r'_(\d+)' + f'(?={url_type}\\?)', select_quality, base_url)
if current_qn != 10000:
base_url = re.sub(r'_(\d+)' + f'(?={url_type}\\?)', select_quality, base_url)
extra = re.sub('&qn=0', f'&qn={qn}', extra)
return host + base_url + extra
@@ -563,12 +615,50 @@ def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
}
@trace_error_decorator
def get_winktv_stream_url(json_data: dict, video_quality: str) -> dict:
if not json_data['is_live']:
return json_data
quality_length = len(json_data['play_url_list'])
quality_list = {'原画': 'hls', '蓝光': 'hls', '超清': 'hls2', '高清': 'hls3', '标清': 'hls4'}
for i in json_data['play_url_list']:
if i in 'hls' and i not in list(quality_list.values()):
json_data['play_url_list'][i] = json_data['play_url_list'][quality_length - 1]
selected_quality = quality_list[video_quality]
flv_url = json_data['play_url_list'][selected_quality][0]['url']
return {
"is_live": True,
"anchor_name": json_data['anchor_name'],
"flv_url": flv_url,
"record_url": flv_url
}
def push_message(content: str):
push_pts = []
if '微信' in live_status_push:
push_pts.append('微信')
xizhi(xizhi_api_url, content)
if '钉钉' in live_status_push:
push_pts.append('钉钉')
dingtalk(dingtalk_api_url, content, dingtalk_phone_num)
if 'TG' in live_status_push:
push_pts.append('TG')
tg_bot(tg_chat_id, tg_token, content)
push_pts = ''.join(push_pts) if len(push_pts) > 0 else ''
return push_pts
def start_record(url_data: tuple, count_variable: int = -1):
global warning_count
global video_save_path
global live_list
global not_record_list
global recording_time_list
start_pushed = False
while True:
try:
@@ -581,8 +671,23 @@ def start_record(url_data: tuple, count_variable: int = -1):
count_time = time.time()
retry = 0
record_quality, record_url, anchor_name = url_data
print(f"\r运行新线程,传入地址 {record_url}")
proxy_address = proxy_addr
if proxy_addr:
proxy_address = None
for platform in enable_proxy_platform_list:
if platform and platform.strip() in url:
proxy_address = proxy_addr
break
if not proxy_address:
if extra_enable_proxy_platform_list:
for pt in extra_enable_proxy_platform_list:
if pt and pt.strip() in url:
proxy_address = proxy_addr_bak if proxy_addr_bak else None
# print(f'\r代理地址:{proxy_address}')
print(f"\r运行新线程,传入地址 {record_url}")
while True:
try:
port_info = []
@@ -590,93 +695,114 @@ def start_record(url_data: tuple, count_variable: int = -1):
platform = '抖音直播'
# 判断如果是浏览器长链接
with semaphore:
# 使用semaphore来控制同时访问资源的线程数量
json_data = get_douyin_stream_data(record_url, cookies=dy_cookie)
json_data = get_douyin_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=dy_cookie)
port_info = get_douyin_stream_url(json_data, record_quality)
elif record_url.find("https://v.douyin.com/") > -1:
platform = '抖音直播'
# 判断如果是app分享链接
is_long_url = True
room_id, sec_user_id = get_sec_user_id(record_url)
web_rid = get_live_room_id(room_id, sec_user_id)
room_id, sec_user_id = get_sec_user_id(url=record_url, proxy_addr=proxy_address)
web_rid = get_live_room_id(room_id, sec_user_id, proxy_addr=proxy_address)
if len(web_rid) == 0:
print('web_rid 获取失败,若多次失败请联系作者修复或者使用浏览器打开后的长链接')
new_record_url = "https://live.douyin.com/" + str(web_rid)
not_record_list.append(new_record_url)
with semaphore:
json_data = get_douyin_stream_data(new_record_url, cookies=dy_cookie)
json_data = get_douyin_stream_data(
url=new_record_url,
proxy_addr=proxy_address,
cookies=dy_cookie)
port_info = get_douyin_stream_url(json_data, record_quality)
elif record_url.find("https://www.tiktok.com/") > -1:
platform = 'TikTok直播'
with semaphore:
if use_proxy:
if global_proxy or proxy_addr != '':
json_data = get_tiktok_stream_data(
url=record_url,
proxy_addr=proxy_addr,
cookies=tiktok_cookie)
port_info = get_tiktok_stream_url(json_data, record_quality)
if global_proxy or proxy_address:
json_data = get_tiktok_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=tiktok_cookie)
port_info = get_tiktok_stream_url(json_data, record_quality)
else:
logger.warning(f"错误信息: 网络异常请检查网络是否能正常访问TikTok平台")
elif record_url.find("https://live.kuaishou.com/") > -1:
platform = '快手直播'
with semaphore:
json_data = get_kuaishou_stream_data(record_url, cookies=ks_cookie)
json_data = get_kuaishou_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=ks_cookie)
port_info = get_kuaishou_stream_url(json_data, record_quality)
elif record_url.find("https://www.huya.com/") > -1:
platform = '虎牙直播'
with semaphore:
json_data = get_huya_stream_data(record_url, cookies=hy_cookie)
json_data = get_huya_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=hy_cookie)
port_info = get_huya_stream_url(json_data, record_quality)
elif record_url.find("https://www.douyu.com/") > -1:
platform = '斗鱼直播'
with semaphore:
json_data = get_douyu_info_data(record_url)
json_data = get_douyu_info_data(url=record_url, proxy_addr=proxy_address)
port_info = get_douyu_stream_url(
json_data, cookies=douyu_cookie,
json_data, proxy_address=proxy_address, cookies=douyu_cookie,
video_quality=record_quality
)
elif record_url.find("https://www.yy.com/") > -1:
platform = 'YY直播'
with semaphore:
json_data = get_yy_stream_data(record_url, cookies=yy_cookie)
json_data = get_yy_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=yy_cookie)
port_info = get_yy_stream_url(json_data)
elif record_url.find("https://live.bilibili.com/") > -1:
platform = 'B站直播'
with semaphore:
json_data = get_bilibili_stream_data(record_url, cookies=bili_cookie)
json_data = get_bilibili_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=bili_cookie)
port_info = get_bilibili_stream_url(json_data, record_quality)
elif record_url.find("https://www.redelight.cn/") > -1:
elif record_url.find("https://www.redelight.cn/") > -1 or \
record_url.find("https://www.xiaohongshu.com/") > -1:
platform = '小红书直播'
if retry > 0:
time.sleep(7200)
retry = 0
with semaphore:
port_info = get_xhs_stream_url(record_url, cookies=xhs_cookie)
port_info = get_xhs_stream_url(url=record_url, proxy_addr=proxy_address, cookies=xhs_cookie)
retry += 1
elif record_url.find("https://www.bigo.tv/") > -1:
platform = 'bigo直播'
platform = 'Bigo直播'
with semaphore:
port_info = get_bigo_stream_url(record_url, cookies=bigo_cookie)
port_info = get_bigo_stream_url(record_url, proxy_addr=proxy_address, cookies=bigo_cookie)
elif record_url.find("https://app.blued.cn/") > -1:
platform = 'blued直播'
platform = 'Blued直播'
with semaphore:
port_info = get_blued_stream_url(record_url, cookies=blued_cookie)
port_info = get_blued_stream_url(record_url, proxy_addr=proxy_address, cookies=blued_cookie)
elif record_url.find("afreecatv.com/") > -1:
platform = 'AfreecaTv直播'
platform = 'AfreecaTV'
with semaphore:
port_info = get_afreecatv_stream_url(
url=record_url, proxy_addr=proxy_addr,
cookies=afreecatv_cookie
)
if global_proxy or proxy_address:
port_info = get_afreecatv_stream_url(
url=record_url, proxy_addr=proxy_address,
cookies=afreecatv_cookie,
username=afreecatv_username,
password=afreecatv_password
)
else:
logger.warning(f"错误信息: 网络异常请检查本网络是否能正常访问AfreecaTV平台")
elif record_url.find("cc.163.com/") > -1:
platform = '网易CC直播'
@@ -687,21 +813,85 @@ def start_record(url_data: tuple, count_variable: int = -1):
elif record_url.find("qiandurebo.com/") > -1:
platform = '千度热播'
with semaphore:
port_info = get_qiandurebo_stream_data(url=record_url, cookies=qiandurebo_cookie)
port_info = get_qiandurebo_stream_data(
url=record_url, proxy_addr=proxy_address, cookies=qiandurebo_cookie)
elif record_url.find("www.pandalive.co.kr/") > -1:
platform = 'pandaTV'
platform = 'PandaTV'
with semaphore:
port_info = get_pandatv_stream_data(
url=record_url,
proxy_addr=proxy_addr,
cookies=pandatv_cookie
)
if global_proxy or proxy_address:
port_info = get_pandatv_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=pandatv_cookie
)
else:
logger.warning(f"错误信息: 网络异常请检查本网络是否能正常访问PandaTV直播平台")
elif record_url.find("fm.missevan.com/") > -1:
platform = '猫耳FM'
platform = '猫耳FM直播'
with semaphore:
port_info = get_maoerfm_stream_url(url=record_url, cookies=maoerfm_cookie)
port_info = get_maoerfm_stream_url(
url=record_url, proxy_addr=proxy_address, cookies=maoerfm_cookie)
elif record_url.find("www.winktv.co.kr/") > -1:
platform = 'WinkTV'
with semaphore:
if global_proxy or proxy_address:
json_data = get_winktv_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=winktv_cookie)
port_info = get_winktv_stream_url(json_data, record_quality)
else:
logger.warning(f"错误信息: 网络异常请检查本网络是否能正常访问WinkTV直播平台")
elif record_url.find("www.flextv.co.kr/") > -1:
platform = 'FlexTV'
with semaphore:
if global_proxy or proxy_address:
port_info = get_flextv_stream_data(
url=record_url,
proxy_addr=proxy_address,
cookies=flextv_cookie,
username=flextv_username,
password=flextv_password
)
else:
logger.warning(f"错误信息: 网络异常请检查本网络是否能正常访问FlexTV直播平台")
elif record_url.find("look.163.com/") > -1:
platform = 'Look直播'
with semaphore:
port_info = get_looklive_stream_url(
url=record_url, proxy_addr=proxy_address, cookies=look_cookie
)
elif record_url.find("www.popkontv.com/") > -1:
platform = 'PopkonTV'
with semaphore:
if global_proxy or proxy_address:
port_info = get_popkontv_stream_url(
url=record_url,
proxy_addr=proxy_address,
access_token=popkontv_access_token,
username=popkontv_username,
password=popkontv_password,
partner_code=popkontv_partner_code
)
else:
logger.warning(f"错误信息: 网络异常请检查本网络是否能正常访问PopkonTV直播平台")
elif record_url.find("twitcasting.tv/") > -1:
platform = 'TwitCasting'
with semaphore:
port_info = get_twitcasting_stream_url(
url=record_url,
proxy_addr=proxy_address,
cookies=twitcasting_cookie,
username=twitcasting_username,
password=twitcasting_password
)
else:
logger.warning(f'{record_url} 未知直播地址')
@@ -736,19 +926,28 @@ def start_record(url_data: tuple, count_variable: int = -1):
run_once = True
if port_info['is_live'] is False:
print(f"{record_name} 等待直播... ")
print(f"\r{record_name} 等待直播... ")
if start_pushed:
content = f"{record_name} 直播已结束!"
push_pts = push_message(content)
if push_pts:
print(f'提示信息:已经将[{record_name}]直播状态消息推送至你的{push_pts}')
start_pushed = False
else:
content = f"{record_name} 正在直播中..."
content = f"\r{record_name} 正在直播中..."
print(content)
# 推送通知
if live_status_push:
if '微信' in live_status_push:
xizhi(xizhi_api_url, content)
if '钉钉' in live_status_push:
dingtalk(dingtalk_api_url, content, dingtalk_phone_num)
if 'TG' in live_status_push:
tg_bot(tg_chat_id, tg_token, content)
if live_status_push and not start_pushed:
push_pts = push_message(f"{content.split('...')[0]},时间:{datetime.datetime.today()}")
if push_pts:
print(f'提示信息:已经将[{record_name}]直播状态消息推送至你的{push_pts}')
start_pushed = True
if disable_record:
time.sleep(push_check_seconds)
continue
real_url = port_info['record_url']
full_path = f'{default_path}/{platform}/{anchor_name}'
@@ -776,36 +975,44 @@ def start_record(url_data: tuple, count_variable: int = -1):
user_agent = ("Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 ("
"KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile "
"Safari/537.36")
analyzeduration = "20000000"
probesize = "10000000"
bufsize = "8000k"
max_muxing_queue_size = "1024"
for pt_host in overseas_platform_host:
if pt_host in record_url:
analyzeduration = "40000000"
probesize = "20000000"
bufsize = "15000k"
max_muxing_queue_size = "2048"
break
ffmpeg_command = [
ffmpeg_path, "-y",
"-v", "verbose",
"-rw_timeout", "30000000", # 改为30s
"-rw_timeout", "30000000",
"-loglevel", "error",
"-hide_banner",
"-user_agent", user_agent,
"-protocol_whitelist", "rtmp,crypto,file,http,https,tcp,tls,udp,rtp",
"-thread_queue_size", "512",
"-analyzeduration", "5000000",
"-probesize", "10000000",
"-thread_queue_size", "1024",
"-analyzeduration", analyzeduration,
"-probesize", probesize,
"-fflags", "+discardcorrupt",
"-i", real_url,
"-bufsize", "9000k", # 适当增加输入缓冲区大小
"-bufsize", bufsize,
"-sn", "-dn",
"-reconnect_delay_max", "60", # 适当增加最大重连延迟
"-reconnect_delay_max", "60",
"-reconnect_streamed", "-reconnect_at_eof",
"-max_muxing_queue_size", "128", # 适当增加输出复用器的最大队列大小
"-max_muxing_queue_size", max_muxing_queue_size,
"-correct_ts_overflow", "1",
]
# 添加代理参数
need_proxy_url = ['tiktok', 'afreecatv', 'pandalive']
for i in need_proxy_url:
if i in real_url:
if use_proxy and proxy_addr != '':
# os.environ["http_proxy"] = proxy_addr
ffmpeg_command.insert(1, "-http_proxy")
ffmpeg_command.insert(2, proxy_addr)
break
if proxy_address:
ffmpeg_command.insert(1, "-http_proxy")
ffmpeg_command.insert(2, proxy_address)
recording.add(record_name)
start_record_time = datetime.datetime.now()
@@ -848,6 +1055,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
save_file_path = f"{full_path}/{anchor_name}_{now}_%03d.mkv"
command = [
"-flags", "global_header",
"-c:v", "copy",
"-c:a", "aac",
"-map", "0",
@@ -868,6 +1076,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
create_var[str(filename_short)].start()
command = [
"-flags", "global_header",
"-map", "0",
"-c:v", "copy",
"-c:a", "copy",
@@ -899,7 +1108,6 @@ def start_record(url_data: tuple, count_variable: int = -1):
"-f", "segment",
"-segment_time", split_time,
"-segment_format", "mp4",
"-movflags", "+faststart",
"-reset_timestamps", "1",
save_file_path,
]
@@ -943,7 +1151,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
ffmpeg_command.extend(command)
_output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT)
if tsconvert_to_m4a:
if ts_to_m4a:
threading.Thread(target=converts_m4a, args=(save_file_path,)).start()
except subprocess.CalledProcessError as e:
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
@@ -965,7 +1173,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
ffmpeg_command.extend(command)
_output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT)
if tsconvert_to_m4a:
if ts_to_m4a:
threading.Thread(target=converts_m4a, args=(save_file_path,)).start()
except subprocess.CalledProcessError as e:
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
@@ -979,7 +1187,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
print(f'{rec_info}/{filename}')
try:
if tsconvert_to_mp4:
if ts_to_mp4:
save_path_name = f"{full_path}/{anchor_name}_{now}_%03d.mp4"
audio_code = 'aac'
segment_format = 'mp4'
@@ -1034,9 +1242,9 @@ def start_record(url_data: tuple, count_variable: int = -1):
ffmpeg_command.extend(command)
_output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT)
if tsconvert_to_mp4:
if ts_to_mp4:
threading.Thread(target=converts_mp4, args=(save_file_path,)).start()
if tsconvert_to_m4a:
if ts_to_m4a:
threading.Thread(target=converts_m4a, args=(save_file_path,)).start()
except subprocess.CalledProcessError as e:
@@ -1178,8 +1386,7 @@ if ffmepg_file_check.find("run") > -1:
# print("ffmpeg存在")
pass
else:
print("重要提示:")
input("检测到ffmpeg不存在,请将ffmpeg.exe放到同目录,或者设置为环境变量,没有ffmpeg将无法录制")
input("重要提示:\n\r检测到ffmpeg不存在,请将ffmpeg.exe放到同目录,或者设置为环境变量,没有ffmpeg将无法录制")
sys.exit(0)
# --------------------------初始化程序-------------------------------------
@@ -1187,9 +1394,9 @@ print("-----------------------------------------------------")
print("| DouyinLiveRecorder |")
print("-----------------------------------------------------")
print(f"版本号{version}")
print("Githubhttps://github.com/ihmily/DouyinLiveRecorder")
print(f'支持平台{platforms}')
print(f"版本号: {version}")
print(f"GitHub: https://github.com/ihmily/DouyinLiveRecorder")
print(f'支持平台: {platforms}')
print('.....................................................')
if not os.path.exists('./config'):
@@ -1199,16 +1406,21 @@ if not os.path.exists('./config'):
t3 = threading.Thread(target=backup_file_start, args=(), daemon=True)
t3.start()
# 录制tiktok时如果开启了电脑全局/规则代理,可以不用再在配置文件中填写代理地址
# 但强烈建议还是配置一下代理地址,否则非常不稳定
try:
# 检测电脑是否开启了全局/规则代理
# 录制国外平台时,如果开启了电脑全局/规则代理,可以正常录制,但强烈建议还是配置一下代理地址,否则非常不稳定
# 检测电脑是否开启了全局/规则代理(如果身处国外请忽略)
print('系统代理检测中,请耐心等待...')
response_g = urllib.request.urlopen("https://www.google.com/", timeout=15)
global_proxy = True
print('全局/规则网络代理已开启√ 注意:配置文件中的代理设置也要开启才会生效哦!')
except Exception:
print('INFO未检测到全局/规则网络代理请检查代理配置若无需录制TikTok/AfreecaTV直播请忽略此条提示')
print('\r全局/规则网络代理已开启√')
except HTTPError as err:
print(f"HTTP error occurred: {err.code} - {err.reason}")
except URLError as err:
# print("URLError:", err.reason)
print('INFO未检测到全局/规则网络代理,请检查代理配置(若无需录制海外直播请忽略此条提示)')
except Exception as err:
print("An unexpected error occurred:", err)
def read_config_value(config_parser: configparser.RawConfigParser, section: str, option: str, default_value: Any) -> (
@@ -1222,6 +1434,10 @@ def read_config_value(config_parser: configparser.RawConfigParser, section: str,
config_parser.add_section('推送配置')
if 'Cookie' not in config_parser.sections():
config_parser.add_section('Cookie')
if 'Authorization' not in config_parser.sections():
config_parser.add_section('Authorization')
if '账号密码' not in config_parser.sections():
config_parser.add_section('账号密码')
return config_parser.get(section, option)
except (configparser.NoSectionError, configparser.NoOptionError):
config_parser.set(section, option, str(default_value))
@@ -1255,29 +1471,47 @@ while True:
file.write(input_url)
video_save_path = read_config_value(config, '录制设置', '直播保存路径(不填则默认)', "")
video_save_type = read_config_value(config, '录制设置', '视频保存格式TS|MKV|FLV|MP4|TS音频|MKV音频', "mp4")
video_save_type = read_config_value(config, '录制设置', '视频保存格式TS|MKV|FLV|MP4|TS音频|MKV音频', "ts")
video_record_quality = read_config_value(config, '录制设置', '原画|超清|高清|标清', "原画")
use_proxy = options.get(read_config_value(config, '录制设置', '是否使用代理ip是/否)', ""), False)
proxy_addr = read_config_value(config, '录制设置', '代理地址', "")
proxy_addr_bak = read_config_value(config, '录制设置', '代理地址', "")
proxy_addr = None if not use_proxy else proxy_addr_bak
max_request = int(read_config_value(config, '录制设置', '同一时间访问网络的线程数', 3))
semaphore = threading.Semaphore(max_request)
delay_default = int(read_config_value(config, '录制设置', '循环时间(秒)', 120))
local_delay_default = int(read_config_value(config, '录制设置', '排队读取网址时间(秒)', 0))
loop_time = options.get(read_config_value(config, '录制设置', '是否显示循环秒数', ""), False)
split_video_by_time = options.get(read_config_value(config, '录制设置', '分段录制是否开启', ""), False)
split_time = str(read_config_value(config, '录制设置', '视频分段时间(秒)', 3600))
tsconvert_to_mp4 = options.get(read_config_value(config, '录制设置', 'TS录制完成后自动转为mp4格式', ""),
False)
tsconvert_to_m4a = options.get(read_config_value(config, '录制设置', 'TS录制完成后自动增加生成m4a格式', ""),
False)
split_time = str(read_config_value(config, '录制设置', '视频分段时间(秒)', 1800))
ts_to_mp4 = options.get(read_config_value(config, '录制设置', 'TS录制完成后自动转为mp4格式', ""),
False)
ts_to_m4a = options.get(read_config_value(config, '录制设置', 'TS录制完成后自动增加生成m4a格式', ""),
False)
delete_origin_file = options.get(read_config_value(config, '录制设置', '追加格式后删除原文件', ""), False)
create_time_file = options.get(read_config_value(config, '录制设置', '生成时间文件', ""), False)
enable_proxy_platform = read_config_value(config, '录制设置', '使用代理录制的平台(逗号分隔)',
'tiktok, afreecatv, pandalive, winktv, flextv, popkontv')
enable_proxy_platform_list = enable_proxy_platform.replace('', ',').split(',') if enable_proxy_platform else None
extra_enable_proxy = read_config_value(config, '录制设置', '额外使用代理录制的平台(逗号分隔)', '')
extra_enable_proxy_platform_list = extra_enable_proxy.replace('', ',').split(',') if extra_enable_proxy else None
live_status_push = read_config_value(config, '推送配置', '直播状态通知(可选微信|钉钉|TG或者都填)', "")
dingtalk_api_url = read_config_value(config, '推送配置', '钉钉推送接口链接', "")
xizhi_api_url = read_config_value(config, '推送配置', '微信推送接口链接', "")
dingtalk_phone_num = read_config_value(config, '推送配置', '钉钉通知@对象(填手机号)', "")
tg_token = read_config_value(config, '推送配置', 'TGAPI令牌', "")
tg_chat_id = read_config_value(config, '推送配置', 'TG聊天ID(个人或者群组ID)', "")
disable_record = options.get(read_config_value(config, '推送配置', '只推送通知不录制(是/否)', ""), False)
push_check_seconds = int(read_config_value(config, '推送配置', '直播推送检测频率(秒)', 1800))
afreecatv_username = read_config_value(config, '账号密码', 'afreecatv账号', '')
afreecatv_password = read_config_value(config, '账号密码', 'afreecatv密码', '')
flextv_username = read_config_value(config, '账号密码', 'flextv账号', '')
flextv_password = read_config_value(config, '账号密码', 'flextv密码', '')
popkontv_username = read_config_value(config, '账号密码', 'popkontv账号', '')
popkontv_partner_code = read_config_value(config, '账号密码', 'partner_code', 'P-00001')
popkontv_password = read_config_value(config, '账号密码', 'popkontv密码', '')
twitcasting_username = read_config_value(config, '账号密码', 'twitcasting账号', '')
twitcasting_password = read_config_value(config, '账号密码', 'twitcasting密码', '')
popkontv_access_token = read_config_value(config, 'Authorization', 'popkontv_token', '')
dy_cookie = read_config_value(config, 'Cookie', '抖音cookie(录制抖音必须要有)', '')
ks_cookie = read_config_value(config, 'Cookie', '快手cookie', '')
tiktok_cookie = read_config_value(config, 'Cookie', 'tiktok_cookie', '')
@@ -1293,6 +1527,10 @@ while True:
qiandurebo_cookie = read_config_value(config, 'Cookie', '千度热播_cookie', '')
pandatv_cookie = read_config_value(config, 'Cookie', 'pandatv_cookie', '')
maoerfm_cookie = read_config_value(config, 'Cookie', '猫耳FM_cookie', '')
winktv_cookie = read_config_value(config, 'Cookie', 'winktv_cookie', '')
flextv_cookie = read_config_value(config, 'Cookie', 'flextv_cookie', '')
look_cookie = read_config_value(config, 'Cookie', 'look_cookie', '')
twitcasting_cookie = read_config_value(config, 'Cookie', 'twitcasting_cookie', '')
if len(video_save_type) > 0:
if video_save_type.upper().lower() == "FLV".lower():
@@ -1360,31 +1598,44 @@ while True:
url = 'https://' + url
url_host = url.split('/')[2]
host_list = [
platform_host = [
'live.douyin.com',
'v.douyin.com',
'www.tiktok.com',
'live.kuaishou.com',
'www.huya.com',
'www.douyu.com',
'www.yy.com',
'live.bilibili.com',
'www.redelight.cn',
'www.xiaohongshu.com',
'www.bigo.tv',
'app.blued.cn',
'play.afreecatv.com',
'm.afreecatv.com',
'cc.163.com',
'qiandurebo.com',
'fm.missevan.com',
'look.163.com',
'twitcasting.tv',
]
overseas_platform_host = [
'www.tiktok.com',
'play.afreecatv.com',
'm.afreecatv.com',
'www.pandalive.co.kr',
'fm.missevan.com'
'www.winktv.co.kr',
'www.flextv.co.kr',
'www.popkontv.com'
]
if url_host in host_list:
platform_host.extend(overseas_platform_host)
if url_host in platform_host:
if url_host in ['live.douyin.com', 'live.bilibili.com']:
update_file(url_config_file, url, url.split('?')[0])
url = url.split('?')[0]
new_line = (quality, url, name)
url_tuples_list.append(new_line)
else:
print(f"{url} 未知链接.此条跳过")
print(f"\r{url} 未知链接.此条跳过")
update_file(url_config_file, url, url, start_str='#')
while len(name_list):
@@ -1409,7 +1660,7 @@ while True:
if url_tuple[1] not in runing_list:
if not first_start:
print(f"新增链接: {url_tuple[1]}")
print(f"\r新增链接: {url_tuple[1]}")
monitoring += 1
args = [url_tuple, monitoring]
# TODO: 执行开始录制的操作

View File

@@ -1,3 +1,4 @@
requests
PyExecJS
loguru==0.7.2
loguru==0.7.2
pycryptodome==3.20.0

1067
spider.py

File diff suppressed because it is too large Load Diff

View File

@@ -4,29 +4,29 @@
Author: Hmily
Github:https://github.com/ihmily
Date: 2023-07-17 23:52:05
Update: 2023-09-07 23:35:00
Update: 2024-03-06 23:35:00
Copyright (c) 2023 by Hmily, All Rights Reserved.
"""
import json
import re
import urllib.parse
from typing import Union
import execjs # pip install PyExecJS
import requests
import urllib.request
no_proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(no_proxy_handler)
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Cookie': 's_v_web_id=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf'
}
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Cookie': 's_v_web_id=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf'
}
# X-bogus算法
def get_xbogus(url) -> str:
def get_xbogus(url: str) -> str:
query = urllib.parse.urlparse(url).query
xbogus = execjs.compile(open('./x-bogus.js').read()).call('sign', query, headers["User-Agent"])
# print(xbogus)
@@ -34,38 +34,46 @@ def get_xbogus(url) -> str:
# 获取房间ID和用户secID
def get_sec_user_id(url):
response = opener.open(url, timeout=15)
def get_sec_user_id(url: str, proxy_addr: Union[str, None] = None):
if proxy_addr:
proxies = {
'http': proxy_addr,
'https': proxy_addr
}
response = requests.get(url, headers=headers, proxies=proxies, timeout=15)
else:
response = opener.open(url, timeout=15)
redirect_url = response.url
sec_user_id=re.search(r'sec_user_id=([\w\d_\-]+)&',redirect_url).group(1)
room_id=redirect_url.split('?')[0].rsplit('/',maxsplit=1)[1]
return room_id,sec_user_id
sec_user_id = re.search(r'sec_user_id=([\w\d_\-]+)&', redirect_url).group(1)
room_id = redirect_url.split('?')[0].rsplit('/', maxsplit=1)[1]
return room_id, sec_user_id
# 获取直播间webID
def get_live_room_id(room_id,sec_user_id):
url= f'https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf&type_id=0&live_id=1&room_id={room_id}&sec_user_id={sec_user_id}&app_id=1128&msToken=wrqzbEaTlsxt52-vxyZo_mIoL0RjNi1ZdDe7gzEGMUTVh_HvmbLLkQrA_1HKVOa2C6gkxb6IiY6TY2z8enAkPEwGq--gM-me3Yudck2ailla5Q4osnYIHxd9dI4WtQ=='
def get_live_room_id(room_id: str, sec_user_id: str, proxy_addr: Union[str, None] = None) -> str:
url = f'https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf&type_id=0&live_id=1&room_id={room_id}&sec_user_id={sec_user_id}&app_id=1128&msToken=wrqzbEaTlsxt52-vxyZo_mIoL0RjNi1ZdDe7gzEGMUTVh_HvmbLLkQrA_1HKVOa2C6gkxb6IiY6TY2z8enAkPEwGq--gM-me3Yudck2ailla5Q4osnYIHxd9dI4WtQ=='
xbogus = get_xbogus(url) # 获取X-Bogus算法
url = url + "&X-Bogus=" + xbogus
# response = requests.get(url,headers=headers)
# json_data=response.json()
# 通通改成用urlib库防止同时录制Tiktok直播时代理影响requests请求出错
req = urllib.request.Request(url, headers=headers)
response = opener.open(req, timeout=15)
html_str = response.read().decode('utf-8')
json_data = json.loads(html_str)
web_rid=json_data['data']['room']['owner']['web_rid']
return web_rid
if proxy_addr:
proxies = {
'http': proxy_addr,
'https': proxy_addr
}
response = requests.get(url, headers=headers, proxies=proxies, timeout=15)
json_str = response.text
else:
req = urllib.request.Request(url, headers=headers)
response = opener.open(req, timeout=15)
json_str = response.read().decode('utf-8')
json_data = json.loads(json_str)
return json_data['data']['room']['owner']['web_rid']
if __name__ == '__main__':
url="https://v.douyin.com/iQLgKSj/"
url = "https://v.douyin.com/iQLgKSj/"
# url="https://v.douyin.com/iQFeBnt/"
# url="https://v.douyin.com/iehvKttp/"
room_id,sec_user_id = get_sec_user_id(url)
web_rid=get_live_room_id(room_id,sec_user_id)
room_id, sec_user_id = get_sec_user_id(url)
web_rid = get_live_room_id(room_id, sec_user_id)
print(web_rid)