体育比赛文字直播工具

查看 99|回复 9
作者:tooooony   
nba季后赛正在如火如荼进行中,上班摸鱼看nba的需求,很多人多有,于是利用某体育社区的接口搞了这个工具。
支持文字直播&聊天室&比赛数据三种模式,bug还是有的,大家看看能不能玩一下。


屏幕截图 2025-04-27 154825.png (296.33 KB, 下载次数: 1)
下载附件
2025-4-27 15:49 上传



屏幕截图 2025-04-27 154844.png (285.15 KB, 下载次数: 0)
下载附件
2025-4-27 15:49 上传



屏幕截图 2025-04-27 154911.png (269.97 KB, 下载次数: 1)
下载附件
2025-4-27 15:49 上传

更新说明:支持斯诺克/网球/F1下拉选项,做了些错误处理检查。
[Python] 纯文本查看 复制代码import tkinter as tk
from tkinter import ttk
import requests
from datetime import datetime
from random import random
from tabulate import tabulate

Living_Matches_Url = 'https://bifen4m.qiumibao.com/json/list.htm'
Match_Max_Sid_Url = 'https://dingshi4pc.qiumibao.com/livetext/data/cache/max_sid/%s/0.htm?time=%s'
Match_Living_Text_Url = 'https://dingshi4pc.qiumibao.com/livetext/data/cache/livetext/%s/0/lit_page_2/%d.htm?get=%s'
Match_Info_Url = 'https://bifen4pc2.qiumibao.com/json/%s/%s.htm?get=%s'
Match_Chat_Count_Url = 'https://dan.zhibo8.cc/data/%s/nba/match%sv_count.htm?rand=%s'
Match_Chat_Text_Url = 'https://dan.zhibo8.cc/data/%s/nba/match%sv_%s.htm?rand=%s'
Match_Score_Rank_Url = 'https://dc4pc.qiumibao.com/dc/matchs/data/%s/score_rank_%s.htm?get=%s'
Match_Player_Url = 'https://dc4pc.qiumibao.com/dc/matchs/data/%s/player_%s.htm?get=%s'

MATCH_TYPES = {'足球': 'football', '篮球': 'basketball', '网球': 'tennis', 'F1': 'f1', '斯诺克': 'snooker'}

class TextLiving:
    """
    文字直播类
    """
    def __init__(self, match_info, **kwargs):
        self.home_team = match_info['home_team']
        self.visit_team = match_info['visit_team']
        self.period_cn = match_info['period_cn']
        self.live_text = kwargs['live_text']
        self.home_score = kwargs['home_score']
        self.visit_score = kwargs['visit_score']

    def __repr__(self):
        return f'{self.home_team} {self.home_score} - {self.visit_score} {self.visit_team} {self.period_cn}\n{self.live_text}\n{"*"*60}'


class Match:
    """
    比赛类
    """
    def __init__(self, **kwargs):
        self.id = kwargs['id']
        self.home_team = kwargs['home_team']
        self.visit_team = kwargs['visit_team']
        self.home_score = kwargs['home_score']
        self.visit_score = kwargs['visit_score']
        self.period_cn = kwargs['period_cn'].replace('\n', ' ')

    def __repr__(self):
        return f'{self.id} {self.home_team} {self.home_score} - {self.visit_score} {self.visit_team} {self.period_cn}'

class Chat:
    """
    聊天室类
    """
    def __init__(self, **kwargs):
        self.id = kwargs['id']
        self.room = kwargs['room']
        self.content = kwargs['content']
        self.order_id = kwargs['order_id']
        self.createtime = kwargs['createtime']

    def __repr__(self):
        return f'{self.order_id} @ {self.createtime} {self.content}'

def _get_living_list(type='basketball'):
    """
    直播比赛列表
    """
    response = requests.get(Living_Matches_Url)
    if response.status_code == requests.codes.ok:
        result = response.json()
        return [Match(**match) for match in result['list'] if match['type'] == type and match['period_cn'] != '完赛']
    return []


