List View

This commit is contained in:
Irony 2018-12-24 00:08:46 +08:00
parent b1bac76923
commit e5db2e65a3
20 changed files with 1034 additions and 996 deletions

View file

@ -0,0 +1,11 @@
# QListView
## 1. Custom Widget Item
[Run](CustomWidgetItem.py)
![CustomWidgetItem](ScreenShot/CustomWidgetItem.png)
## 2. Custom Widget Sort Item
[Run](CustomWidgetSortItem.py)
![CustomWidgetSortItem](ScreenShot/CustomWidgetSortItem.gif)

View file

@ -0,0 +1,11 @@
# QListView
## 1、显示自定义Widget
[运行](CustomWidgetItem.py)
![CustomWidgetItem](ScreenShot/CustomWidgetItem.png)
## 2、显示自定义Widget并排序
[运行](CustomWidgetSortItem.py)
![CustomWidgetSortItem](ScreenShot/CustomWidgetSortItem.gif)

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 323 KiB

View file

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -1,29 +1,29 @@
# 腾讯视频热播列表 # 腾讯视频热播列表
简单思路说明: 简单思路说明:
- 利用QScrollArea滚动显示QGridLayout或者FlowLayout做布局来放置自定义的Widget - 利用QScrollArea滚动显示QGridLayout或者FlowLayout做布局来放置自定义的Widget
- QNetworkAccessManager异步下载网页和图片 - QNetworkAccessManager异步下载网页和图片
- QScrollArea滚动到底部触发下一页加载 - QScrollArea滚动到底部触发下一页加载
自定义控件说明: 自定义控件说明:
- 主要是多个layout和控件的结合其中图片QLabel为自定义通过setPixmap设置图片重写paintEvent绘制底部渐变矩形框和白色文字 - 主要是多个layout和控件的结合其中图片QLabel为自定义通过setPixmap设置图片重写paintEvent绘制底部渐变矩形框和白色文字
- 字体颜色用qss设置 - 字体颜色用qss设置
- 图标利用了QSvgWidget显示可以是svg 动画(如圆形加载图) - 图标利用了QSvgWidget显示可以是svg 动画(如圆形加载图)
# 截图 # 截图
使用QGridLayout 固定列数效果图 使用QGridLayout 固定列数效果图
![截图1](ScreenShot/1.gif) ![截图1](ScreenShot/1.gif)
使用自定义布局FlowLayout 自动列数效果图 使用自定义布局FlowLayout 自动列数效果图
![截图2](ScreenShot/2.gif) ![截图2](ScreenShot/2.gif)
使用QListWidget 配合setFlow(QListWidget.LeftToRight)和 使用QListWidget 配合setFlow(QListWidget.LeftToRight)和
setWrapping(True)和 setWrapping(True)和
setResizeMode(QListWidget.Adjust)达到类似FlowLayout的效果 setResizeMode(QListWidget.Adjust)达到类似FlowLayout的效果
![截图3](ScreenShot/3.gif) ![截图3](ScreenShot/3.gif)

View file

Before

Width:  |  Height:  |  Size: 4.3 MiB

After

Width:  |  Height:  |  Size: 4.3 MiB

View file

Before

Width:  |  Height:  |  Size: 5 MiB

After

Width:  |  Height:  |  Size: 5 MiB

View file

Before

Width:  |  Height:  |  Size: 6.6 MiB

After

Width:  |  Height:  |  Size: 6.6 MiB

View file

