mirror of
https://github.com/ihmily/DouyinLiveRecorder.git
synced 2025-12-26 05:48:32 +08:00
feat: add baidu and weibo live record, fix douyu record
This commit is contained in:
parent
7f1618b7de
commit
a7e0a5779f
19
README.md
19
README.md
@ -34,6 +34,8 @@
|
||||
- [x] FlexTV
|
||||
- [x] PopkonTV
|
||||
- [x] TwitCasting
|
||||
- [x] 百度直播
|
||||
- [x] 微博直播
|
||||
- [ ] 更多平台正在更新中
|
||||
|
||||
</div>
|
||||
@ -148,6 +150,12 @@ https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117
|
||||
|
||||
TwitCasting:
|
||||
https://twitcasting.tv/c:uonq
|
||||
|
||||
百度直播:
|
||||
https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category
|
||||
|
||||
微博直播:
|
||||
https://weibo.com/l/wblive/p/show/1022:2321325026370190442592
|
||||
```
|
||||
|
||||
直播间分享地址和网页端长地址都能正常进行录制(抖音尽量用长链接,避免因短链接转换失效导致不能正常录制,而且需要有nodejs环境,否则无法转换)。
|
||||
@ -280,11 +288,18 @@ docker-compose stop
|
||||
|
||||
## ⏳提交日志
|
||||
|
||||
- 20240423
|
||||
- 新增百度直播录制、微博直播录制
|
||||
|
||||
- 修复斗鱼录制直播回放的问题
|
||||
|
||||
- 新增直播源地址显示以及输出到日志文件设置
|
||||
|
||||
- 20240311
|
||||
- 修复海外平台录制bug,增加画质选择,增强录制稳定性
|
||||
|
||||
|
||||
- 修复虎牙录制bug (虎牙`一起看`频道 有特殊限制,有时无法录制)
|
||||
|
||||
|
||||
- 20240309
|
||||
- 修复虎牙直播、小红书直播和B站直播录制
|
||||
- 新增5个直播平台录制,包括winktv、flextv、look、popkontv、twitcasting
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
循环时间(秒) = 120
|
||||
排队读取网址时间(秒) = 0
|
||||
是否显示循环秒数 = 否
|
||||
是否显示直播源地址 = 否
|
||||
分段录制是否开启 = 否
|
||||
视频分段时间(秒) = 1800
|
||||
ts录制完成后自动转为mp4格式 = 否
|
||||
@ -50,6 +51,8 @@ winktv_cookie =
|
||||
flextv_cookie =
|
||||
look_cookie =
|
||||
twitcasting_cookie =
|
||||
baidu_cookie =
|
||||
weibo_cookie =
|
||||
|
||||
[Authorization]
|
||||
popkontv_token =
|
||||
|
||||
22
logger.py
22
logger.py
@ -2,5 +2,23 @@
|
||||
|
||||
from loguru import logger
|
||||
|
||||
# 每天日志自动分文件
|
||||
logger.add("./logs/DouyinLiveRecorder.log", rotation="12:00")
|
||||
logger.add(
|
||||
"./logs/PlayURL.log",
|
||||
level="INFO",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {message}",
|
||||
filter=lambda i: i["level"].name == "INFO",
|
||||
serialize=False,
|
||||
enqueue=True,
|
||||
rotation="12:00",
|
||||
retention="10 days",
|
||||
)
|
||||
|
||||
logger.add(
|
||||
"./logs/DouyinLiveRecorder.log",
|
||||
level="WARNING",
|
||||
serialize=False,
|
||||
enqueue=True,
|
||||
rotation="12:00",
|
||||
retention="10 days",
|
||||
)
|
||||
|
||||
|
||||
130
main.py
130
main.py
@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub: https://github.com/ihmily
|
||||
Date: 2023-07-17 23:52:05
|
||||
Update: 2024-04-12 18:54:27
|
||||
Update: 2024-04-23 21:34:55
|
||||
Copyright (c) 2023-2024 by Hmily, All Rights Reserved.
|
||||
Function: Record live stream video.
|
||||
"""
|
||||
@ -49,7 +49,9 @@ from spider import (
|
||||
get_flextv_stream_data,
|
||||
get_looklive_stream_url,
|
||||
get_popkontv_stream_url,
|
||||
get_twitcasting_stream_url
|
||||
get_twitcasting_stream_url,
|
||||
get_baidu_stream_data,
|
||||
get_weibo_stream_url
|
||||
)
|
||||
|
||||
from web_rid import (
|
||||
@ -62,8 +64,8 @@ from utils import (
|
||||
)
|
||||
from msg_push import dingtalk, xizhi, tg_bot
|
||||
|
||||
version = "v3.0.2"
|
||||
platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|网易CC|千度热播|猫耳FM|Look直播|TwitCasting" \
|
||||
version = "v3.0.3"
|
||||
platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|网易CC|千度热播|猫耳FM|Look直播|TwitCasting|百度直播|微博直播" \
|
||||
"\n海外站点:TikTok|AfreecaTV|PandaTV|WinkTV|FlexTV|PopkonTV"
|
||||
|
||||
# --------------------------全局变量-------------------------------------
|
||||
@ -153,7 +155,7 @@ def display_info():
|
||||
else:
|
||||
start_display_time = now_time
|
||||
except Exception as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
|
||||
def update_file(file_path: str, old_str: str, new_str: str, start_str: str = None):
|
||||
@ -484,6 +486,9 @@ def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
@trace_error_decorator
|
||||
def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str, proxy_address: str) -> dict:
|
||||
# TODO: 获取斗鱼直播源地址
|
||||
if not json_data["is_live"]:
|
||||
return json_data
|
||||
|
||||
video_quality_options = {
|
||||
"原画": '0',
|
||||
"蓝光": '0',
|
||||
@ -492,25 +497,15 @@ def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str, prox
|
||||
"标清": '1'
|
||||
}
|
||||
|
||||
room_info = json_data.get('pageContext', json_data)['pageProps']['room']['roomInfo']['roomInfo']
|
||||
anchor_name = room_info.get('nickname', '')
|
||||
status = room_info.get('isLive', False)
|
||||
result = {
|
||||
"anchor_name": anchor_name,
|
||||
"is_live": False,
|
||||
}
|
||||
# 如果status值为1,则正在直播
|
||||
# 这边有个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=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
|
||||
return result
|
||||
rid = str(json_data["room_id"])
|
||||
json_data.pop("room_id", None)
|
||||
rate = video_quality_options.get(video_quality, '0') # 默认为原画,只有登录后才能获取最高画质,需要配置cookie
|
||||
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:
|
||||
json_data['flv_url'] = flv_url
|
||||
json_data['record_url'] = flv_url
|
||||
return json_data
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
@ -599,6 +594,7 @@ def get_bilibili_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
|
||||
@trace_error_decorator
|
||||
def get_afreecatv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取afreecatv直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
@ -620,6 +616,7 @@ def get_afreecatv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
|
||||
@trace_error_decorator
|
||||
def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取netease直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
stream_list = json_data['stream_list']['resolution']
|
||||
@ -642,6 +639,7 @@ def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
|
||||
@trace_error_decorator
|
||||
def get_pandatv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取pandatv直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
@ -663,6 +661,7 @@ def get_pandatv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
|
||||
@trace_error_decorator
|
||||
def get_winktv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取winktv直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
@ -684,6 +683,7 @@ def get_winktv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
|
||||
@trace_error_decorator
|
||||
def get_flextv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取flextv直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
@ -703,6 +703,28 @@ def get_flextv_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_baidu_stream_url(json_data: dict, video_quality: str) -> dict:
|
||||
# TODO: 获取百度直播源地址
|
||||
if not json_data['is_live']:
|
||||
return json_data
|
||||
|
||||
play_url_list = json_data['play_url_list']
|
||||
quality_list = {'原画': 0, '蓝光': 0, '超清': 1, '高清': 2, '标清': 3}
|
||||
while len(play_url_list) < 4:
|
||||
play_url_list.append(play_url_list[-1])
|
||||
|
||||
selected_quality = quality_list[video_quality]
|
||||
m3u8_url = play_url_list[selected_quality]
|
||||
|
||||
return {
|
||||
"anchor_name": json_data['anchor_name'],
|
||||
"is_live": True,
|
||||
"m3u8_url": m3u8_url,
|
||||
"record_url": m3u8_url
|
||||
}
|
||||
|
||||
|
||||
def push_message(content: str):
|
||||
push_pts = []
|
||||
if '微信' in live_status_push:
|
||||
@ -794,7 +816,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
cookies=tiktok_cookie)
|
||||
port_info = get_tiktok_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查网络是否能正常访问TikTok平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查网络是否能正常访问TikTok平台")
|
||||
|
||||
elif record_url.find("https://live.kuaishou.com/") > -1:
|
||||
platform = '快手直播'
|
||||
@ -869,7 +891,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
)
|
||||
port_info = get_afreecatv_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问AfreecaTV平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问AfreecaTV平台")
|
||||
|
||||
elif record_url.find("cc.163.com/") > -1:
|
||||
platform = '网易CC直播'
|
||||
@ -894,7 +916,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
)
|
||||
port_info = get_pandatv_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问PandaTV直播平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问PandaTV直播平台")
|
||||
|
||||
elif record_url.find("fm.missevan.com/") > -1:
|
||||
platform = '猫耳FM直播'
|
||||
@ -912,7 +934,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
cookies=winktv_cookie)
|
||||
port_info = get_winktv_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问WinkTV直播平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问WinkTV直播平台")
|
||||
|
||||
elif record_url.find("www.flextv.co.kr/") > -1:
|
||||
platform = 'FlexTV'
|
||||
@ -927,7 +949,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
)
|
||||
port_info = get_flextv_stream_url(json_data, record_quality)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问FlexTV直播平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问FlexTV直播平台")
|
||||
|
||||
elif record_url.find("look.163.com/") > -1:
|
||||
platform = 'Look直播'
|
||||
@ -949,7 +971,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
partner_code=popkontv_partner_code
|
||||
)
|
||||
else:
|
||||
logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问PopkonTV直播平台")
|
||||
logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问PopkonTV直播平台")
|
||||
|
||||
elif record_url.find("twitcasting.tv/") > -1:
|
||||
platform = 'TwitCasting'
|
||||
@ -962,8 +984,23 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
password=twitcasting_password
|
||||
)
|
||||
|
||||
elif record_url.find("live.baidu.com/") > -1:
|
||||
platform = '百度直播'
|
||||
with semaphore:
|
||||
json_data = get_baidu_stream_data(
|
||||
url=record_url,
|
||||
proxy_addr=proxy_address,
|
||||
cookies=baidu_cookie)
|
||||
port_info = get_baidu_stream_url(json_data, record_quality)
|
||||
|
||||
elif record_url.find("weibo.com/") > -1:
|
||||
platform = '微博直播'
|
||||
with semaphore:
|
||||
port_info = get_weibo_stream_url(
|
||||
url=record_url, proxy_addr=proxy_address, cookies=weibo_cookie)
|
||||
|
||||
else:
|
||||
logger.warning(f'{record_url} 未知直播地址')
|
||||
logger.error(f'{record_url} 未知直播地址')
|
||||
return
|
||||
|
||||
if anchor_name:
|
||||
@ -1037,10 +1074,10 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
if not os.path.exists(full_path):
|
||||
os.makedirs(full_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
logger.warning(
|
||||
logger.error(
|
||||
"错误信息: 保存路径不存在,不能生成录制.请避免把本程序放在c盘,桌面,下载文件夹,qq默认传输目录.请重新检查设置")
|
||||
|
||||
user_agent = ("Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 ("
|
||||
@ -1090,6 +1127,8 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
recording_time_list[record_name] = [start_record_time, record_quality]
|
||||
rec_info = f"\r{anchor_name} 录制视频中: {full_path}"
|
||||
filename_short = full_path + '/' + anchor_name + '_' + now
|
||||
if show_url:
|
||||
logger.info(f"{platform} | {anchor_name} | 直播源地址: {port_info['record_url']}")
|
||||
|
||||
if video_save_type == "FLV":
|
||||
filename = anchor_name + '_' + now + '.flv'
|
||||
@ -1112,7 +1151,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
raise Exception('该直播无flv直播流,请切换视频保存类型')
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1159,7 +1198,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
_output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT)
|
||||
record_finished = True
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1203,7 +1242,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
_output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT)
|
||||
record_finished = True
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1255,7 +1294,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
threading.Thread(target=converts_m4a, args=(save_file_path,)).start()
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1304,7 +1343,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
threading.Thread(target=converts_m4a, args=(save_file_path,)).start()
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1339,7 +1378,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
record_finished = True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(
|
||||
logger.error(
|
||||
f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
@ -1375,7 +1414,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
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}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
no_error = False
|
||||
|
||||
@ -1397,7 +1436,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
record_finished_2 = False
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
|
||||
num = random.randint(-5, 5) + delay_default # 生成-5到5的随机数,加上delay_default
|
||||
@ -1430,7 +1469,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
if loop_time:
|
||||
print('\r检测直播间中...', end="")
|
||||
except Exception as e:
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
warning_count += 1
|
||||
time.sleep(2)
|
||||
|
||||
@ -1597,6 +1636,7 @@ while True:
|
||||
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)
|
||||
show_url = 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, '录制设置', '视频分段时间(秒)', 1800))
|
||||
ts_to_mp4 = options.get(read_config_value(config, '录制设置', 'ts录制完成后自动转为mp4格式', "否"),
|
||||
@ -1651,6 +1691,8 @@ while True:
|
||||
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', '')
|
||||
baidu_cookie = read_config_value(config, 'Cookie', 'baidu_cookie', '')
|
||||
weibo_cookie = read_config_value(config, 'Cookie', 'weibo_cookie', '')
|
||||
|
||||
if len(video_save_type) > 0:
|
||||
if video_save_type.upper().lower() == "FLV".lower():
|
||||
@ -1735,6 +1777,8 @@ while True:
|
||||
'fm.missevan.com',
|
||||
'look.163.com',
|
||||
'twitcasting.tv',
|
||||
'live.baidu.com',
|
||||
'weibo.com',
|
||||
]
|
||||
overseas_platform_host = [
|
||||
'www.tiktok.com',
|
||||
@ -1793,7 +1837,7 @@ while True:
|
||||
first_start = False
|
||||
|
||||
except Exception as err:
|
||||
logger.warning(f"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}")
|
||||
logger.error(f"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}")
|
||||
|
||||
if first_run:
|
||||
t = threading.Thread(target=display_info, args=(), daemon=True)
|
||||
|
||||
157
spider.py
157
spider.py
@ -4,12 +4,13 @@
|
||||
Author: Hmily
|
||||
GitHub:https://github.com/ihmily
|
||||
Date: 2023-07-15 23:15:00
|
||||
Update: 2024-04-12 19:14:00
|
||||
Update: 2024-04-23 21:14:21
|
||||
Copyright (c) 2023 by Hmily, All Rights Reserved.
|
||||
Function: Get live stream data.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
@ -27,21 +28,19 @@ from utils import (
|
||||
)
|
||||
import http.cookiejar
|
||||
|
||||
|
||||
no_proxy_handler = urllib.request.ProxyHandler({})
|
||||
opener = urllib.request.build_opener(no_proxy_handler)
|
||||
|
||||
|
||||
def get_req(
|
||||
url: str,
|
||||
proxy_addr: Union[str, None] = None,
|
||||
headers: Union[dict, None] = None,
|
||||
data: Union[dict, bytes, None] = None,
|
||||
json_data: dict = None,
|
||||
timeout: int = 20,
|
||||
abroad: bool = False
|
||||
url: str,
|
||||
proxy_addr: Union[str, None] = None,
|
||||
headers: Union[dict, None] = None,
|
||||
data: Union[dict, bytes, None] = None,
|
||||
json_data: dict = None,
|
||||
timeout: int = 20,
|
||||
abroad: bool = False
|
||||
) -> Union[str, Any]:
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
try:
|
||||
@ -51,7 +50,8 @@ def get_req(
|
||||
'https': proxy_addr
|
||||
}
|
||||
if data or json_data:
|
||||
response = requests.post(url, data=data, json=json_data, headers=headers, proxies=proxies, timeout=timeout)
|
||||
response = requests.post(url, data=data, json=json_data, headers=headers, proxies=proxies,
|
||||
timeout=timeout)
|
||||
else:
|
||||
response = requests.get(url, headers=headers, proxies=proxies, timeout=timeout)
|
||||
resp_str = response.text
|
||||
@ -89,7 +89,6 @@ def get_req(
|
||||
|
||||
|
||||
def get_partner_code(url, params):
|
||||
|
||||
parsed_url = urllib.parse.urlparse(url)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
|
||||
@ -99,6 +98,19 @@ def get_partner_code(url, params):
|
||||
return None
|
||||
|
||||
|
||||
def jsonp_to_json(jsonp_str):
|
||||
pattern = r'(\w+)\((.*)\);?$'
|
||||
match = re.search(pattern, jsonp_str)
|
||||
|
||||
if match:
|
||||
_, json_str = match.groups()
|
||||
json_obj = json.loads(json_str)
|
||||
return json_obj
|
||||
else:
|
||||
print("No JSON data found in JSONP response.")
|
||||
return None
|
||||
|
||||
|
||||
def get_play_url_list(m3u8: str, proxy: Union[str, None] = None, header: Union[dict, None] = None) -> list:
|
||||
resp = get_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=True)
|
||||
play_url_list = []
|
||||
@ -321,15 +333,21 @@ def get_douyu_info_data(url: str, proxy_addr: Union[str, None] = None) -> Dict[s
|
||||
else:
|
||||
rid = re.search('douyu.com/(.*?)(?=\?|$)', url).group(1)
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
||||
'referer': 'https://www.douyu.com/7644887?dyshid=0-40f7c4a06aae9dc5bede316000031701&dyshci=181',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',
|
||||
}
|
||||
url2 = f'https://m.douyu.com/{rid}'
|
||||
html_str = get_req(url=url2, proxy_addr=proxy_addr, headers=headers)
|
||||
|
||||
json_str = re.search('<script id="vike_pageContext" type="application/json">(.*?)</script>',
|
||||
html_str).group(1)
|
||||
url2 = f'https://www.douyu.com/betard/{rid}'
|
||||
json_str = get_req(url=url2, proxy_addr=proxy_addr, headers=headers)
|
||||
json_data = json.loads(json_str)
|
||||
return json_data
|
||||
result = {
|
||||
"anchor_name": json_data['room']['nickname'],
|
||||
"is_live": False
|
||||
}
|
||||
if json_data['room']['videoLoop'] == 0 and json_data['room']['show_status'] ==1:
|
||||
result["is_live"] = True
|
||||
result["room_id"] = json_data['room']['room_id']
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
@ -1329,7 +1347,8 @@ def get_popkontv_stream_url(
|
||||
status_msg = json_data["statusMsg"]
|
||||
if json_data['statusCd'] == "L000A":
|
||||
print('获取直播源失败,', status_msg)
|
||||
raise RuntimeError('你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用')
|
||||
raise RuntimeError(
|
||||
'你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用')
|
||||
elif json_data['statusCd'] == "L0001":
|
||||
cast_start_date_code = int(cast_start_date_code) - 1
|
||||
json_str = fetch_data(headers, partner_code)
|
||||
@ -1350,7 +1369,6 @@ def get_popkontv_stream_url(
|
||||
def login_twitcasting(
|
||||
username: str, password: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None
|
||||
) -> Union[str, None]:
|
||||
|
||||
headers = {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
@ -1408,7 +1426,6 @@ def get_twitcasting_stream_url(
|
||||
username: Union[str, None] = None,
|
||||
password: Union[str, None] = None,
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
headers = {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
@ -1423,7 +1440,7 @@ def get_twitcasting_stream_url(
|
||||
|
||||
def get_data(header):
|
||||
html_str = get_req(url, proxy_addr=proxy_addr, headers=header)
|
||||
anchor = re.search("<title>(.*?)\(@(.*?)\) 's Live - Twit",html_str)
|
||||
anchor = re.search("<title>(.*?)\(@(.*?)\) 's Live - Twit", html_str)
|
||||
status = re.search('data-is-onlive="(.*?)"\n\s+data-view-mode', html_str)
|
||||
movie_id = re.search('data-movie-id="(.*?)" data-audience-id', html_str)
|
||||
return f'{anchor.group(1).strip()}-{anchor.group(2)}-{movie_id.group(1)}', status.group(1)
|
||||
@ -1451,6 +1468,94 @@ def get_twitcasting_stream_url(
|
||||
return result
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_baidu_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \
|
||||
Dict[str, Any]:
|
||||
headers = {
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://live.baidu.com/',
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0',
|
||||
}
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
|
||||
uid = random.choice([
|
||||
'h5-683e85bdf741bf2492586f7ca39bf465',
|
||||
'h5-c7c6dc14064a136be4215b452fab9eea',
|
||||
'h5-4581281f80bb8968bd9a9dfba6050d3a'
|
||||
])
|
||||
room_id = re.search('room_id=(.*?)&', url).group(1)
|
||||
params = {
|
||||
'cmd': '371',
|
||||
'action': 'star',
|
||||
'service': 'bdbox',
|
||||
'osname': 'baiduboxapp',
|
||||
'data': '{"data":{"room_id":"' + room_id + '","device_id":"h5-683e85bdf741bf2492586f7ca39bf465","source_type":0,"osname":"baiduboxapp"},"replay_slice":0,"nid":"","schemeParams":{"src_pre":"pc","src_suf":"other","bd_vid":"","share_uid":"","share_cuk":"","share_ecid":"","zb_tag":"","shareTaskInfo":"{\\"room_id\\":\\"9175031377\\"}","share_from":"","ext_params":"","nid":""}}',
|
||||
'ua': '360_740_ANDROID_0',
|
||||
'bd_vid': '',
|
||||
'uid': uid,
|
||||
'_': str(int(time.time() * 1000)),
|
||||
'callback': '__jsonp_callback_1__',
|
||||
}
|
||||
app_api = f'https://mbd.baidu.com/searchbox?{urllib.parse.urlencode(params)}'
|
||||
jsonp_str = get_req(url=app_api, proxy_addr=proxy_addr, headers=headers)
|
||||
json_data = jsonp_to_json(jsonp_str)
|
||||
key = list(json_data['data'].keys())[0]
|
||||
data = json_data['data'][key]
|
||||
anchor_name = data['host']['name']
|
||||
result = {
|
||||
"anchor_name": anchor_name,
|
||||
"is_live": False,
|
||||
}
|
||||
live_status = data['video']['stream']
|
||||
if live_status == 1:
|
||||
play_url_list = data['video']['url_clarity_list']
|
||||
url_list = []
|
||||
prefix = 'https://hls.liveshow.bdstatic.com/live/'
|
||||
for i in play_url_list:
|
||||
url_list.append(prefix + i['urls']['flv'].rsplit('.', maxsplit=1)[0].rsplit('/', maxsplit=1)[1]+'.m3u8')
|
||||
if play_url_list:
|
||||
result['play_url_list'] = url_list
|
||||
result['is_live'] = True
|
||||
return result
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_weibo_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \
|
||||
Dict[str, Any]:
|
||||
headers = {
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://live.baidu.com/',
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0',
|
||||
}
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
|
||||
room_id = url.split('?')[0].split('show/')[1]
|
||||
|
||||
app_api = f'https://weibo.com/l/pc/anchor/live?live_id={room_id}'
|
||||
# app_api = f'https://weibo.com/l/!/2/wblive/room/show_pc_live.json?live_id={room_id}'
|
||||
json_str = get_req(url=app_api, proxy_addr=proxy_addr, headers=headers)
|
||||
json_data = json.loads(json_str)
|
||||
|
||||
anchor_name = json_data['data']['user_info']['name']
|
||||
result = {
|
||||
"anchor_name": anchor_name,
|
||||
"is_live": False,
|
||||
}
|
||||
live_status = json_data['data']['item']['status']
|
||||
if live_status == 1:
|
||||
result["is_live"] = True
|
||||
play_url_list = json_data['data']['item']['stream_info']['pull']
|
||||
result['m3u8_url'] = play_url_list['live_origin_hls_url']
|
||||
result['flv_url'] = play_url_list['live_origin_flv_url']
|
||||
result['record_url'] = play_url_list['live_origin_hls_url']
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 尽量用自己的cookie,以避免默认的不可用导致无法获取数据
|
||||
# 以下示例链接不保证时效性,请自行查看链接是否能正常访问
|
||||
@ -1481,6 +1586,8 @@ if __name__ == '__main__':
|
||||
# room_url = 'https://look.163.com/live?id=65108820&position=3' # Look直播
|
||||
# room_url = 'https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117' # popkontv
|
||||
# room_url = 'https://twitcasting.tv/c:uonq' # TwitCasting
|
||||
# room_url = 'https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category' # 百度直播
|
||||
# room_url = 'https://weibo.com/l/wblive/p/show/1022:2321325026370190442592' # 微博直播
|
||||
|
||||
print(get_douyin_stream_data(room_url, proxy_addr=''))
|
||||
# print(get_tiktok_stream_data(room_url, proxy_addr=''))
|
||||
@ -1502,4 +1609,6 @@ if __name__ == '__main__':
|
||||
# print(get_flextv_stream_data(room_url,proxy_addr='', username='', password=''))
|
||||
# print(get_looklive_stream_url(room_url, proxy_addr=''))
|
||||
# print(get_popkontv_stream_url(room_url, proxy_addr='', username='', password=''))
|
||||
# print(get_twitcasting_stream_url(room_url, proxy_addr='', username='', password=''))
|
||||
# print(get_twitcasting_stream_url(room_url, proxy_addr='', username='', password=''))
|
||||
# print(get_baidu_stream_data(room_url, proxy_addr=''))
|
||||
# print(get_weibo_stream_url(room_url, proxy_addr=''))
|
||||
|
||||
2
utils.py
2
utils.py
@ -15,7 +15,7 @@ def trace_error_decorator(func):
|
||||
except Exception as e:
|
||||
error_line = traceback.extract_tb(e.__traceback__)[-1].lineno
|
||||
error_info = f"错误信息: type: {type(e).__name__}, {str(e)} in function {func.__name__} at line: {error_line}"
|
||||
logger.warning(error_info)
|
||||
logger.error(error_info)
|
||||
return []
|
||||
|
||||
return wrapper
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user