def get_match_max_sid(match_id, last_max_sid=-1):
    """
    获取比赛最大sid
    """
    response = requests.get(Match_Max_Sid_Url % (match_id, random()))
    if response.status_code == requests.codes.ok:
        return int(response.text)
    return last_max_sid


def get_match_living(match_id, max_sid):
    """
    获取比赛直播数据(文字直播数据)
    """
    match_info = get_match_info(match_id)
    response = requests.get(Match_Living_Text_Url % (match_id, max_sid, random()))
    if response.status_code == requests.codes.ok:
        result = response.json()
        return [TextLiving(match_info, **living) for living in result]
    return []


def get_match_chat_page(match_id, latest_page=1):
    """
    获取比赛评论最新页码
    """
    year = datetime.now().strftime('%Y')
    response = requests.get(Match_Chat_Count_Url % (year, match_id, random()))
    if response.status_code == requests.codes.ok:
        result = response.json()
        num = int(result['num'])
        per_page = int(result['per_page'])
        return num // per_page
    return latest_page

def get_match_latest_chat(match_id, latest_page=1):
    """
    获取比赛最新聊天内容
    """
    year = datetime.now().strftime('%Y')
    response = requests.get(Match_Chat_Text_Url % (year, match_id, latest_page, random()))
    if response.status_code == requests.codes.ok:
        return [Chat(**comment) for comment in response.json()]
    return []

def get_player_rank(match_id):
    """
    获取两队球员数据之最
    """
    today = datetime.now().strftime('%Y-%m-%d')
    response = requests.get(Match_Score_Rank_Url % (today, match_id, random()))
    if response.status_code == requests.codes.ok:
        return response.json()
    return {}

def get_player_data(match_id):
    """
    获取两队球员数据
    """
    today = datetime.now().strftime('%Y-%m-%d')
    response = requests.get(Match_Player_Url % (today, match_id, random()))
    if response.status_code == requests.codes.ok:
        return response.json()
    return {}

def get_match_info(match_id, last_match_info=[]):
    """
    获取比赛信息
    """
    today = datetime.now().strftime('%Y-%m-%d')
    response = requests.get(Match_Info_Url % (today, match_id, random()))
    if response.status_code == requests.codes.ok:
        return response.json()
    return last_match_info


def get_living_matches(type='basketball'):
    """
    获取直播比赛列表
    """
    matches = _get_living_list(type)
    for match in matches:
        print(match)
    return matches


def get_watch_match(matches):
    """
    获取用户输入的比赛信息
    """
    match_id = input('请输入比赛ID:')
    for match in matches:
        if match.id == match_id:
            return match
    else:
        print('输入的ID不正确!!!')
        return None

def get_type(id):
    return MATCH_TYPES.get(id, 'basketball')