@ -1,336 +1,336 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
Created on 2018年2月4日 Created on 2018年2月4日
@author: Irony."[讽刺] @author: Irony."[讽刺]
@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 @site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447
@email: 892768447@qq.com @email: 892768447@qq.com
@file: TencentMovieHotPlay @file: TencentMovieHotPlay
@description: @description:
''' '''
import os import os
import sys import sys
import webbrowser import webbrowser
from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
QBrush, QPaintEvent, QPixmap QBrush, QPaintEvent, QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\ QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QGridLayout,\
QAbstractSlider QAbstractSlider
from lxml.etree import HTML # @UnresolvedImport from lxml.etree import HTML # @UnresolvedImport
__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" __Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" __Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
__Version__ = "Version 1.0" __Version__ = "Version 1.0"
# offset=0,30,60,90 # offset=0,30,60,90
Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
# 播放量图标 # 播放量图标
Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1"> Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path> <path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path>
</svg> </svg>
'''.encode() '''.encode()
Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"> Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a"> <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
<stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/> <stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/>
<stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/> <stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/>
<stop stop-color="#03a9f4" offset="100%"/> <stop stop-color="#03a9f4" offset="100%"/>
</linearGradient> </linearGradient>
</defs> </defs>
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)"> <g transform="translate(1 1)">
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2"> <path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</path> </path>
<circle fill="#03a9f4" cx="36" cy="18" r="4"> <circle fill="#03a9f4" cx="36" cy="18" r="4">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</circle> </circle>
</g> </g>
</g> </g>
</svg>'''.encode() </svg>'''.encode()
# 主演 # 主演
Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;''' Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;'''
class CoverLabel(QLabel): class CoverLabel(QLabel):
def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
super(CoverLabel, self).__init__(*args, **kwargs) super(CoverLabel, self).__init__(*args, **kwargs)
self.setCursor(Qt.PointingHandCursor) self.setCursor(Qt.PointingHandCursor)
self.setScaledContents(True) self.setScaledContents(True)
self.setMinimumSize(220, 308) self.setMinimumSize(220, 308)
self.setMaximumSize(220, 308) self.setMaximumSize(220, 308)
self.cover_path = cover_path self.cover_path = cover_path
self.cover_title = cover_title self.cover_title = cover_title
self.video_url = video_url self.video_url = video_url
self.setPixmap(QPixmap(cover_path)) self.setPixmap(QPixmap(cover_path))
def setCoverPath(self, path): def setCoverPath(self, path):
self.cover_path = path self.cover_path = path
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
super(CoverLabel, self).mouseReleaseEvent(event) super(CoverLabel, self).mouseReleaseEvent(event)
webbrowser.open_new_tab(self.video_url) webbrowser.open_new_tab(self.video_url)
def paintEvent(self, event): def paintEvent(self, event):
super(CoverLabel, self).paintEvent(event) super(CoverLabel, self).paintEvent(event)
if hasattr(self, "cover_title") and self.cover_title != "": if hasattr(self, "cover_title") and self.cover_title != "":
# 底部绘制文字 # 底部绘制文字
painter = QPainter(self) painter = QPainter(self)
rect = self.rect() rect = self.rect()
# 粗略字体高度 # 粗略字体高度
painter.save() painter.save()
fheight = self.fontMetrics().height() fheight = self.fontMetrics().height()
# 底部矩形框背景渐变颜色 # 底部矩形框背景渐变颜色
bottomRectColor = QLinearGradient( bottomRectColor = QLinearGradient(
rect.width() / 2, rect.height() - 24 - fheight, rect.width() / 2, rect.height() - 24 - fheight,
rect.width() / 2, rect.height()) rect.width() / 2, rect.height())
bottomRectColor.setSpread(QGradient.PadSpread) bottomRectColor.setSpread(QGradient.PadSpread)
bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70))
bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50))
# 画半透明渐变矩形框 # 画半透明渐变矩形框
painter.setPen(Qt.NoPen) painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(bottomRectColor)) painter.setBrush(QBrush(bottomRectColor))
painter.drawRect(rect.x(), rect.height() - 24 - painter.drawRect(rect.x(), rect.height() - 24 -
fheight, rect.width(), 24 + fheight) fheight, rect.width(), 24 + fheight)
painter.restore() painter.restore()
# 距离底部一定高度画文字 # 距离底部一定高度画文字
font = self.font() or QFont() font = self.font() or QFont()
font.setPointSize(8) font.setPointSize(8)
painter.setFont(font) painter.setFont(font)
painter.setPen(Qt.white) painter.setPen(Qt.white)
rect.setHeight(rect.height() - 12) # 底部减去一定高度 rect.setHeight(rect.height() - 12) # 底部减去一定高度
painter.drawText(rect, Qt.AlignHCenter | painter.drawText(rect, Qt.AlignHCenter |
Qt.AlignBottom, self.cover_title) Qt.AlignBottom, self.cover_title)
class ItemWidget(QWidget): class ItemWidget(QWidget):
def __init__(self, cover_path, figure_info, figure_title, def __init__(self, cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs):
super(ItemWidget, self).__init__(*args, **kwargs) super(ItemWidget, self).__init__(*args, **kwargs)
self.setMaximumSize(220, 380) self.setMaximumSize(220, 380)
self.setMaximumSize(220, 380) self.setMaximumSize(220, 380)
self.img_path = img_path self.img_path = img_path
self.cover_url = cover_url self.cover_url = cover_url
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
# 图片label # 图片label
self.clabel = CoverLabel(cover_path, figure_info, video_url, self) self.clabel = CoverLabel(cover_path, figure_info, video_url, self)
layout.addWidget(self.clabel) layout.addWidget(self.clabel)
# 片名和分数 # 片名和分数
flayout = QHBoxLayout() flayout = QHBoxLayout()
flayout.addWidget(QLabel(figure_title, self)) flayout.addWidget(QLabel(figure_title, self))
flayout.addItem(QSpacerItem( flayout.addItem(QSpacerItem(
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;"))
layout.addLayout(flayout) layout.addLayout(flayout)
# 主演 # 主演
layout.addWidget( layout.addWidget(
QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True))
# 播放量 # 播放量
blayout = QHBoxLayout() blayout = QHBoxLayout()
count_icon = QSvgWidget(self) count_icon = QSvgWidget(self)
count_icon.setMaximumSize(16, 16) count_icon.setMaximumSize(16, 16)
count_icon.load(Svg_icon_play_sm) count_icon.load(Svg_icon_play_sm)
blayout.addWidget(count_icon) blayout.addWidget(count_icon)
blayout.addWidget( blayout.addWidget(
QLabel(figure_count, self, styleSheet="color: #999999;")) QLabel(figure_count, self, styleSheet="color: #999999;"))
layout.addLayout(blayout) layout.addLayout(blayout)
def setCover(self, path): def setCover(self, path):
self.clabel.setCoverPath(path) self.clabel.setCoverPath(path)
self.clabel.setPixmap(QPixmap(path)) self.clabel.setPixmap(QPixmap(path))
# self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path))) # self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path)))
def sizeHint(self): def sizeHint(self):
# 每个item控件的大小 # 每个item控件的大小
return QSize(220, 380) return QSize(220, 380)
def event(self, event): def event(self, event):
if isinstance(event, QPaintEvent): if isinstance(event, QPaintEvent):
if event.rect().height() > 20 and hasattr(self, "clabel"): if event.rect().height() > 20 and hasattr(self, "clabel"):
if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载
# print("start download img:", self.cover_url) # print("start download img:", self.cover_url)
req = QNetworkRequest(QUrl(self.cover_url)) req = QNetworkRequest(QUrl(self.cover_url))
# 设置两个自定义属性方便后期reply中处理 # 设置两个自定义属性方便后期reply中处理
req.setAttribute(QNetworkRequest.User + 1, self) req.setAttribute(QNetworkRequest.User + 1, self)
req.setAttribute(QNetworkRequest.User + 2, self.img_path) req.setAttribute(QNetworkRequest.User + 2, self.img_path)
self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载
return super(ItemWidget, self).event(event) return super(ItemWidget, self).event(event)
class GridWidget(QWidget): class GridWidget(QWidget):
Page = 0 Page = 0
loadStarted = pyqtSignal(bool) loadStarted = pyqtSignal(bool)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(GridWidget, self).__init__(*args, **kwargs) super(GridWidget, self).__init__(*args, **kwargs)
self._layout = QGridLayout(self, spacing=20) self._layout = QGridLayout(self, spacing=20)
self._layout.setContentsMargins(20, 20, 20, 20) self._layout.setContentsMargins(20, 20, 20, 20)
# 异步网络下载管理器 # 异步网络下载管理器
self._manager = QNetworkAccessManager(self) self._manager = QNetworkAccessManager(self)
self._manager.finished.connect(self.onFinished) self._manager.finished.connect(self.onFinished)
def load(self): def load(self):
if self.Page == -1: if self.Page == -1:
return return
self.loadStarted.emit(True) self.loadStarted.emit(True)
# 延迟一秒后调用目的在于显示进度条 # 延迟一秒后调用目的在于显示进度条
QTimer.singleShot(1000, self._load) QTimer.singleShot(1000, self._load)
def _load(self): def _load(self):
print("load url:", Url.format(self.Page * 30)) print("load url:", Url.format(self.Page * 30))
url = QUrl(Url.format(self.Page * 30)) url = QUrl(Url.format(self.Page * 30))
self._manager.get(QNetworkRequest(url)) self._manager.get(QNetworkRequest(url))
def onFinished(self, reply): def onFinished(self, reply):
# 请求完成后会调用该函数 # 请求完成后会调用该函数
req = reply.request() # 获取请求 req = reply.request() # 获取请求
iwidget = req.attribute(QNetworkRequest.User + 1, None) iwidget = req.attribute(QNetworkRequest.User + 1, None)
path = req.attribute(QNetworkRequest.User + 2, None) path = req.attribute(QNetworkRequest.User + 2, None)
html = reply.readAll().data() html = reply.readAll().data()
reply.deleteLater() reply.deleteLater()
del reply del reply
if iwidget and path and html: if iwidget and path and html:
# 这里是图片下载完毕 # 这里是图片下载完毕
open(path, "wb").write(html) open(path, "wb").write(html)
iwidget.setCover(path) iwidget.setCover(path)
return return
# 解析网页 # 解析网页
self._parseHtml(html) self._parseHtml(html)
self.loadStarted.emit(False) self.loadStarted.emit(False)
def splist(self, src, length): def splist(self, src, length):
# 等分列表 # 等分列表
return (src[i:i + length] for i in range(len(src)) if i % length == 0) return (src[i:i + length] for i in range(len(src)) if i % length == 0)
def _parseHtml(self, html): def _parseHtml(self, html):
# encoding = chardet.detect(html) or {} # encoding = chardet.detect(html) or {}
# html = html.decode(encoding.get("encoding","utf-8")) # html = html.decode(encoding.get("encoding","utf-8"))
html = HTML(html) html = HTML(html)
# 查找所有的li list_item # 查找所有的li list_item
lis = html.xpath("//li[@class='list_item']") lis = html.xpath("//li[@class='list_item']")
if not lis: if not lis:
self.Page = -1 # 后面没有页面了 self.Page = -1 # 后面没有页面了
return return
lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准 lack_count = self._layout.count() % 30 # 获取布局中上次还缺几个5行*6列的标准
row_count = int(self._layout.count() / 6) # 行数 row_count = int(self._layout.count() / 6) # 行数
print("lack_count:", lack_count) print("lack_count:", lack_count)
self.Page += 1 # 自增+1 self.Page += 1 # 自增+1
if lack_count != 0: # 上一次没有满足一行6个,需要补齐 if lack_count != 0: # 上一次没有满足一行6个,需要补齐
lack_li = lis[:lack_count] lack_li = lis[:lack_count]
lis = lis[lack_count:] lis = lis[lack_count:]
self._makeItem(lack_li, row_count) # 补齐 self._makeItem(lack_li, row_count) # 补齐
if lack_li and lis: if lack_li and lis:
row_count += 1 row_count += 1
self._makeItem(lis, row_count) # 完成剩下的 self._makeItem(lis, row_count) # 完成剩下的
else: else:
self._makeItem(lis, row_count) self._makeItem(lis, row_count)
def _makeItem(self, li_s, row_count): def _makeItem(self, li_s, row_count):
li_s = self.splist(li_s, 6) li_s = self.splist(li_s, 6)
for row, lis in enumerate(li_s): for row, lis in enumerate(li_s):
for col, li in enumerate(lis): for col, li in enumerate(lis):
a = li.find("a") a = li.find("a")
video_url = a.get("href") # 视频播放地址 video_url = a.get("href") # 视频播放地址
img = a.find("img") img = a.find("img")
cover_url = "http:" + img.get("r-lazyload") # 封面图片 cover_url = "http:" + img.get("r-lazyload") # 封面图片
figure_title = img.get("alt") # 电影名 figure_title = img.get("alt") # 电影名
figure_info = a.find("div/span") figure_info = a.find("div/span")
figure_info = "" if figure_info is None else figure_info.text # 影片信息 figure_info = "" if figure_info is None else figure_info.text # 影片信息
figure_score = "".join(li.xpath(".//em/text()")) # 评分 figure_score = "".join(li.xpath(".//em/text()")) # 评分
# 主演 # 主演
figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \ figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \
"".join([Actor.format(**dict(fd.items())) "".join([Actor.format(**dict(fd.items()))
for fd in li.xpath(".//div[@class='figure_desc']/a")]) for fd in li.xpath(".//div[@class='figure_desc']/a")])
# 播放数 # 播放数
figure_count = ( figure_count = (
li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
path = "cache/{0}.jpg".format( path = "cache/{0}.jpg".format(
os.path.splitext(os.path.basename(video_url))[0]) os.path.splitext(os.path.basename(video_url))[0])
cover_path = "pic_v.png" cover_path = "pic_v.png"
if os.path.isfile(path): if os.path.isfile(path):
cover_path = path cover_path = path
iwidget = ItemWidget(cover_path, figure_info, figure_title, iwidget = ItemWidget(cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, path, self) figure_score, figure_desc, figure_count, video_url, cover_url, path, self)
self._layout.addWidget(iwidget, row_count + row, col) self._layout.addWidget(iwidget, row_count + row, col)
class Window(QScrollArea): class Window(QScrollArea):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs) super(Window, self).__init__(*args, **kwargs)
self.resize(800, 600) self.resize(800, 600)
self.setFrameShape(self.NoFrame) self.setFrameShape(self.NoFrame)
self.setWidgetResizable(True) self.setWidgetResizable(True)
self.setAlignment(Qt.AlignCenter) self.setAlignment(Qt.AlignCenter)
self._loadStart = False self._loadStart = False
# 网格窗口 # 网格窗口
self._widget = GridWidget(self) self._widget = GridWidget(self)
self._widget.loadStarted.connect(self.setLoadStarted) self._widget.loadStarted.connect(self.setLoadStarted)
self.setWidget(self._widget) self.setWidget(self._widget)
# 连接竖着的滚动条滚动事件 # 连接竖着的滚动条滚动事件
self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered)
# 进度条 # 进度条
self.loadWidget = QSvgWidget( self.loadWidget = QSvgWidget(
self, minimumHeight=120, minimumWidth=120, visible=False) self, minimumHeight=120, minimumWidth=120, visible=False)
self.loadWidget.load(Svg_icon_loading) self.loadWidget.load(Svg_icon_loading)
def setLoadStarted(self, started): def setLoadStarted(self, started):
self._loadStart = started self._loadStart = started
self.loadWidget.setVisible(started) self.loadWidget.setVisible(started)
def onActionTriggered(self, action): def onActionTriggered(self, action):
# 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题 # 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题
# 同时防止多次加载同一个url # 同时防止多次加载同一个url
if action != QAbstractSlider.SliderMove or self._loadStart: if action != QAbstractSlider.SliderMove or self._loadStart:
return return
# 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
# 可以下一页了 # 可以下一页了
self._widget.load() self._widget.load()
def resizeEvent(self, event): def resizeEvent(self, event):
super(Window, self).resizeEvent(event) super(Window, self).resizeEvent(event)
self.loadWidget.setGeometry( self.loadWidget.setGeometry(
int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.width() - self.loadWidget.minimumWidth()) / 2),
int((self.height() - self.loadWidget.minimumHeight()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2),
self.loadWidget.minimumWidth(), self.loadWidget.minimumWidth(),
self.loadWidget.minimumHeight() self.loadWidget.minimumHeight()
) )
if __name__ == "__main__": if __name__ == "__main__":
os.makedirs("cache", exist_ok=True) os.makedirs("cache", exist_ok=True)
app = QApplication(sys.argv) app = QApplication(sys.argv)
w = Window() w = Window()
w.show() w.show()
w._widget.load() w._widget.load()
sys.exit(app.exec_()) sys.exit(app.exec_())

View file

@ -1,320 +1,320 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
Created on 2018年2月4日 Created on 2018年2月4日
@author: Irony."[讽刺] @author: Irony."[讽刺]
@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 @site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447
@email: 892768447@qq.com @email: 892768447@qq.com
@file: TencentMovieHotPlay_Flow @file: TencentMovieHotPlay_Flow
@description: @description:
''' '''
import os import os
import sys import sys
import webbrowser import webbrowser
from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal from PyQt5.QtCore import QSize, Qt, QUrl, QTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
QBrush, QPaintEvent, QPixmap QBrush, QPaintEvent, QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider QHBoxLayout, QSpacerItem, QSizePolicy, QScrollArea, QAbstractSlider
from flowlayout import FlowLayout # @UnresolvedImport from flowlayout import FlowLayout # @UnresolvedImport
from lxml.etree import HTML # @UnresolvedImport from lxml.etree import HTML # @UnresolvedImport
__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" __Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" __Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
__Version__ = "Version 1.0" __Version__ = "Version 1.0"
# offset=0,30,60,90 # offset=0,30,60,90
Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
# 播放量图标 # 播放量图标
Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1"> Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path> <path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path>
</svg> </svg>
'''.encode() '''.encode()
Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"> Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a"> <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
<stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/> <stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/>
<stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/> <stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/>
<stop stop-color="#03a9f4" offset="100%"/> <stop stop-color="#03a9f4" offset="100%"/>
</linearGradient> </linearGradient>
</defs> </defs>
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)"> <g transform="translate(1 1)">
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2"> <path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</path> </path>
<circle fill="#03a9f4" cx="36" cy="18" r="4"> <circle fill="#03a9f4" cx="36" cy="18" r="4">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</circle> </circle>
</g> </g>
</g> </g>
</svg>'''.encode() </svg>'''.encode()
# 主演 # 主演
Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;''' Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;'''
class CoverLabel(QLabel): class CoverLabel(QLabel):
def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
super(CoverLabel, self).__init__(*args, **kwargs) super(CoverLabel, self).__init__(*args, **kwargs)
# super(CoverLabel, self).__init__( # super(CoverLabel, self).__init__(
# '<html><head/><body><img src="{0}"/></body></html>'.format(os.path.abspath(cover_path)), *args, **kwargs) # '<html><head/><body><img src="{0}"/></body></html>'.format(os.path.abspath(cover_path)), *args, **kwargs)
self.setCursor(Qt.PointingHandCursor) self.setCursor(Qt.PointingHandCursor)
self.setScaledContents(True) self.setScaledContents(True)
self.setMinimumSize(220, 308) self.setMinimumSize(220, 308)
self.setMaximumSize(220, 308) self.setMaximumSize(220, 308)
self.cover_path = cover_path self.cover_path = cover_path
self.cover_title = cover_title self.cover_title = cover_title
self.video_url = video_url self.video_url = video_url
self.setPixmap(QPixmap(cover_path)) self.setPixmap(QPixmap(cover_path))
def setCoverPath(self, path): def setCoverPath(self, path):
self.cover_path = path self.cover_path = path
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
super(CoverLabel, self).mouseReleaseEvent(event) super(CoverLabel, self).mouseReleaseEvent(event)
webbrowser.open_new_tab(self.video_url) webbrowser.open_new_tab(self.video_url)
def paintEvent(self, event): def paintEvent(self, event):
super(CoverLabel, self).paintEvent(event) super(CoverLabel, self).paintEvent(event)
if hasattr(self, "cover_title") and self.cover_title != "": if hasattr(self, "cover_title") and self.cover_title != "":
# 底部绘制文字 # 底部绘制文字
painter = QPainter(self) painter = QPainter(self)
rect = self.rect() rect = self.rect()
# 粗略字体高度 # 粗略字体高度
painter.save() painter.save()
fheight = self.fontMetrics().height() fheight = self.fontMetrics().height()
# 底部矩形框背景渐变颜色 # 底部矩形框背景渐变颜色
bottomRectColor = QLinearGradient( bottomRectColor = QLinearGradient(
rect.width() / 2, rect.height() - 24 - fheight, rect.width() / 2, rect.height() - 24 - fheight,
rect.width() / 2, rect.height()) rect.width() / 2, rect.height())
bottomRectColor.setSpread(QGradient.PadSpread) bottomRectColor.setSpread(QGradient.PadSpread)
bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70))
bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50))
# 画半透明渐变矩形框 # 画半透明渐变矩形框
painter.setPen(Qt.NoPen) painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(bottomRectColor)) painter.setBrush(QBrush(bottomRectColor))
painter.drawRect(rect.x(), rect.height() - 24 - painter.drawRect(rect.x(), rect.height() - 24 -
fheight, rect.width(), 24 + fheight) fheight, rect.width(), 24 + fheight)
painter.restore() painter.restore()
# 距离底部一定高度画文字 # 距离底部一定高度画文字
font = self.font() or QFont() font = self.font() or QFont()
font.setPointSize(8) font.setPointSize(8)
painter.setFont(font) painter.setFont(font)
painter.setPen(Qt.white) painter.setPen(Qt.white)
rect.setHeight(rect.height() - 12) # 底部减去一定高度 rect.setHeight(rect.height() - 12) # 底部减去一定高度
painter.drawText(rect, Qt.AlignHCenter | painter.drawText(rect, Qt.AlignHCenter |
Qt.AlignBottom, self.cover_title) Qt.AlignBottom, self.cover_title)
class ItemWidget(QWidget): class ItemWidget(QWidget):
def __init__(self, cover_path, figure_info, figure_title, def __init__(self, cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs): figure_score, figure_desc, figure_count, video_url, cover_url, img_path, *args, **kwargs):
super(ItemWidget, self).__init__(*args, **kwargs) super(ItemWidget, self).__init__(*args, **kwargs)
self.setMaximumSize(220, 420) self.setMaximumSize(220, 420)
self.setMaximumSize(220, 420) self.setMaximumSize(220, 420)
self.img_path = img_path self.img_path = img_path
self.cover_url = cover_url self.cover_url = cover_url
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(10, 20, 10, 0) layout.setContentsMargins(10, 20, 10, 0)
# 图片label # 图片label
self.clabel = CoverLabel(cover_path, figure_info, video_url, self) self.clabel = CoverLabel(cover_path, figure_info, video_url, self)
layout.addWidget(self.clabel) layout.addWidget(self.clabel)
# 片名和分数 # 片名和分数
flayout = QHBoxLayout() flayout = QHBoxLayout()
flayout.addWidget(QLabel(figure_title, self)) flayout.addWidget(QLabel(figure_title, self))
flayout.addItem(QSpacerItem( flayout.addItem(QSpacerItem(
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;"))
layout.addLayout(flayout) layout.addLayout(flayout)
# 主演 # 主演
layout.addWidget( layout.addWidget(
QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True))
# 播放量 # 播放量
blayout = QHBoxLayout() blayout = QHBoxLayout()
count_icon = QSvgWidget(self) count_icon = QSvgWidget(self)
count_icon.setMaximumSize(16, 16) count_icon.setMaximumSize(16, 16)
count_icon.load(Svg_icon_play_sm) count_icon.load(Svg_icon_play_sm)
blayout.addWidget(count_icon) blayout.addWidget(count_icon)
blayout.addWidget( blayout.addWidget(
QLabel(figure_count, self, styleSheet="color: #999999;")) QLabel(figure_count, self, styleSheet="color: #999999;"))
layout.addLayout(blayout) layout.addLayout(blayout)
def setCover(self, path): def setCover(self, path):
self.clabel.setCoverPath(path) self.clabel.setCoverPath(path)
self.clabel.setPixmap(QPixmap(path)) self.clabel.setPixmap(QPixmap(path))
# self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path))) # self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path)))
def sizeHint(self): def sizeHint(self):
# 每个item控件的大小 # 每个item控件的大小
return QSize(220, 420) return QSize(220, 420)
def event(self, event): def event(self, event):
if isinstance(event, QPaintEvent): if isinstance(event, QPaintEvent):
if event.rect().height() > 20 and hasattr(self, "clabel"): if event.rect().height() > 20 and hasattr(self, "clabel"):
if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载
# print("start download img:", self.cover_url) # print("start download img:", self.cover_url)
req = QNetworkRequest(QUrl(self.cover_url)) req = QNetworkRequest(QUrl(self.cover_url))
# 设置两个自定义属性方便后期reply中处理 # 设置两个自定义属性方便后期reply中处理
req.setAttribute(QNetworkRequest.User + 1, self) req.setAttribute(QNetworkRequest.User + 1, self)
req.setAttribute(QNetworkRequest.User + 2, self.img_path) req.setAttribute(QNetworkRequest.User + 2, self.img_path)
self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载 self.parentWidget()._manager.get(req) # 调用父窗口中的下载器下载
return super(ItemWidget, self).event(event) return super(ItemWidget, self).event(event)
class GridWidget(QWidget): class GridWidget(QWidget):
Page = 0 Page = 0
loadStarted = pyqtSignal(bool) loadStarted = pyqtSignal(bool)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(GridWidget, self).__init__(*args, **kwargs) super(GridWidget, self).__init__(*args, **kwargs)
self._layout = FlowLayout(self) # 使用自定义流式布局 self._layout = FlowLayout(self) # 使用自定义流式布局
# 异步网络下载管理器 # 异步网络下载管理器
self._manager = QNetworkAccessManager(self) self._manager = QNetworkAccessManager(self)
self._manager.finished.connect(self.onFinished) self._manager.finished.connect(self.onFinished)
def load(self): def load(self):
if self.Page == -1: if self.Page == -1:
return return
self.loadStarted.emit(True) self.loadStarted.emit(True)
# 延迟一秒后调用目的在于显示进度条 # 延迟一秒后调用目的在于显示进度条
QTimer.singleShot(1000, self._load) QTimer.singleShot(1000, self._load)
def _load(self): def _load(self):
print("load url:", Url.format(self.Page * 30)) print("load url:", Url.format(self.Page * 30))
url = QUrl(Url.format(self.Page * 30)) url = QUrl(Url.format(self.Page * 30))
self._manager.get(QNetworkRequest(url)) self._manager.get(QNetworkRequest(url))
def onFinished(self, reply): def onFinished(self, reply):
# 请求完成后会调用该函数 # 请求完成后会调用该函数
req = reply.request() # 获取请求 req = reply.request() # 获取请求
iwidget = req.attribute(QNetworkRequest.User + 1, None) iwidget = req.attribute(QNetworkRequest.User + 1, None)
path = req.attribute(QNetworkRequest.User + 2, None) path = req.attribute(QNetworkRequest.User + 2, None)
html = reply.readAll().data() html = reply.readAll().data()
reply.deleteLater() reply.deleteLater()
del reply del reply
if iwidget and path and html: if iwidget and path and html:
# 这里是图片下载完毕 # 这里是图片下载完毕
open(path, "wb").write(html) open(path, "wb").write(html)
iwidget.setCover(path) iwidget.setCover(path)
return return
# 解析网页 # 解析网页
self._parseHtml(html) self._parseHtml(html)
self.loadStarted.emit(False) self.loadStarted.emit(False)
def _parseHtml(self, html): def _parseHtml(self, html):
# encoding = chardet.detect(html) or {} # encoding = chardet.detect(html) or {}
# html = html.decode(encoding.get("encoding","utf-8")) # html = html.decode(encoding.get("encoding","utf-8"))
html = HTML(html) html = HTML(html)
# 查找所有的li list_item # 查找所有的li list_item
lis = html.xpath("//li[@class='list_item']") lis = html.xpath("//li[@class='list_item']")
if not lis: if not lis:
self.Page = -1 # 后面没有页面了 self.Page = -1 # 后面没有页面了
return return
self.Page += 1 self.Page += 1
self._makeItem(lis) self._makeItem(lis)
def _makeItem(self, lis): def _makeItem(self, lis):
for li in lis: for li in lis:
a = li.find("a") a = li.find("a")
video_url = a.get("href") # 视频播放地址 video_url = a.get("href") # 视频播放地址
img = a.find("img") img = a.find("img")
cover_url = "http:" + img.get("r-lazyload") # 封面图片 cover_url = "http:" + img.get("r-lazyload") # 封面图片
figure_title = img.get("alt") # 电影名 figure_title = img.get("alt") # 电影名
figure_info = a.find("div/span") figure_info = a.find("div/span")
figure_info = "" if figure_info is None else figure_info.text # 影片信息 figure_info = "" if figure_info is None else figure_info.text # 影片信息
figure_score = "".join(li.xpath(".//em/text()")) # 评分 figure_score = "".join(li.xpath(".//em/text()")) # 评分
# 主演 # 主演
figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \ figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \
"".join([Actor.format(**dict(fd.items())) "".join([Actor.format(**dict(fd.items()))
for fd in li.xpath(".//div[@class='figure_desc']/a")]) for fd in li.xpath(".//div[@class='figure_desc']/a")])
# 播放数 # 播放数
figure_count = ( figure_count = (
li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
path = "cache/{0}.jpg".format( path = "cache/{0}.jpg".format(
os.path.splitext(os.path.basename(video_url))[0]) os.path.splitext(os.path.basename(video_url))[0])
cover_path = "pic_v.png" cover_path = "pic_v.png"
if os.path.isfile(path): if os.path.isfile(path):
cover_path = path cover_path = path
iwidget = ItemWidget(cover_path, figure_info, figure_title, iwidget = ItemWidget(cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, path, self) figure_score, figure_desc, figure_count, video_url, cover_url, path, self)
self._layout.addWidget(iwidget) self._layout.addWidget(iwidget)
class Window(QScrollArea): class Window(QScrollArea):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs) super(Window, self).__init__(*args, **kwargs)
self.resize(800, 600) self.resize(800, 600)
self.setFrameShape(self.NoFrame) self.setFrameShape(self.NoFrame)
self.setWidgetResizable(True) self.setWidgetResizable(True)
self.setAlignment(Qt.AlignCenter) self.setAlignment(Qt.AlignCenter)
self._loadStart = False self._loadStart = False
# 网格窗口 # 网格窗口
self._widget = GridWidget(self) self._widget = GridWidget(self)
self._widget.loadStarted.connect(self.setLoadStarted) self._widget.loadStarted.connect(self.setLoadStarted)
self.setWidget(self._widget) self.setWidget(self._widget)
# 连接竖着的滚动条滚动事件 # 连接竖着的滚动条滚动事件
self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered)
# 进度条 # 进度条
self.loadWidget = QSvgWidget( self.loadWidget = QSvgWidget(
self, minimumHeight=120, minimumWidth=120, visible=False) self, minimumHeight=120, minimumWidth=120, visible=False)
self.loadWidget.load(Svg_icon_loading) self.loadWidget.load(Svg_icon_loading)
def setLoadStarted(self, started): def setLoadStarted(self, started):
self._loadStart = started self._loadStart = started
self.loadWidget.setVisible(started) self.loadWidget.setVisible(started)
def onActionTriggered(self, action): def onActionTriggered(self, action):
# 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题 # 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题
# 同时防止多次加载同一个url # 同时防止多次加载同一个url
if action != QAbstractSlider.SliderMove or self._loadStart: if action != QAbstractSlider.SliderMove or self._loadStart:
return return
# 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
# 可以下一页了 # 可以下一页了
self._widget.load() self._widget.load()
def resizeEvent(self, event): def resizeEvent(self, event):
super(Window, self).resizeEvent(event) super(Window, self).resizeEvent(event)
self.loadWidget.setGeometry( self.loadWidget.setGeometry(
int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.width() - self.loadWidget.minimumWidth()) / 2),
int((self.height() - self.loadWidget.minimumHeight()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2),
self.loadWidget.minimumWidth(), self.loadWidget.minimumWidth(),
self.loadWidget.minimumHeight() self.loadWidget.minimumHeight()
) )
if __name__ == "__main__": if __name__ == "__main__":
os.makedirs("cache", exist_ok=True) os.makedirs("cache", exist_ok=True)
app = QApplication(sys.argv) app = QApplication(sys.argv)
w = Window() w = Window()
w.show() w.show()
w._widget.load() w._widget.load()
sys.exit(app.exec_()) sys.exit(app.exec_())

View file

@ -1,312 +1,312 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
Created on 2018年2月4日 Created on 2018年2月4日
@author: Irony."[讽刺] @author: Irony."[讽刺]
@site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447 @site: http://alyl.vip, http://orzorz.vip, https://coding.net/u/892768447, https://github.com/892768447
@email: 892768447@qq.com @email: 892768447@qq.com
@file: TencentMovieHotPlay_ListWidget @file: TencentMovieHotPlay_ListWidget
@description: @description:
''' '''
import os import os
import sys import sys
import webbrowser import webbrowser
from PyQt5.QtCore import QSize, Qt, QUrl, QTimer from PyQt5.QtCore import QSize, Qt, QUrl, QTimer
from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\ from PyQt5.QtGui import QPainter, QFont, QLinearGradient, QGradient, QColor,\
QBrush, QPaintEvent, QPixmap QBrush, QPaintEvent, QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\ from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel,\
QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\ QHBoxLayout, QSpacerItem, QSizePolicy, QAbstractSlider,\
QListWidget, QListWidgetItem QListWidget, QListWidgetItem
from lxml.etree import HTML # @UnresolvedImport from lxml.etree import HTML # @UnresolvedImport
__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" __Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
__Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]" __Copyright__ = "Copyright (c) 2018 Irony.\"[讽刺]"
__Version__ = "Version 1.0" __Version__ = "Version 1.0"
# offset=0,30,60,90 # offset=0,30,60,90
Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}" Url = "http://v.qq.com/x/list/movie?pay=-1&offset={0}"
# 播放量图标 # 播放量图标
Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1"> Svg_icon_play_sm = '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path> <path d="M10.83 8.31v.022l-4.08 2.539-.005.003-.048.03-.012-.005c-.073.051-.15.101-.246.101-.217 0-.376-.165-.413-.369l-.027-.011V5.461l.009-.005c0-.009-.009-.014-.009-.022 0-.24.197-.435.44-.435.096 0 .174.049.247.101l.031-.017 4.129 2.569v.016a.42.42 0 0 1 .153.317.418.418 0 0 1-.169.325zm3.493 2.604a.986.986 0 0 1-.948.742 1 1 0 0 1-1-1 .98.98 0 0 1 .094-.412l-.019-.01C12.79 9.559 13 8.807 13 8a5 5 0 1 0-5 5c.766 0 1.484-.186 2.133-.494l.013.03a.975.975 0 0 1 .417-.097 1 1 0 0 1 1 1 .987.987 0 0 1-.77.954A6.936 6.936 0 0 1 8 14.999a7 7 0 1 1 7-7c0 1.048-.261 2.024-.677 2.915z" fill="#999999"></path>
</svg> </svg>
'''.encode() '''.encode()
Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"> Svg_icon_loading = '''<svg width="100%" height="100%" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a"> <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
<stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/> <stop stop-color="#03a9f4" stop-opacity="0" offset="0%"/>
<stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/> <stop stop-color="#03a9f4" stop-opacity=".631" offset="63.146%"/>
<stop stop-color="#03a9f4" offset="100%"/> <stop stop-color="#03a9f4" offset="100%"/>
</linearGradient> </linearGradient>
</defs> </defs>
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)"> <g transform="translate(1 1)">
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2"> <path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</path> </path>
<circle fill="#03a9f4" cx="36" cy="18" r="4"> <circle fill="#03a9f4" cx="36" cy="18" r="4">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="0.5s" dur="0.5s"
repeatCount="indefinite" /> repeatCount="indefinite" />
</circle> </circle>
</g> </g>
</g> </g>
</svg>'''.encode() </svg>'''.encode()
# 主演 # 主演
Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;''' Actor = '''<a href="{href}" target="_blank" title="{title}" style="text-decoration: none;font-size: 12px;color: #999999;">{title}</a>&nbsp;'''
class CoverLabel(QLabel): class CoverLabel(QLabel):
def __init__(self, cover_path, cover_title, video_url, *args, **kwargs): def __init__(self, cover_path, cover_title, video_url, *args, **kwargs):
super(CoverLabel, self).__init__(*args, **kwargs) super(CoverLabel, self).__init__(*args, **kwargs)
# super(CoverLabel, self).__init__( # super(CoverLabel, self).__init__(
# '<html><head/><body><img src="{0}"/></body></html>'.format(os.path.abspath(cover_path)), *args, **kwargs) # '<html><head/><body><img src="{0}"/></body></html>'.format(os.path.abspath(cover_path)), *args, **kwargs)
self.setCursor(Qt.PointingHandCursor) self.setCursor(Qt.PointingHandCursor)
self.setScaledContents(True) self.setScaledContents(True)
self.setMinimumSize(220, 308) self.setMinimumSize(220, 308)
self.setMaximumSize(220, 308) self.setMaximumSize(220, 308)
self.cover_path = cover_path self.cover_path = cover_path
self.cover_title = cover_title self.cover_title = cover_title
self.video_url = video_url self.video_url = video_url
self.setPixmap(QPixmap(cover_path)) self.setPixmap(QPixmap(cover_path))
def setCoverPath(self, path): def setCoverPath(self, path):
self.cover_path = path self.cover_path = path
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
super(CoverLabel, self).mouseReleaseEvent(event) super(CoverLabel, self).mouseReleaseEvent(event)
webbrowser.open_new_tab(self.video_url) webbrowser.open_new_tab(self.video_url)
def paintEvent(self, event): def paintEvent(self, event):
super(CoverLabel, self).paintEvent(event) super(CoverLabel, self).paintEvent(event)
if hasattr(self, "cover_title") and self.cover_title != "": if hasattr(self, "cover_title") and self.cover_title != "":
# 底部绘制文字 # 底部绘制文字
painter = QPainter(self) painter = QPainter(self)
rect = self.rect() rect = self.rect()
# 粗略字体高度 # 粗略字体高度
painter.save() painter.save()
fheight = self.fontMetrics().height() fheight = self.fontMetrics().height()
# 底部矩形框背景渐变颜色 # 底部矩形框背景渐变颜色
bottomRectColor = QLinearGradient( bottomRectColor = QLinearGradient(
rect.width() / 2, rect.height() - 24 - fheight, rect.width() / 2, rect.height() - 24 - fheight,
rect.width() / 2, rect.height()) rect.width() / 2, rect.height())
bottomRectColor.setSpread(QGradient.PadSpread) bottomRectColor.setSpread(QGradient.PadSpread)
bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70)) bottomRectColor.setColorAt(0, QColor(255, 255, 255, 70))
bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50)) bottomRectColor.setColorAt(1, QColor(0, 0, 0, 50))
# 画半透明渐变矩形框 # 画半透明渐变矩形框
painter.setPen(Qt.NoPen) painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(bottomRectColor)) painter.setBrush(QBrush(bottomRectColor))
painter.drawRect(rect.x(), rect.height() - 24 - painter.drawRect(rect.x(), rect.height() - 24 -
fheight, rect.width(), 24 + fheight) fheight, rect.width(), 24 + fheight)
painter.restore() painter.restore()
# 距离底部一定高度画文字 # 距离底部一定高度画文字
font = self.font() or QFont() font = self.font() or QFont()
font.setPointSize(8) font.setPointSize(8)
painter.setFont(font) painter.setFont(font)
painter.setPen(Qt.white) painter.setPen(Qt.white)
rect.setHeight(rect.height() - 12) # 底部减去一定高度 rect.setHeight(rect.height() - 12) # 底部减去一定高度
painter.drawText(rect, Qt.AlignHCenter | painter.drawText(rect, Qt.AlignHCenter |
Qt.AlignBottom, self.cover_title) Qt.AlignBottom, self.cover_title)
class ItemWidget(QWidget): class ItemWidget(QWidget):
def __init__(self, cover_path, figure_info, figure_title, def __init__(self, cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, img_path, manager, *args, **kwargs): figure_score, figure_desc, figure_count, video_url, cover_url, img_path, manager, *args, **kwargs):
super(ItemWidget, self).__init__(*args, **kwargs) super(ItemWidget, self).__init__(*args, **kwargs)
self.setMaximumSize(220, 420) self.setMaximumSize(220, 420)
self.setMaximumSize(220, 420) self.setMaximumSize(220, 420)
self.img_path = img_path self.img_path = img_path
self.cover_url = cover_url self.cover_url = cover_url
self._manager = manager self._manager = manager
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(10, 20, 10, 0) layout.setContentsMargins(10, 20, 10, 0)
# 图片label # 图片label
self.clabel = CoverLabel(cover_path, figure_info, video_url, self) self.clabel = CoverLabel(cover_path, figure_info, video_url, self)
layout.addWidget(self.clabel) layout.addWidget(self.clabel)
# 片名和分数 # 片名和分数
flayout = QHBoxLayout() flayout = QHBoxLayout()
flayout.addWidget(QLabel(figure_title, self)) flayout.addWidget(QLabel(figure_title, self))
flayout.addItem(QSpacerItem( flayout.addItem(QSpacerItem(
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) 20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;")) flayout.addWidget(QLabel(figure_score, self, styleSheet="color: red;"))
layout.addLayout(flayout) layout.addLayout(flayout)
# 主演 # 主演
layout.addWidget( layout.addWidget(
QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True)) QLabel(figure_desc, self, styleSheet="color: #999999;", openExternalLinks=True))
# 播放量 # 播放量
blayout = QHBoxLayout() blayout = QHBoxLayout()
count_icon = QSvgWidget(self) count_icon = QSvgWidget(self)
count_icon.setMaximumSize(16, 16) count_icon.setMaximumSize(16, 16)
count_icon.load(Svg_icon_play_sm) count_icon.load(Svg_icon_play_sm)
blayout.addWidget(count_icon) blayout.addWidget(count_icon)
blayout.addWidget( blayout.addWidget(
QLabel(figure_count, self, styleSheet="color: #999999;")) QLabel(figure_count, self, styleSheet="color: #999999;"))
layout.addLayout(blayout) layout.addLayout(blayout)
def setCover(self, path): def setCover(self, path):
self.clabel.setCoverPath(path) self.clabel.setCoverPath(path)
self.clabel.setPixmap(QPixmap(path)) self.clabel.setPixmap(QPixmap(path))
# self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path))) # self.clabel.setText('<img src="{0}"/>'.format(os.path.abspath(path)))
def sizeHint(self): def sizeHint(self):
# 每个item控件的大小 # 每个item控件的大小
return QSize(220, 420) return QSize(220, 420)
def event(self, event): def event(self, event):
if isinstance(event, QPaintEvent): if isinstance(event, QPaintEvent):
if event.rect().height() > 20 and hasattr(self, "clabel"): if event.rect().height() > 20 and hasattr(self, "clabel"):
if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载 if self.clabel.cover_path.find("pic_v.png") > -1: # 封面未加载
# print("start download img:", self.cover_url) # print("start download img:", self.cover_url)
req = QNetworkRequest(QUrl(self.cover_url)) req = QNetworkRequest(QUrl(self.cover_url))
# 设置两个自定义属性方便后期reply中处理 # 设置两个自定义属性方便后期reply中处理
req.setAttribute(QNetworkRequest.User + 1, self) req.setAttribute(QNetworkRequest.User + 1, self)
req.setAttribute(QNetworkRequest.User + 2, self.img_path) req.setAttribute(QNetworkRequest.User + 2, self.img_path)
self._manager.get(req) # 调用父窗口中的下载器下载 self._manager.get(req) # 调用父窗口中的下载器下载
return super(ItemWidget, self).event(event) return super(ItemWidget, self).event(event)
class Window(QListWidget): class Window(QListWidget):
Page = 0 Page = 0
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs) super(Window, self).__init__(*args, **kwargs)
self.resize(800, 600) self.resize(800, 600)
self.setFrameShape(self.NoFrame) # 无边框 self.setFrameShape(self.NoFrame) # 无边框
self.setFlow(self.LeftToRight) # 从左到右 self.setFlow(self.LeftToRight) # 从左到右
self.setWrapping(True) # 这三个组合可以达到和FlowLayout一样的效果 self.setWrapping(True) # 这三个组合可以达到和FlowLayout一样的效果
self.setResizeMode(self.Adjust) self.setResizeMode(self.Adjust)
self._loadStart = False self._loadStart = False
# 连接竖着的滚动条滚动事件 # 连接竖着的滚动条滚动事件
self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered) self.verticalScrollBar().actionTriggered.connect(self.onActionTriggered)
# 进度条 # 进度条
self.loadWidget = QSvgWidget( self.loadWidget = QSvgWidget(
self, minimumHeight=120, minimumWidth=120, visible=False) self, minimumHeight=120, minimumWidth=120, visible=False)
self.loadWidget.load(Svg_icon_loading) self.loadWidget.load(Svg_icon_loading)
# 异步网络下载管理器 # 异步网络下载管理器
self._manager = QNetworkAccessManager(self) self._manager = QNetworkAccessManager(self)
self._manager.finished.connect(self.onFinished) self._manager.finished.connect(self.onFinished)
def load(self): def load(self):
if self.Page == -1: if self.Page == -1:
return return
self._loadStart = True self._loadStart = True
self.loadWidget.setVisible(True) self.loadWidget.setVisible(True)
# 延迟一秒后调用目的在于显示进度条 # 延迟一秒后调用目的在于显示进度条
QTimer.singleShot(1000, self._load) QTimer.singleShot(1000, self._load)
def _load(self): def _load(self):
print("load url:", Url.format(self.Page * 30)) print("load url:", Url.format(self.Page * 30))
url = QUrl(Url.format(self.Page * 30)) url = QUrl(Url.format(self.Page * 30))
self._manager.get(QNetworkRequest(url)) self._manager.get(QNetworkRequest(url))
def onFinished(self, reply): def onFinished(self, reply):
# 请求完成后会调用该函数 # 请求完成后会调用该函数
req = reply.request() # 获取请求 req = reply.request() # 获取请求
iwidget = req.attribute(QNetworkRequest.User + 1, None) iwidget = req.attribute(QNetworkRequest.User + 1, None)
path = req.attribute(QNetworkRequest.User + 2, None) path = req.attribute(QNetworkRequest.User + 2, None)
html = reply.readAll().data() html = reply.readAll().data()
reply.deleteLater() reply.deleteLater()
del reply del reply
if iwidget and path and html: if iwidget and path and html:
# 这里是图片下载完毕 # 这里是图片下载完毕
open(path, "wb").write(html) open(path, "wb").write(html)
iwidget.setCover(path) iwidget.setCover(path)
return return
# 解析网页 # 解析网页
self._parseHtml(html) self._parseHtml(html)
self._loadStart = False self._loadStart = False
self.loadWidget.setVisible(False) self.loadWidget.setVisible(False)
def _parseHtml(self, html): def _parseHtml(self, html):
# encoding = chardet.detect(html) or {} # encoding = chardet.detect(html) or {}
# html = html.decode(encoding.get("encoding","utf-8")) # html = html.decode(encoding.get("encoding","utf-8"))
html = HTML(html) html = HTML(html)
# 查找所有的li list_item # 查找所有的li list_item
lis = html.xpath("//li[@class='list_item']") lis = html.xpath("//li[@class='list_item']")
if not lis: if not lis:
self.Page = -1 # 后面没有页面了 self.Page = -1 # 后面没有页面了
return return
self.Page += 1 self.Page += 1
self._makeItem(lis) self._makeItem(lis)
def _makeItem(self, lis): def _makeItem(self, lis):
for li in lis: for li in lis:
a = li.find("a") a = li.find("a")
video_url = a.get("href") # 视频播放地址 video_url = a.get("href") # 视频播放地址
img = a.find("img") img = a.find("img")
cover_url = "http:" + img.get("r-lazyload") # 封面图片 cover_url = "http:" + img.get("r-lazyload") # 封面图片
figure_title = img.get("alt") # 电影名 figure_title = img.get("alt") # 电影名
figure_info = a.find("div/span") figure_info = a.find("div/span")
figure_info = "" if figure_info is None else figure_info.text # 影片信息 figure_info = "" if figure_info is None else figure_info.text # 影片信息
figure_score = "".join(li.xpath(".//em/text()")) # 评分 figure_score = "".join(li.xpath(".//em/text()")) # 评分
# 主演 # 主演
figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \ figure_desc = "<span style=\"font-size: 12px;\">主演:</span>" + \
"".join([Actor.format(**dict(fd.items())) "".join([Actor.format(**dict(fd.items()))
for fd in li.xpath(".//div[@class='figure_desc']/a")]) for fd in li.xpath(".//div[@class='figure_desc']/a")])
# 播放数 # 播放数
figure_count = ( figure_count = (
li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0] li.xpath(".//div[@class='figure_count']/span/text()") or [""])[0]
path = "cache/{0}.jpg".format( path = "cache/{0}.jpg".format(
os.path.splitext(os.path.basename(video_url))[0]) os.path.splitext(os.path.basename(video_url))[0])
cover_path = "pic_v.png" cover_path = "pic_v.png"
if os.path.isfile(path): if os.path.isfile(path):
cover_path = path cover_path = path
iwidget = ItemWidget(cover_path, figure_info, figure_title, iwidget = ItemWidget(cover_path, figure_info, figure_title,
figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self) figure_score, figure_desc, figure_count, video_url, cover_url, path, self._manager, self)
item = QListWidgetItem(self) item = QListWidgetItem(self)
item.setSizeHint(iwidget.sizeHint()) item.setSizeHint(iwidget.sizeHint())
self.setItemWidget(item, iwidget) self.setItemWidget(item, iwidget)
def onActionTriggered(self, action): def onActionTriggered(self, action):
# 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题 # 这里要判断action=QAbstractSlider.SliderMove可以避免窗口大小改变的问题
# 同时防止多次加载同一个url # 同时防止多次加载同一个url
if action != QAbstractSlider.SliderMove or self._loadStart: if action != QAbstractSlider.SliderMove or self._loadStart:
return return
# 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断 # 使用sliderPosition获取值可以同时满足鼠标滑动和拖动判断
if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum(): if self.verticalScrollBar().sliderPosition() == self.verticalScrollBar().maximum():
# 可以下一页了 # 可以下一页了
self.load() self.load()
def resizeEvent(self, event): def resizeEvent(self, event):
super(Window, self).resizeEvent(event) super(Window, self).resizeEvent(event)
self.loadWidget.setGeometry( self.loadWidget.setGeometry(
int((self.width() - self.loadWidget.minimumWidth()) / 2), int((self.width() - self.loadWidget.minimumWidth()) / 2),
int((self.height() - self.loadWidget.minimumHeight()) / 2), int((self.height() - self.loadWidget.minimumHeight()) / 2),
self.loadWidget.minimumWidth(), self.loadWidget.minimumWidth(),
self.loadWidget.minimumHeight() self.loadWidget.minimumHeight()
) )
if __name__ == "__main__": if __name__ == "__main__":
os.makedirs("cache", exist_ok=True) os.makedirs("cache", exist_ok=True)
app = QApplication(sys.argv) app = QApplication(sys.argv)
w = Window() w = Window()
w.show() w.show()
w.load() w.load()
sys.exit(app.exec_()) sys.exit(app.exec_())

View file

@ -0,0 +1,16 @@
# QListView
## 1、删除自定义Item
[运行](DeleteCustomItem.py)
1. 删除item时先要通过`QListWidget.indexFromItem(item).row()`得到它的行数
2. 通过`takeItem`函数取出该Item并删除掉,`item = self.listWidget.takeItem(row)`
3. 移除item对应的自定义控件`self.listWidget.removeItemWidget(item)`
4. 如果是清空所有Item可以通过循环删除但是删除的时候行号一直是0即可原因和删除list数组一样。
![CustomWidgetItem](ScreenShot/DeleteCustomItem.gif)
## 2、自定义可拖拽Item
[运行](DragDrop.py)
![CustomWidgetSortItem](ScreenShot/DragDrop.gif)

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB