本文主要是介绍【PyQt6】小说下载DrissionPage及阅读PyQt6,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 1 简介
- 2 DrissionPage
- demo
- 3 阅读界面
- 3.1 Qt Designer
- 3.2 界面类
- 总结
- 2024年2月25日 更新
1 简介
看到一本小说 《无敌六皇子》 【https://www.xsobiquge.org/book/178299/】看小说简介觉得挺有意思的,想读一读。浏览器阅读 不能保存进度,就好烦。想着就爬下来,本地看。
看着挺简单的网站,requests 就只能爬个目录页,分章内容总是被拒,UA Refer Cookie 以及所有的请求头都加了,也没有效果,最终还是回到浏览器去,此时 DrissionPage 就用上了。
2 DrissionPage
DrissionPage 号称同时实现“写得快”和“跑得快”,试过以后还真是巨方便。
DrissionPage 的教程我就略过不谈了,网上有总结的,感谢哪些前辈们的分享。
我就直接分享一下 具体的爬虫代码吧, 代码量真的很少
demo
from DrissionPage import SessionPage
import os
import time
import random
import re# 创建页面对象
page = SessionPage()subpages = []reg = r"第(\d+)章.*"
pat = re.compile(reg)def formatTitle(title):match = pat.search(title)if match:idx = match.group(1)res = title.replace(idx, f'{idx:0>4}')return resreturn titledef download_catalog(url):page.get(start_url)# 根据 xpath 或 css selector 查找xpath = '//*[@id="list"]/dl/dd'lists = page.eles(f'xpath:{xpath}')for li in lists:a = li.ele('tag:a')href = a.attr('href')title = a.texttitle = formatTitle(title)# print(a.attr('href'), a.text)subpages.append({'href': href, 'title': title})print(f'{url} 下载完成')def download_chapter():for i in range(min_chapters-1, max_chapters):dic = subpages[i]time.sleep(random.random() * 2)ret = page.get(dic['href'])if not ret:print(ret)return# //*[@id="content"]xpath = '//*[@id="content"]'elem = page.ele(f'xpath:{xpath}')context = elem.textwith open(f"{dic['title']}.txt", 'w', encoding='utf-8') as f:lines = context.splitlines()for line in lines:line = line.strip()if line == '网页版章节内容慢,请下载爱阅小说app阅读最新内容':breakif line:f.write('\t'+line+'\n')print(f'{dic['title']} 下载完成')if __name__ == '__main__':book_name = "无敌六皇子"start_url = 'https://www.xsobiquge.org/book/178299/'min_chapters = 401max_chapters = 600if not os.path.exists(book_name):os.mkdir(book_name)download_catalog(start_url)os.chdir(book_name)download_chapter()
3 阅读界面
小说的分章终于保存到了本地,txt 格式,直接使用 VSC 看,那个阅读体验真的不好。于是就顺手写了一个阅读器,大体上就是用 QTextEdit 来阅读,调整一下字体大小,行高,宽度,上一章 下一章的功能,基本就能满足要求,阅读进度使用 json 保存
3.1 Qt Designer
用 Designer 简单写个界面,东西很少,看图就明白
加载页面使用 self.ui = load_ui.loadUi(“Novel.ui”, self)
这种方式有利于看界面效果,但没代码提示,写代码不友好
建议界面定型后,使用 pyuic6 把ui文件转成 py文件,再导入
3.2 界面类
界面类,这一部分也没啥东西,直接上代码看吧
from PyQt6.QtCore import QTimer, QSettings, Qt
from PyQt6.QtGui import QCloseEvent, QKeyEvent, QPalette, QColor
from PyQt6.QtWidgets import (QMainWindow,QWidget,QApplication,QFileDialog,QSplitter,QStyleFactory,
)
from PyQt6.uic import load_ui
import os
import jsonclass MainWindow(QMainWindow):# ================= 构造函数 =============================def __init__(self, parent: QWidget = None):super().__init__(parent)# --------------------------------- 加载界面 -------------------------------------------self.ui = load_ui.loadUi("Novel.ui", self)# --------------------------------- 设置属性 -------------------------------------------self.dir_path = Noneself.filename = Noneself.verBar = 0# --------------------------------- 设置 splitter -------------------------------------splitter = self.ui.splittersplitter.setStretchFactor(0, 1)splitter.setStretchFactor(1, 4)splitter.setHandleWidth(1)# --------------------------------- 设置 菜单项 -------------------------------------opd = self.ui.opendir_actopd.triggered.connect(self.opendir_act_triggered)# -- 设置 QListWidget 文件目录listw = self.ui.listWidgetlistw.currentTextChanged.connect(self.currentFileChanged)# -----------------设置 QPlainTextEdit 文件阅读区 -------------------------------------pte = self.ui.plainTextEditfont = pte.font()font.setFamily("楷体")font.setPointSize(30)pte.setFont(font)pte.setReadOnly(True)# pte.setViewportMargins(0, 10, 0, 10)# -- 导入设置数据 -------------------------------------------------------if not os.path.exists("data.txt"):returnwith open("data.txt", "r", encoding="utf-8") as f:dic = json.loads(f.read())self.dir_path = dic["path"]self.file = dic["file"]font.setPointSize(dic["pointSize"])pte.setFont(font)self.verBar = dic["verBar"]self.update_listWidget()# -- 链接信号# pte.cursorPositionChanged.connect(self.cursorPositionChanged)# pte.verticalScrollBar().valueChanged.connect(self.cursorPositionChanged)def cursorPositionChanged(self, value):pte = self.ui.plainTextEdit# pte.verticalScrollBar().blockSignals(True)max = pte.verticalScrollBar().maximum()if max - value > 50:value -= 50else:value = maxpte.verticalScrollBar().setValue(value)# def f():# pte.verticalScrollBar().blockSignals(False)# QTimer.singleShot(100, f)passdef update_listWidget(self):listw = self.ui.listWidgetlistw.blockSignals(True)# 保存当前目录cur_dir = os.getcwd()os.chdir(self.dir_path)files = os.listdir(self.dir_path)if not files:returnlistw.clear()listw.addItems(files)# 切换回当前目录os.chdir(cur_dir)def f():listw.setCurrentItem(None)listw.blockSignals(False)idx = files.index(self.file)listw.setCurrentRow(idx)QTimer.singleShot(100, f)def opendir_act_triggered(self):self.dir_path = QFileDialog.getExistingDirectory()if self.dir_path:listw = self.ui.listWidgetlistw.blockSignals(True)# 保存当前目录cur_dir = os.getcwd()os.chdir(self.dir_path)files = os.listdir(self.dir_path)if not files:returnfiles = [file for file in files if os.path.isfile(file) and file != "data.txt"]listw.clear()listw.addItems(files)# 切换回当前目录os.chdir(cur_dir)def f():listw.setCurrentItem(None)listw.blockSignals(False)QTimer.singleShot(100, f)def currentFileChanged(self, file):self.setWindowTitle(file)self.filename = filepte = self.ui.plainTextEdit# 保存当前目录cur_dir = os.getcwd()os.chdir(self.dir_path)with open(file, "r", encoding="utf-8") as f:lines = f.readlines()os.chdir(cur_dir)# text_cursor = QTextCursor()pte.clear()text_cursor = pte.textCursor()width = pte.width()TextBlockFormat = text_cursor.blockFormat()TextBlockFormat.setLeftMargin(width * 0.1)TextBlockFormat.setRightMargin(width * 0.1)TextBlockFormat.setBottomMargin(50)TextBlockFormat.setLineHeight(150, 1)TextBlockFormat.setTextIndent(20 * 4)text_cursor.setBlockFormat(TextBlockFormat)for line in lines:text_cursor.insertText(line.strip() + "\n")pte.verticalScrollBar().setValue(self.verBar)def closeEvent(self, a0: QCloseEvent) -> None:pte = self.ui.plainTextEditif self.filename:pte = self.ui.plainTextEditfont = pte.font()dict = {"path": self.dir_path,"file": self.filename,"pointSize": font.pointSize(),"verBar": pte.verticalScrollBar().value(),}# os.chdir(self.dir_path)with open("data.txt", "w", encoding="utf-8") as f:f.write(json.dumps(dict, ensure_ascii=False))# 保存 self.ui.splitter 的状态splitter_saveState(self.ui.splitter)return super().closeEvent(a0)def keyReleaseEvent(self, a0: QKeyEvent) -> None:# print('*'*20)pte = self.ui.plainTextEditmatch a0.key():# 上一章case Qt.Key.Key_Left:nextrow = self.ui.listWidget.currentRow() - 1if nextrow > -1:self.ui.listWidget.setCurrentRow(nextrow)# self.verBar = 0pte.verticalScrollBar().setValue(0)a0.accept()# 下一章case Qt.Key.Key_Right:nextrow = self.ui.listWidget.currentRow() + 1if nextrow < self.ui.listWidget.count():self.ui.listWidget.setCurrentRow(nextrow)self.verBar = 0pte.verticalScrollBar().setValue(0)a0.accept()return super().keyReleaseEvent(a0)def splitter_restoreState(splitter: QSplitter):set = QSettings("splitterSizes", QSettings.Format.IniFormat)splitter.restoreState(set.value("splitterSizes"))def splitter_saveState(splitter: QSplitter):set = QSettings("splitterSizes", QSettings.Format.IniFormat)set.setValue("splitterSizes", splitter.saveState())if __name__ == "__main__":qApp = QApplication([])qApp.setStyle(QStyleFactory.create("fusion"))palette = qApp.palette()palette.setColor(QPalette.ColorRole.Base, QColor(31, 31, 31))palette.setColor(QPalette.ColorRole.Text, QColor(78, 201, 176))palette.setColor(QPalette.ColorRole.Window, QColor(43, 43, 43))palette.setColor(QPalette.ColorRole.Highlight, QColor(38, 79, 120))palette.setColor(QPalette.ColorRole.HighlightedText, QColor(255, 255, 255))palette.setColor(QPalette.ColorRole.ButtonText, QColor(204, 204, 204))qApp.setPalette(palette)mw = MainWindow()mw.showMaximized()splitter_restoreState(mw.ui.splitter)qApp.exec()
总结
到此这个阅读器也就能马马虎虎的使用了,翻页的时候不太友好,因为 QTextEdit 翻页的时候,默认翻的是viewport 的大小,对于文字来说,有时会卡半行,真想重写 QTextEdit 的空格键啊,有机会再说。
最后再说一句 Python 真的很方便啊,能随手写写小工具
2024年2月25日 更新
阅读器代码有了更新, QTextEdit 加了 事件过滤器, 重写了 空格键, 翻页的时候少移动 30像素, 另外如果页面到底自动下一章功能
我一直还在想 怎么把 QEvent类 转成 QKeyEvent , 结果完全没有必要, 只要事件类型是 keypress ,就直接当 QKeyEvent 用
代码就不发了, 发链接吧
小说爬虫
这篇关于【PyQt6】小说下载DrissionPage及阅读PyQt6的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!