class SportsApp:
    def __init__(self, root):
        self.root = root
        self.root.title("体育直播")
        self.root.geometry("800x600")
         
        # 顶部控制面板
        self.create_controls()
         
        # 底部显示区域
        self.create_display_area()

        # 比赛uuid
        self.sport_uuid = None
         
    def create_controls(self):
        # 顶部框架容器
        control_frame = ttk.Frame(self.root, padding=10)
        control_frame.pack(fill="x")
         
        # 第一个下拉框:体育项目
        self.sport_var = tk.StringVar()
        self.sport_cb = ttk.Combobox(
            control_frame,
            textvariable=self.sport_var,
            values=['篮球', '足球', '斯诺克', '网球', 'F1'],
            state="readonly"
        )
        self.sport_cb.pack(side="left", padx=10)
        self.sport_cb.bind(">", self.update_matches)
        self.sport_cb.config(width=20)
         
        # 第二个下拉框:比赛选择
        self.match_var = tk.StringVar()
        self.match_cb = ttk.Combobox(
            control_frame,
            textvariable=self.match_var,
            state="readonly"
        )
        self.match_cb.pack(side="left", padx=10)
        self.match_cb.bind(">", self.update_display)
        self.match_cb.config(width=40)
         
        # 第三个下拉框:查看模式
        self.mode_var = tk.StringVar()
        self.mode_cb = ttk.Combobox(
            control_frame,
            textvariable=self.mode_var,
            values=["文字直播", "聊天室", "比赛数据"],
            state="readonly"
        )
        self.mode_cb.pack(side="left", padx=5)
        self.mode_cb.bind(">", self.update_display)
         
    def create_display_area(self):
        # 主显示区域框架
        display_frame = ttk.Frame(self.root)
        display_frame.pack(fill="both", expand=True, padx=10, pady=10)
         
        # 滚动条
        scrollbar = ttk.Scrollbar(display_frame)
        scrollbar.pack(side="right", fill="y")
         
        # 文本框
        self.display_text = tk.Text(
            display_frame,
            wrap="word",
            yscrollcommand=scrollbar.set,
            font=("微软雅黑", 12),
            padx=10,
            pady=10
        )
        self.display_text.pack(fill="both", expand=True)
        scrollbar.config(command=self.display_text.yview)
         
    def update_matches(self, event=None):
        # 更新比赛列表
        selected_sport = self.sport_var.get()
        if selected_sport:
            matches = get_living_matches(get_type(selected_sport))
            self.match_cb["values"] = [str(m) for m in matches] if len(matches) > 0 else ['暂无比赛']
            self.match_cb.current(0)
            self.update_display()
         
    def update_display(self, event=None):        
        # 获取当前选择
        sport = self.sport_var.get()
        match = self.match_var.get()
        mode = self.mode_var.get()
         
        if not all([sport, match, mode]):
            return
         
        # 清空文本框
        if self.sport_uuid != f'{sport}_{match}_{mode}':
            self.display_text.delete(1.0, tk.END)
            self.sport_uuid = f'{sport}_{match}_{mode}'

        match_id = match.split(' ')[0]
        if not match_id.isdigit(): return
        if mode == '聊天室':
            self.chat_live(match_id)
        elif mode == '文字直播':
            self.text_live(match_id)
        elif mode == '比赛数据':
            if sport == '篮球':
                self.stat_basketball_live(match_id)
            else:
                self.display_text.delete(1.0, tk.END)
                self.display_text.insert(tk.END, "暂不支持该模式\n")
               
        self.root.after(2000, self.update_display)  # 每秒更新
    def text_live(self, match_id):
        """
        文字直播
        """
        current_match_max_sid = -1
        match_max_sid = get_match_max_sid(match_id, current_match_max_sid)
        if current_match_max_sid == match_max_sid: return

        current_match_max_sid = match_max_sid
        text_livings = get_match_living(match_id, current_match_max_sid)
        text = ''
        for text in text_livings:
            self.display_text.insert(tk.END, str(text) + '\n')
        if '完赛' in str(text):
            self.display_text.insert(tk.END, '\n\n**********比赛结束**********')

    def chat_live(self, match_id):
        """
        聊天室
        """
        current_page = 1
        latest_page = get_match_chat_page(match_id, current_page)
        if current_page == latest_page: return
        current_page = latest_page
        comments = get_match_latest_chat(match_id, current_page)
        for comment in comments:
            self.display_text.insert(tk.END, str(comment) + '\n')

    def stat_basketball_live(self, match_id):
        """
        比赛数据
        """
        data_1 = get_player_data(match_id)
        data_2 = get_player_rank(match_id)
        host_1 = data_1.get('data', {}).get('host')
        guest_1 = data_1.get('data', {}).get('guest')
        host_2 = data_2.get('data', {}).get('host')
        guest_2 = data_2.get('data', {}).get('guest')
        if host_1 is None:
            self.display_text.delete(1.0, tk.END)
            self.display_text.insert(tk.END, '暂无数据\n')
            return
        self.display_text.delete(1.0, tk.END)
        headers_1 = ['球员', '位置', '出场时间', '投篮', '三分', '罚球', '总篮板', '助攻', '抢断', '盖帽', '失误', '犯规', '得分']
        self.display_text.insert(tk.END, f'**********客队: {host_1["team_name_cn"]}**********\n')
        table_host_1 = []
        for p in host_1['on']:
            table_host_1.append([p['player_name_cn'], p['pos'], p['minutes'], p['field'], p['three'], p['free'], p['off+def'], p['ass'], p['ste'], p['blo'], p['turn'], p['fouls'], p['points']])

        table_host_1.append(['总计', '-',  '-', host_1['total']['field'], host_1['total']['three'], host_1['total']['free'], host_1['total']['off+def'], host_1['total']['ass'], host_1['total']['ste'], host_1['total']['blo'], host_1['total']['turn'], host_1['total']['fouls'], host_1['total']['points']])
        self.display_text.insert(tk.END, tabulate(table_host_1, headers_1, tablefmt='grid') + '\n')

        self.display_text.insert(tk.END, f'\n\n**********主队: {guest_1["team_name_cn"]}**********\n')
        table_guest_1 = []
        for p in guest_1['on']:
            table_guest_1.append([p['player_name_cn'], p['pos'], p['minutes'], p['field'], p['three'], p['free'], p['off+def'], p['ass'], p['ste'], p['blo'], p['turn'], p['fouls'], p['points']])

        table_guest_1.append(['总计', '-',  '-', guest_1['total']['field'], guest_1['total']['three'], guest_1['total']['free'], guest_1['total']['off+def'], guest_1['total']['ass'], guest_1['total']['ste'], guest_1['total']['blo'], guest_1['total']['turn'], guest_1['total']['fouls'], guest_1['total']['points']])
        self.display_text.insert(tk.END, tabulate(table_guest_1, headers_1, tablefmt='grid') + '\n')
            
        if host_2 is None: return

        headers_2 = ['最高', host_2['team_info']['team_name'], '数值', guest_2['team_info']['team_name'], '数值']
        self.display_text.insert(tk.END, '\n\n**********两队技术统计之最**********\n')
        table_2 = []
        table_2.append([host_2['points']['name'], host_2['points']['player_name'], host_2['points']['points'], guest_2['points']['player_name'], guest_2['points']['points']])
        table_2.append([host_2['off+def']['name'], host_2['off+def']['player_name'], host_2['off+def']['points'], guest_2['off+def']['player_name'], guest_2['off+def']['points']])
        table_2.append([host_2['ass']['name'], host_2['ass']['player_name'], host_2['ass']['points'], guest_2['ass']['player_name'], guest_2['ass']['points']])
        table_2.append([host_2['ste']['name'], host_2['ste']['player_name'], host_2['ste']['points'], guest_2['ste']['player_name'], guest_2['ste']['points']])
        table_2.append([host_2['blo']['name'], host_2['blo']['player_name'], host_2['blo']['points'], guest_2['blo']['player_name'], guest_2['blo']['points']])
        table_2.append([host_2['minutes']['name'], host_2['minutes']['player_name'], host_2['minutes']['points'], guest_2['minutes']['player_name'], guest_2['minutes']['points']])
        table_2.append([host_2['turn']['name'], host_2['turn']['player_name'], host_2['turn']['points'], guest_2['turn']['player_name'], guest_2['turn']['points']])
        table_2.append([host_2['per_fouls']['name'], host_2['per_fouls']['player_name'], host_2['per_fouls']['points'], guest_2['per_fouls']['player_name'], guest_2['per_fouls']['points']])

        self.display_text.insert(tk.END, tabulate(table_2, headers_2, tablefmt='grid') + '\n')

if __name__ == "__main__":
    root = tk.Tk()
    app = SportsApp(root)
    root.mainloop()

数据, 文字

zuq001   

想起来当年诺基亚看球赛文字直播的时代
kaisen868   

记得十多年前在直播吧看消息,当时很好奇文字直播是如何实时转换成文字的,尤其涉及到实时比分更新、个人数据、排版格式等很多细节,而且很少出现错误。
Jason19821220   

工具软件打包压
冯古屋   

你做了我一直想做的事情,哈哈
yangyanghq   

看看怎样弄!感觉有用!
WORSONG178   

小工具真是多多益善,摸索下。
lgbndf   

摸索一下,这个不错
Mariner   

下次看NBA 方便多了
皮皮囧   

感谢分享!
您需要登录后才可以回帖 登录 | 立即注册

返回顶部