使用 pyqt +vlc 搭建一个爬虫播放器

一: 需要的安装包

1
pip install  requests pyqt5 python-vlc

1

功能:

  1. 播放第一财经直播,及其广播

  2. 输入播放路径,本地或者网络路径可以播放视频

  3. 可以获取当日资金流入前五的行业板块

  4. 可以调节音量

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
import os, platform, webbrowser, requests, re, time,json

os.environ['PYTHON_VLC_MODULE_PATH'] = "./_dll"
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit, QDialog, QTableWidget, QTableWidgetItem, QHeaderView
from PyQt5.QtGui import QFont, QPalette, QColor
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets, QtGui, QtCore
import vlc, logging




# '''
# Player类封装了VLC的功能,用于控制媒体播放。__init__方法初始化一个VLC播放器实例
# '''
class Player:

def __init__(self, *args):

if args:

instance = vlc.Instance(*args)
self.media = instance.media_player_new()
else:
self.media = vlc.MediaPlayer()

# 设置待播放的url地址或本地文件路径,每次调用都会重新加载资源

def set_uri(self, uri):
self.media.set_mrl(uri)

# 播放 成功返回0,失败返回-1
def play(self, path=None):
if path:
self.set_uri(path)
return self.media.play()
else:
return self.media.play()

# 暂停
def pause(self):
self.media.pause()

# 恢复
def resume(self):
self.media.set_pause(0)

# 停止
def stop(self):
self.media.stop()

# 释放资源
def release(self):
return self.media.release()

# 是否正在播放
def is_playing(self):
return self.media.is_playing()

# 已播放时间,返回毫秒值
def get_time(self):
return self.media.get_time()

# 拖动指定的毫秒值处播放。成功返回0,失败返回-1 (需要注意,只有当前多媒体格式或流媒体协议支持才会生效)
def set_time(self, ms):
return self.media.set_time(ms)

# 音视频总长度,返回毫秒值
def get_length(self):
return self.media.get_length()

# 获取当前音量(0~100)
def get_volume(self):
return self.media.audio_get_volume()

# 设置音量(0~100)
def set_volume(self, volume):
return self.media.audio_set_volume(volume)

# 返回当前状态:正在播放;暂停中;其他
def get_state(self):
state = self.media.get_state()
if state == vlc.State.Playing:
return 1
elif state == vlc.State.Paused:
return 0
else:
return -1

# 当前播放进度情况。返回0.0~1.0之间的浮点数
def get_position(self):
return self.media.get_position()

# 拖动当前进度,传入0.0~1.0之间的浮点数(需要注意,只有当前多媒体格式或流媒体协议支持才会生效)
def set_position(self, float_val):
return self.media.set_position(float_val)

# 获取当前文件播放速率
def get_rate(self):
return self.media.get_rate()

# 设置播放速率(如:1.2,表示加速1.2倍播放)
def set_rate(self, rate):
return self.media.set_rate(rate)

# 设置宽高比率(如"16:9","4:3")
def set_ratio(self, ratio):
self.media.video_set_scale(0)
self.media.video_set_aspect_ratio(ratio)

# 设置窗口句柄
def set_window(self, wm_id):
if platform.system() == 'Windows':
self.media.set_hwnd(wm_id)
else:
self.media.set_xwindow(wm_id)

# 注册监听器
def add_callback(self, event_type, callback):
self.media.event_manager().event_attach(event_type, callback)

# 移除监听器
def remove_callback(self, event_type, callback):
self.media.event_manager().event_detach(event_type, callback)


class TableDialog(QDialog):
def __init__(self):
super().__init__()
self.init_ui()

def num_to_chinese_units(self,num):
chinese_units = {'亿': 100000000, '万': 10000}
result = []
remainder = num
for unit, value in chinese_units.items():
if remainder >= value:
quotient, remainder = divmod(remainder, value)
result.append(f'{quotient:.2f}{unit}')
if remainder != 0:
result.append(f'{remainder:.2f}元')
return ' '.join(result)
def get_data(self):
t = int(time.time() * 1000)

url = f'https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery112301302125560766778_{t}&pn=1&pz=500&po=1&np=1&fields=f12%2Cf13%2Cf14%2Cf62&fid=f62&fs=m%3A90%2Bt%3A2&ut=b2884a393a59ad64002292a3e90d46a5&_={t + 1}'
res = requests.get(url,verify=False).text
# res_json=json.loads(re.findall(r'jQuery112301302125560766778_(\d+)\((.*?)\)',res)[0][1])
res_json = json.loads(re.findall(r'\((.*?)\)', res)[0])
# print(res_json)
data=[]
for i in range(0, 6):
disk = {}
disk['name'] = res_json['data']['diff'][i]['f14']
disk['price'] = self.num_to_chinese_units(res_json['data']['diff'][i]['f62'])
data.append(disk)
return data
def init_ui(self):
self.setWindowIcon(QtGui.QIcon('./favicon.ico'))
self.setWindowTitle('今日板块资金流入前五')
self.setGeometry(200, 200, 600, 200)

