├── images ├── hupu-0.gif ├── hupu-1.gif ├── hupu-2.gif ├── hupu-3.gif ├── hupu-4.gif └── hupu-5.gif ├── requirements.txt ├── setup.py ├── .gitignore ├── README.md └── hupu.py /images/hupu-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-0.gif -------------------------------------------------------------------------------- /images/hupu-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-1.gif -------------------------------------------------------------------------------- /images/hupu-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-2.gif -------------------------------------------------------------------------------- /images/hupu-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-3.gif -------------------------------------------------------------------------------- /images/hupu-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-4.gif -------------------------------------------------------------------------------- /images/hupu-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjiandongx/HupuLive/HEAD/images/hupu-5.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | beautifulsoup4==4.6.0 3 | bs4==0.0.1 4 | docopt==0.6.2 5 | lxml==3.7.3 6 | packaging==16.8 7 | pyparsing==2.2.0 8 | requests==2.14.2 9 | six==1.10.0 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='HupuLive', 5 | version='1.4', 6 | author='chenjiandongx', 7 | author_email='chenjiandongx@qq.com', 8 | url = "https://github.com/chenjiandongx/HupuLive", 9 | description='Proudly presented by Hupu JRs', 10 | license="MIT", 11 | py_modules=['hupu'], 12 | requires=["bs4", "docopt", "requests", "lxml"], 13 | entry_points={ 14 | 'console_scripts': [ 15 | 'hupu=hupu:cli' 16 | ] 17 | } 18 | ) 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | .idea 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 虎扑篮球直播命令行版 2 | 3 | 作为一个加入虎扑快 700 天的 JRs,一直都是虎扑的铁粉。喜欢逛绿化街,喔不对,是步行街;喜欢看评论,搬好小板凳,欣赏段子手们的表演,前排偶尔还有出售瓜子和爆米花;喜欢虎扑的直播风格,幽默有趣,还能时不时蹦出金句,比如詹姆斯一个后撤步,后撤距离达到一个郭敬明。主播们都是被虎扑耽误了的作家,且节间中场还经常有福利图,需要定时补营养。毕竟随着身体一阵抽搐,整个帖子也就变得索然无味了。 4 | 5 | **所以,我有了一个大胆的想法** 6 | 7 | 平时也喜欢在命令行下折腾东西,虽然没钱用 Mac 不过 Windows/Linux 下的也不错阿。所以就想来个虎扑文字直播命令行版的,不过没有找到手机直播的数据,就只能将就找网页版的文字直播。有点可惜,因为发现网页版的直播语言太正式了,一点都不像我认识的虎扑,说好的我的三分剑,是地狱的火焰呢。 8 | 9 | 时不我待,来一个网易云的电音歌单配上一杯速溶咖啡 10 | 时间悄悄地在流逝,然后项目就写好了 11 | 12 | ### 测试环境 13 | * Windows 10 14 | * Python 3.5.2 15 | 16 | ### 如何安装 17 | 1. ``` git clone https://github.com/chenjiandongx/HupuLive.git ``` 18 | 2. ``` cd HupuLive ``` 19 | 3. ``` pip install -r requirements.txt``` 20 | 3. ``` python setup.py install ``` 21 | 22 | ### 使用指南 23 | ```hupu -h``` 或 ```hupu --help``` 能够查看如何使用,明细各项参数功能 24 | 25 | ![使用指南](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-0.gif) 26 | 27 | ### 获取比赛直播场次 28 | ```hupu -l``` 或 ```hupu --list``` 查询当天比赛的直播的场次,结果返回比赛场次,包括对阵双方以及场次的序号 29 | 30 | ![获取比赛直播场次](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-2.gif) 31 | 32 | ### 选取比赛开始直播 33 | ```hupu -w``` 或 ```hupu --watch``` 根据获得的场次序号来选择具体的比赛,比如这里的 0 34 | 35 | ![选取比赛开始直播](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-3.gif) 36 | 37 | 对齐看起来很舒服有没有,强迫症的福音有没有!!! 38 | 如果不想看了可以按 Ctrl-C 来中断直播,或者直接关闭终端就行了 39 | 40 | ### 获取比赛统计数据 41 | ```hupu -d``` 或 ```hupu --data``` 根据获取的场次序号来选择具体比赛的统计数据 42 | 43 | ![获取比赛统计数据](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-4.gif) 44 | 数据也是对齐的看起来也是很爽的有没有!!! 45 | 46 | ### 获取比赛赛后新闻 47 | ```hupu -n``` 或 ```hupu --news``` 同样根据获取的场次序号来选择具体比赛的赛后新闻 48 | 49 | ![获取比赛赛后新闻](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-5.gif) 50 | 51 | ### 获取近期比赛赛程 52 | ```hupu -s``` 或 ```hupu --schedule``` 查看近七天的比赛赛程 53 | 54 | ![获取近期比赛赛程](https://github.com/chenjiandongx/HupuLive/blob/master/images/hupu-1.gif) 55 | 56 | ### 如何卸载 57 | 使用 ```pip uninstall HupuLive``` 卸载 58 | -------------------------------------------------------------------------------- /hupu.py: -------------------------------------------------------------------------------- 1 | 2 | """Hupu Live: 3 | Proudly presented by Hupu JRs. 4 | 5 | Usage: 6 | hupu -l | --list 7 | hupu -s | --shedule 8 | hupu -w | --watch 9 | hupu -d | --data 10 | hupu -n | --news 11 | hupu -h | --help 12 | hupu -v | --version 13 | 14 | Tips: 15 | Please hit Ctrl-C on the keyborad when you want to interrupt the game live. 16 | 17 | Arguments: 18 | gameNumber The key number contact to the specific game. 19 | 20 | Options: 21 | -h --help Show this help message and exit. 22 | -v --version Show version. 23 | -l --list Show game live list. 24 | -w --watch Select a game live to watch. 25 | -d --data Show game statistical data (like points, rebounds, assists) 26 | -n --news Show postgame news 27 | -s --schedule Show game schedule 28 | """ 29 | import time 30 | import re 31 | 32 | from docopt import docopt 33 | import requests 34 | from bs4 import BeautifulSoup 35 | 36 | class Hupu(): 37 | 38 | __headers__ = {'X-Requested-With': 'XMLHttpRequest', 39 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 ' 40 | '(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'} 41 | 42 | def __init__(self, **kwargs): 43 | self._args = kwargs 44 | self._namelst = [] 45 | self._hreflst = [] 46 | self._datalst = [] 47 | self._headers = self.__headers__ 48 | self.get_gamelist() 49 | 50 | def get_command(self): 51 | """ 处理命令行参数 """ 52 | if self._args.get('-l') or self._args.get('--list'): 53 | if self._namelst: 54 | print("\n 比赛\t\t| 比赛场次\n", "-" * 25) 55 | for i, v in enumerate(self._namelst): 56 | print(" {}\t| {}\n".format(v, i), "-" * 25) 57 | else: 58 | print(">> 暂无比赛直播") 59 | if self._args.get('-w') or self._args.get('--watch'): 60 | try: 61 | self.live_game(self._hreflst[int(self._args.get(''))]) 62 | except Exception: 63 | print(">> 无该比赛场次文字直播") 64 | if self._args.get('-d') or self._args.get('--data'): 65 | try: 66 | self.show_data(self._datalst[int(self._args.get(''))]) 67 | except Exception: 68 | print(">> 无该比赛场次球员数据") 69 | if self._args.get('-n') or self._args.get('--news'): 70 | try: 71 | self.show_news(int(self._args.get(''))) 72 | except Exception: 73 | print(">> 无该比赛场次赛后新闻") 74 | if self._args.get('-s') or self._args.get('--schedule'): 75 | try: 76 | self.show_schedule() 77 | except Exception: 78 | print(">> 无法查询到比赛的赛程") 79 | 80 | def get_gamelist(self): 81 | """ 获取直播场次列表 """ 82 | url = "https://nba.hupu.com/games/playbyplay" 83 | try: 84 | r = requests.get(url, headers=self._headers, timeout=6).text 85 | game_href = BeautifulSoup(r, 'lxml').find_all('a', target="_self") 86 | for href in game_href: 87 | h = href['href'] 88 | if "playbyplay" in h: 89 | self._hreflst.append(h) 90 | game = BeautifulSoup(requests.get(h).text, 'lxml').find('p', class_="bread-crumbs").text 91 | self._namelst.append(str(game).strip().split()[2][:-4]) # 获取对阵双方 92 | if "boxscore" in h: 93 | self._datalst.append(h) 94 | except requests.exceptions.ConnectTimeout: 95 | print(">> 获取比赛场次失败,请检查您的网络连接情况") 96 | 97 | def show_data_title(self, url): 98 | """" 显示比赛统计数据的各项标题,如得分,篮板 """ 99 | r = requests.get(url, headers=self.__headers__, timeout=6).text 100 | title = BeautifulSoup(r, 'lxml').find('tr', class_="title bg_a").find_all('td') 101 | print() 102 | for i, v in enumerate([t.text for t in title]): 103 | if i == 0: 104 | print(" 位置", end="") 105 | elif i <= 15: 106 | print(v, end="\t") 107 | print("球员\n") 108 | 109 | def show_data(self, url): 110 | """ 显示比赛统计数据 """ 111 | self.show_data_title(url) 112 | r = requests.get(url, headers=self.__headers__, timeout=6).text 113 | players = BeautifulSoup(r, 'lxml').find_all('tr', style="background-color: rgb(255, 255, 255);") 114 | for index, player in enumerate(players): 115 | if index == 15: 116 | print("-" * 135) 117 | self.show_data_title(url) 118 | tdlst = [td.text for td in player.find_all('td')] 119 | for i, v in enumerate(tdlst): 120 | if i > 0: 121 | if i == 1: 122 | print(" ", v.replace("\n", ""), end="\t") 123 | elif i == 13: 124 | print(v.replace("\n", ""), end="\t\t") 125 | else: 126 | print(v.replace("\n", ""), end="\t") 127 | print(tdlst[0], "\n") 128 | 129 | def show_news(self, index): 130 | """ 显示赛后新闻 """ 131 | r = requests.get(self._hreflst[index], headers=self._headers, timeout=6).text 132 | news_href = BeautifulSoup(r, 'lxml').find_all('a', target="_self") 133 | for href in news_href: 134 | h = href['href'] 135 | if "recap" in h: 136 | r = requests.get(h, headers=self._headers, timeout=6).text 137 | news = BeautifulSoup(r, 'lxml').find("div", class_="news_box").text 138 | print(str(news).replace("\n", "\n\n")) 139 | return 140 | print(">> 无该比赛场次赛后新闻") 141 | 142 | def show_schedule(self): 143 | """ 显示赛程 """ 144 | url = "https://nba.hupu.com/schedule" 145 | r = requests.get(url, headers=self._headers, timeout=6).text 146 | result = str(BeautifulSoup(r, 'lxml').find('table', class_="players_table").text).split() 147 | schedule = [r for r in result if r not in ["数据统计", "比赛视频", "比赛前瞻", "视频直播"]] 148 | index = 0 149 | print() 150 | for i, v in enumerate(schedule): 151 | if i < 3: 152 | print(v, end=" ") 153 | elif i == 3: 154 | print(v) 155 | else: 156 | if index % 4 == 0 and index != 0: 157 | index = 0 158 | print() 159 | if re.findall(r"\d{2}月\d{2}日", v): 160 | print("\n" + v, end=" ") 161 | elif re.findall(r"星期[一二三四五六日]", v): 162 | print(v, end="\n") 163 | else: 164 | index += 1 165 | print(v, end=" ") 166 | print() 167 | 168 | def live_order(self, trlst, currid_cnt, table): 169 | """ 调整比赛直播顺序 """ 170 | for tr in trlst: 171 | if currid_cnt <= len(trlst): 172 | currid_cnt += 1 173 | td = [t.text for t in table.find('tr', id=tr['id']).find_all('td')] 174 | if len(td) == 4: 175 | gtime, gteam, gevent, gscore = td 176 | if len(gteam) < 5: # 补全空格,使显示格式对齐,强迫症 177 | gteam += int(5 - len(gteam)) * 2 * " " 178 | print("\t{} \t {} \t {}\t{}\n".format(gtime, gscore, gteam[1:], gevent)) 179 | elif len(td) == 1: # 显示如比赛暂停,第一节结束等非比分信息 180 | print("", td[0], "\n") 181 | if td[0] == "比赛结束": 182 | return 183 | return currid_cnt 184 | 185 | def live_game(self, url): 186 | """ 循环文字直播比赛 """ 187 | currid_cnt = 1 188 | r = requests.get(url, headers=self._headers, timeout=6).text 189 | title = BeautifulSoup(r, 'lxml').find('tr', class_="title bg_a").find_all('td') 190 | print("\n\t{} \t {}\n".format(title[0].text, title[3].text)) # 获取比赛标题信息 191 | table = BeautifulSoup(r, 'lxml').find('div', class_="table_list_live playbyplay_td table_overflow") 192 | 193 | # 比赛结束时序列是升序的,比赛中序列是降序的,匹配比赛是否结束然后选择排序方法 194 | # 由于直播的 tr 的 id 不按套路出牌时大时小,所以不能根据 id 大小来判断直播顺序了,要按 id 数量 195 | # 所以分两部分进行,第一部先一次性打印出已经存在的直播内容,第二部分循环直播接下来的内容 196 | if re.findall(r'team_num">(\S+)', r): 197 | trlst = [tr for tr in reversed(table.find('table').find_all('tr'))] 198 | currid_cnt = self.live_order(trlst, 1, table) 199 | else: 200 | trlst = table.find('table').find_all('tr') 201 | self.live_order(trlst, currid_cnt, table) 202 | return 203 | try: 204 | while True: 205 | r = requests.get(url, headers=self._headers, timeout=6).text 206 | table = BeautifulSoup(r, 'lxml').find('div', class_="table_list_live playbyplay_td table_overflow") 207 | tr = list(table.find('table').find_all('tr')) 208 | if currid_cnt <= len(tr): 209 | currid_cnt += 1 210 | td = [t.text for t in table.find('tr', id=tr[0]['id']).find_all('td')] 211 | if len(td) == 4: 212 | gtime, gteam, gevent, gscore = td 213 | if len(gteam) < 5: 214 | gteam += int(5 - len(gteam)) * 2 * " " 215 | print("\t{} \t {} \t {}\t{}\n".format(gtime, gscore, gteam[1:], gevent)) 216 | elif len(td) == 1: 217 | print("", td[0], "\n") 218 | if td[0] == "比赛结束": 219 | return 220 | time.sleep(1) # 每隔一秒发起一次请求 221 | except requests.exceptions.ConnectTimeout: 222 | print(">> 网络连接失败,无法获得直播数据") 223 | except KeyboardInterrupt: 224 | print(">> 您已中断直播") 225 | 226 | 227 | def cli(): 228 | """ 入口方法 """ 229 | args = docopt(__doc__, version='Hupu Live 1.4') 230 | Hupu(**args).get_command() 231 | 232 | if __name__ == '__main__': 233 | cli() 234 | --------------------------------------------------------------------------------