#!/usr/bin/env python # -*- coding: utf-8 -*- ''' Created on 2018年2月4日 @author: Irony."[讽刺] @site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 @email: 892768447@qq.com @file: TencentMovieHotPlay @description: ''' import os import sys import webbrowser from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ QBrush, QPaintEvent, QPixmap from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ QAbstractSlider from lxml.etree import HTML # @UnresolvedImport __Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" __Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" __Version__ = "Version 1.0" # offset=0,30,60,90 Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" # 播放量图标 Svg_icon_play_sm = ''' '''.encode() Svg_icon_loading = ''' '''.encode() # 主演 Actor = '''{title} ''' class CoverLabel(QLabel): def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): super(CoverLabel, self).__init__(*args, **kwargs) self.setCursor(Qt.PointingHandCursor) self.setScaledContents(True) self.setMinimumSize(220, 308) self.setMaximumSize(220, 308) self.cover_path = cover_path self.cover_title = cover_title self.video_url = video_url self.setPixmap(QPixmap(cover_path)) def setCoverPath(self, path): self.cover_path = path def mouseReleaseEvent(self, event): super(CoverLabel, self).mouseReleaseEvent(event) webbrowser.open_new_tab(self.video_url) def paintEvent(self, event): super(CoverLabel, self).paintEvent(event) if hasattr(self, "cover_title") and self.cover_title != "": # 底部绘制文字 painter = QPainter(self) rect = self.rect() # 粗略字体高度 painter.save() fheight = self.fontMetrics().height() # 底部矩形框背景渐变颜色 bottomRectColor = QLinearGradient( rect.width() / 2, rect.height() - 24 - fheight, rect.width() / 2, rect.height()) bottomRectColor.setSpread(QGradient.PadSpread) bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) # 画半透明渐变矩形框 painter.setPen(Qt.NoPen) painter.setBrush(QBrush(bottomRectColor)) painter.drawRect(rect.x(), rect.height() - 24 - fheight, rect.width(), 24 + fheight) painter.restore() # 距离底部一定高度画文字 font = self.font() or QFont() font.setPointSize(8) painter.setFont(font) painter.setPen(Qt.white) rect.setHeight(rect.height() - 12) # 底部减去一定高度 painter.drawText(rect, Qt.AlignHCenter | Qt.AlignBottom, self.cover_title) class ItemWidget(QWidget): def __init__(self, cover_path, figure_info, figure_title, figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): super(ItemWidget, self).__init__(*args, **kwargs) self.setMaximumSize(220, 380) self.setMaximumSize(220, 380) self.img_path = img_path self.cover_url = cover_url layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # 图片label self.clabel = CoverLabel(cover_path, figure_info, video_url, self) layout.addWidget(self.clabel) # 片名和分数 flayout = QHBoxLayout() flayout.addWidget(QLabel(figure_title, self)) flayout.addItem(QSpacerItem( 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) layout.addLayout(flayout) # 主演 layout.addWidget( QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) # 播放量 blayout = QHBoxLayout() count_icon = QSvgWidget(self) count_icon.setMaximumSize(16, 16) count_icon.load(Svg_icon_play_sm) blayout.addWidget(count_icon) blayout.addWidget( QLabel(figure_count, self, styleSheet="color: #999999;")) layout.addLayout(blayout) def setCover(self, path): self.clabel.setCoverPath(path) self.clabel.setPixmap(QPixmap(path)) # self.clabel.setText(''.format(os.path.abspath(path))) def sizeHint(self): # 每个item控件的大小 return QSize(220, 380) def event(self, event): if isinstance(event, QPaintEvent): if event.rect().height() > 20 and hasattr(self, "clabel"): if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 # print("start download img:", self.cover_url) req = QNetworkRequest(QUrl(self.cover_url)) # 设置两个自定义属性方便后期reply中处理 req.setAttribute(QNetworkRequest.User + 1, self) req.setAttribute(QNetworkRequest.User + 2, self.img_path) self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 return super(ItemWidget, self).event(event) class GridWidget(QWidget): Page = 0 loadStarted = pyqtSignal(bool) def __init__(self, *args, **kwargs): super(GridWidget, self).__init__(*args, **kwargs) self._layout = QGridLayout(self, spacing=20) self._layout.setContentsMargins(20, 20, 20, 20) # 异步网络下载管理器 self._manager = QNetworkAccessManager(self) self._manager.finished.connect(self.onFinished) def load(self): if self.Page == -1: return self.loadStarted.emit(True) # 延迟一秒后调用目的在于显示进度条 QTimer.singleShot(1000, self._load) def _load(self): print("load url:", Url.format(self.Page * 30)) url = QUrl(Url.format(self.Page * 30)) self._manager.get(QNetworkRequest(url)) def onFinished(self, reply): # 请求完成后会调用该函数 req = reply.request() # 获取请求 iwidget = req.attribute(QNetworkRequest.User + 1, None) path = req.attribute(QNetworkRequest.User + 2, None) html = reply.readAll().data() reply.deleteLater() del reply if iwidget and path and html: # 这里是图片下载完毕 open(path, "wb").write(html) iwidget.setCover(path) return # 解析网页 self._parseHtml(html) self.loadStarted.emit(False) def splist(self, src, length): # 等分列表 return (src[i:i + length] for i in range(len(src)) if i % length == 0) def _parseHtml(self, html): # encoding = chardet.detect(html) or {} # html = html.decode(encoding.get("encoding","utf-8")) html = HTML(html) # 查找所有的li list_item lis = html.xpath("//li[@class='list_item']") if not lis: self.Page = -1 # 后面没有页面了 return lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 row_count = int(self._layout.count() / 6) # 行数 print("lack_count:", lack_count) self.Page += 1 # 自增+1 if lack_count != 0: # 上一次没有满足一行6个,需要补齐 lack_li = lis[:lack_count] lis = lis[lack_count:] self._makeItem(lack_li, row_count) # 补齐 if lack_li and lis: row_count += 1 self._makeItem(lis, row_count) # 完成剩下的 else: self._makeItem(lis, row_count) def _makeItem(self, li_s, row_count): li_s = self.splist(li_s, 6) for row, lis in enumerate(li_s): for col, li in enumerate(lis): a = li.find("a") video_url = a.get("href") # 视频播放地址 img = a.find("img") cover_url = "http:" + img.get("r-lazyload") # 封面图片 figure_title = img.get("alt") # 电影名 figure_info = a.find("div/span") figure_info = "" if figure_info is None else figure_info.text # 影片信息 figure_score = "".join(li.xpath(".//em/text()")) # 评分 # 主演 figure_desc = "主演:" + \ "".join([Actor.format(**dict(fd.items())) for fd in li.xpath(".//div[@class='figure_desc']/a")]) # 播放数 figure_count = ( li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] path = "cache/{0}.jpg".format( os.path.splitext(os.path.basename(video_url))[0]) cover_path = "pic_v.png" if os.path.isfile(path): cover_path = path iwidget = ItemWidget(cover_path, figure_info, figure_title, figure_score, figure_desc, figure_count, video_url, cover_url, path, self) self._layout.addWidget(iwidget, row_count + row, col) class Window(QScrollArea): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.resize(800, 600) self.setFrameShape(self.NoFrame) self.setWidgetResizable(True) self.setAlignment(Qt.AlignCenter) self._loadStart = False # 网格窗口 self._widget = GridWidget(self) self._widget.loadStarted.connect(self.setLoadStarted) self.setWidget(self._widget) # 连接竖着的滚动条滚动事件 self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) # 进度条 self.loadWidget = QSvgWidget( self, minimumHeight=120, minimumWidth=120, visible=False) self.loadWidget.load(Svg_icon_loading) def setLoadStarted(self, started): self._loadStart = started self.loadWidget.setVisible(started) def onActionTriggered(self, action): # 这里要判断action=QAbstractSlider.SliderMove,可以避免窗口大小改变的问题 # 同时防止多次加载同一个url if action != QAbstractSlider.SliderMove or self._loadStart: return # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): # 可以下一页了 self._widget.load() def resizeEvent(self, event): super(Window, self).resizeEvent(event) self.loadWidget.setGeometry( int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2), self.loadWidget.minimumWidth(), self.loadWidget.minimumHeight() ) if __name__ == "__main__": os.makedirs("cache", exist_ok=True) app = QApplication(sys.argv) w = Window() w.show() w._widget.load() sys.exit(app.exec_())