self.table_widget = QTableWidget()
self.table_widget.setRowCount(5)
self.table_widget.setColumnCount(2)
self.table_widget.setHorizontalHeaderLabels(['行业板块', '主力流入资金'])
# 设置表头字体
header_font = QFont("Arial", 18, QFont.Bold)
# header_palette = QPalette()
# header_palette.setColor(QPalette.WindowText, QColor('red'))
self.table_widget.horizontalHeader().setFont(header_font)
# self.table_widget.horizontalHeader().setPalette(header_palette)
data=self.get_data()
print(data)
datas = data

for row, item in enumerate(datas):
name_item = QTableWidgetItem(item['name'])
name_item.setFlags(name_item.flags() & ~Qt.ItemIsEditable) # 设置不可编辑
name_item.setFont(QFont("Arial", 15, QFont.Bold)) # 设置字体加粗
name_item.setTextAlignment(Qt.AlignCenter) # 居中对齐
self.table_widget.setItem(row, 0, name_item)

price_item = QTableWidgetItem(item['price'])
price_item.setFlags(price_item.flags() & ~Qt.ItemIsEditable) # 设置不可编辑
name_item.setFont(QFont("Arial", 12, QFont.Bold)) # 设置字体加粗
name_item.setTextAlignment(Qt.AlignCenter) # 居中对齐
self.table_widget.setItem(row, 1, price_item)

self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

layout = QVBoxLayout()
layout.addWidget(self.table_widget)
self.setLayout(layout)





'''
Play类继承自QWidget,用于创建主窗口。__init__方法初始化播放器实例并设置用户界面
'''


class Play(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.player = Player()

self.init_ui()
# self.timer = QtCore.QTimer(self)
# self.timer.timeout.connect(self.update_progress)
# self.timer.start(1000) # 每秒更新一次进度条
# # 添加状态记录变量
# self.playback_state_before_drag = False
# self.position_before_drag = 0
# self.is_dragging_slider = False # 添加一个变量来追踪进度条是否正在被拖动
# 配置日志记录器

logging.basicConfig(level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s')
self.logger = logging.getLogger(__name__)

def init_ui(self):

'''
init_ui方法设置窗口标题、图标和尺寸,
并创建一个垂直布局vbox。video_frame用于显示视频内容,
并添加到布局中。最后,set_window方法将视频输出设置到video_frame中
'''

# 初始化用户界面
self.setWindowTitle('第一财经直播')
#设置置顶页面
# self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# 设置窗口标志为无边框窗口
# self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
# 设置窗口背景透明
# self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setGeometry(100, 100, 800, 600)
self.setWindowIcon(QtGui.QIcon('./favicon.ico'))

vbox = QtWidgets.QVBoxLayout()
self.setLayout(vbox)

self.video_frame = QtWidgets.QFrame()
self.video_frame.setStyleSheet("background-color: black;")
vbox.addWidget(self.video_frame)

self.create_controls(vbox)

# 创建菜单栏
self.create_menu_bar(vbox)
self.player.set_window(self.video_frame.winId())

def create_menu_bar(self, layout):
# 创建菜单栏
menu_bar = QtWidgets.QMenuBar(self)
menu_bar.setGeometry(QtCore.QRect(0, 0, 800, 21))

# 添加菜单
menu = menu_bar.addMenu('菜单')

# 添加版本更新动作
update_action = QtWidgets.QAction('版本更新', self)
update_action.triggered.connect(self.check_for_updates)
menu.addAction(update_action)

layout.setMenuBar(menu_bar)

def check_for_updates(self):
# 模拟版本更新检查
QtWidgets.QMessageBox.information(self, "版本更新", "检查更新中...")

def create_controls(self, layout):
'''
create_controls方法创建了一些控制按钮,
包括“第一财经直播”、“经济之声广播”、“停止”和“播放不了点我”。
每个按钮都连接到click方法,并添加到control_box布局中

'''
# 添加进度条
# self.progress_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
# self.progress_slider.setRange(0, 100)
# self.progress_slider.setValue(0)
# self.progress_slider.sliderMoved.connect(self.set_player_position)
# layout.addWidget(self.progress_slider)

control_box = QtWidgets.QHBoxLayout()

btn_live = QtWidgets.QPushButton("第一财经直播")
btn_live.clicked.connect(lambda: self.click(0))
btn_live.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: green; background-color: black;")
control_box.addWidget(btn_live)

btn_radio = QtWidgets.QPushButton("经济之声广播")
btn_radio.clicked.connect(lambda: self.click(4))
btn_radio.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: white; background-color: black;")
control_box.addWidget(btn_radio)

btn_stop = QtWidgets.QPushButton("暂 停")
btn_stop.clicked.connect(lambda: self.click(2))
btn_stop.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: white; background-color: black;")
control_box.addWidget(btn_stop)

btn_restart = QtWidgets.QPushButton("恢 复")
btn_restart.clicked.connect(lambda: self.click(1))
btn_restart.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: white; background-color: black;")
control_box.addWidget(btn_restart)

btn_restart = QtWidgets.QPushButton("资金流入前五")
btn_restart.clicked.connect(lambda: self.click(5))
btn_restart.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: red; background-color: black;")
control_box.addWidget(btn_restart)

btn_open_browser = QtWidgets.QPushButton("播放不了点我")
btn_open_browser.clicked.connect(lambda: self.click(3))
btn_open_browser.setStyleSheet("font-family: 华文行楷; font-size: 20px; color: white; background-color: black;")
control_box.addWidget(btn_open_browser)

layout.addLayout(control_box)

# 添加文本框和播放按钮
input_box = QtWidgets.QHBoxLayout()
self.input_url = QtWidgets.QLineEdit()
self.input_url.setPlaceholderText('填入播放地址(本地或者网络都可以)')
input_box.addWidget(self.input_url)
btn_play_custom = QtWidgets.QPushButton("播 放")
btn_play_custom.clicked.connect(self.play_custom_url)
input_box.addWidget(btn_play_custom)
layout.addLayout(input_box)

# 音量控制滑块
volume_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
volume_slider.setRange(0, 100)
volume_slider.setValue(self.player.get_volume())
volume_slider.setTickInterval(10)
volume_slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
volume_slider.valueChanged.connect(self.set_volume)
layout.addWidget(volume_slider)
# 添加进度条事件处理程序
# self.progress_slider.sliderPressed.connect(self.start_dragging_slider)
# self.progress_slider.sliderReleased.connect(self.stop_dragging_slider)
# self.progress_slider.sliderMoved.connect(self.update_player_position)
# 添加日志显示栏

# 元素点击控制
def click(self, action):
if action == 0:
try:
self.player.stop()
self.player.play(self.url_live())
except Exception as e:
self.show_error_dialog(str(e))

elif action == 1:
self.player.resume()

elif action == 2:
self.player.pause()

elif action == 3:
self.player.stop()
webbrowser.open('https://www.yicai.com/tv/')
elif action == 4:
self.player.play('http://ngcdn002.cnr.cn/live/jjzs/index.m3u8')
elif action == 5:
try:
self.show_table_dialog()
except Exception as e:
self.show_error_dialog(str(e))
else:
self.player.stop()

def show_table_dialog(self):
self.table_dialog = TableDialog()
self.table_dialog.exec_()

def update_progress(self):
duration = self.player.get_length()
if duration > 0:
current_position = self.player.get_time()
if current_position >= 0:
progress = int(current_position * 100 / duration)
self.progress_slider.setValue(progress)

def update_player_position(self, position):
# 如果正在拖动进度条,则不更新播放位置
if not self.is_dragging_slider:
duration = self.player.get_length()
if duration > 0:
self.player.set_position(position / duration)

def start_dragging_slider(self):
self.is_dragging_slider = True
# 记录拖动前的播放状态和位置
self.playback_state_before_drag = self.player.is_playing()
self.position_before_drag = self.player.get_time()
# 暂停视频的播放
self.player.pause()

def stop_dragging_slider(self):
self.is_dragging_slider = False
# 如果拖动前视频正在播放,则继续播放并设置新的位置
if self.playback_state_before_drag:
self.player.set_time(self.position_before_drag)
self.player.resume()
else:
# 如果拖动前视频未在播放,则仅设置新的位置
self.set_player_position(self.progress_slider.value())

def play_custom_url(self):
# 去除首尾“号
remove_quotes = lambda text1: text1[1:-1] if text1[0] == '"' and text1[-1] == '"' else text1
text = self.input_url.text()
url = remove_quotes(text)
if url:
try:
self.player.stop()
self.player.play(url)
except Exception as e:
self.show_error_dialog(str(e))

def set_player_position(self, position):
duration = self.player.get_length()
if duration > 0:
self.player.set_position(position / duration)

# 音量控制
def set_volume(self, value):
self.player.set_volume(value)

def show_error_dialog(self, message):
try:
QtWidgets.QMessageBox.critical(self, "Error", message)
# 记录错误信息到日志
self.logger.error(message)
# 同时将错误信息显示在日志显示栏中
self.log_text_edit.append_text(message)
except:
pass
# try:
# msg_box = QtWidgets.QMessageBox()
# msg_box.setIcon(QtWidgets.QMessageBox.Critical)
# msg_box.setWindowTitle("Error")
# msg_box.setText(message)
# msg_box.exec_()
#
# # 记录错误信息到日志
# self.logger.error(message)
# # 同时将错误信息显示在日志显示栏中
# self.log_text_edit.append_text(message)
# except:
# pass
def url_live(self):
'''
url_live方法通过HTTP请求获取第一财经直播的URL,并返回该URL
'''
start_time = str(int(time.time() - 9999))
end_time = str(int(time.time()))
headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Referer": "https://www.yicai.com/tv/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
'cookie': f'connect.sid=s%3APUcBq7F4IaCyh7u37qPCWeFFxB3CuRA1.1F%2Fz0iG7hrAxeBAvDPjLX9hhKBHgAqeAgtfjKiwzJys; uam_user=%7B%22access_token%22%3A%22cdc1ab12cdc86af96fc00369d8805ad4bc257f3a8b3f657b83ba13706a60fcf3%22%2C%22expires_in%22%3A172800%2C%22token_type%22%3A%22Bearer%22%2C%22scope%22%3A%22user%22%2C%22refresh_token%22%3A%2271cd6203b65b855ac9af82f42a37b0b6258709574c4d724db095a66803aa6eca%22%2C%22uid%22%3A%22141964785%22%2C%22regstatus%22%3A0%2C%22point%22%3A0%2C%22expires_date%22%3A1715822534046%2C%22check%22%3A%226e73cf35bc01d264cbb3fc61b8e41e4c%22%2C%22nickname%22%3A%22175****1021%22%2C%22avatar%22%3A%22https%3A%2F%2Fimgcdn.yicai.com%2Fvms-storage%2Fuploads%2Fuspics%2Foauth2uploads%2Fedb2db61155f487545a0d420e9d6c205.jpg%22%7D; Hm_lvt_80b762a374ca9a39e4434713ecc02488={start_time}; _ga=GA1.1.437620187.{start_time}; yu_id=1dcb8199dbbcc4c144648d8386789045; acw_tc=2f624a3a17158407230525459e344402047c9d8479965a03fa27bf0f497133; Hm_lpvt_80b762a374ca9a39e4434713ecc02488={end_time}; _ga_BW57C8STG3=GS1.1.1715840723.3.1.{end_time}.0.0.0; tfstk=f9AerRNuiXhEAi9lbG1Pb0OoTJ5dasnb8Qs5rUYlRMj3OWZkQeteO2pBrg-yfGF7F6AyB78vyBgdN2CpJ_CocmGfaeLdwJYu8Gi6S4bJXxx58jTpJPvqUqVKG8PtXT0Pq3XhI5bAjwVhq34GIGQRrkbutV8GXafl-WVlIlbVouVh-Xnk-UVNl9mL-syORD7dLiYZH7VysS6FmejaZjAN8iID-GPuZMLMGuLwRDPv56d60ZtIsW-2-KxknhcaTOpH7QWvwk0GZTv6h9JZx7S6OaWcZOzuZF5hXtdlijqNWdYBEBKUz7bpOI6PDOuuwT1MGtvDYz3vS6bk2tdSDWIHrEdvhslzs6Wl4lqRS5_ywpruU9bO7igZSJILryf_IPc_e8BGMNSjRww8e9fG7igab8eRpn_Nc24F.'
}
data = {
'type': 'live',
'url': 'https://ycallive.yicai.com/live/cbn_yld.m3u8'
}
response = requests.post('https://www.yicai.com/api/post/getvideosrc', headers=headers, data=data)
response_data = response.json()
url_1 = response_data['res']
# self.show_error_dialog(url_1)
return url_1


if __name__ == "__main__":
# 程序创建一个Qt应用实例app,初始化Play类并显示窗口,最后进入应用的主循环。
app = QtWidgets.QApplication([])
player = Play()
player.click(0) # 默认自动播放第一财经
# 创建弹窗实例并显示
try:
table_dialog = TableDialog()
table_dialog.show()
except:
pass
player.show()
app.exec_()