【PyQt6】小说下载DrissionPage及阅读PyQt6

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 res

    return title


def 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.text
        title = 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.text

        with open(f"{dic['title']}.txt", 'w', encoding='utf-8') as f:
            lines = context.splitlines()
            for line in lines:
                line = line.strip()
                if line == '网页版章节内容慢,请下载爱阅小说app阅读最新内容':
                    break
                if 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 = 401
    max_chapters = 600

    if 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 json


class MainWindow(QMainWindow):
    # ================= 构造函数 =============================
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)

        # --------------------------------- 加载界面 -------------------------------------------
        self.ui = load_ui.loadUi("Novel.ui", self)

        # --------------------------------- 设置属性 -------------------------------------------
        self.dir_path = None
        self.filename = None
        self.verBar = 0

        # --------------------------------- 设置 splitter -------------------------------------
        splitter = self.ui.splitter
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 4)
        splitter.setHandleWidth(1)

        # --------------------------------- 设置 菜单项  -------------------------------------
        opd = self.ui.opendir_act
        opd.triggered.connect(self.opendir_act_triggered)

        # -- 设置 QListWidget 文件目录
        listw = self.ui.listWidget
        listw.currentTextChanged.connect(self.currentFileChanged)

        # -----------------设置 QPlainTextEdit 文件阅读区 -------------------------------------
        pte = self.ui.plainTextEdit
        font = 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"):
            return
        with 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 -= 50
        else:
            value = max
        pte.verticalScrollBar().setValue(value)

        # def f():
        #     pte.verticalScrollBar().blockSignals(False)
        # QTimer.singleShot(100, f)

        pass

    def update_listWidget(self):
        listw = self.ui.listWidget
        listw.blockSignals(True)
        # 保存当前目录
        cur_dir = os.getcwd()
        os.chdir(self.dir_path)

        files = os.listdir(self.dir_path)
        if not files:
            return

        listw.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.listWidget
            listw.blockSignals(True)
            # 保存当前目录
            cur_dir = os.getcwd()
            os.chdir(self.dir_path)

            files = os.listdir(self.dir_path)
            if not files:
                return
            files = [
                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 = file
        pte = 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.plainTextEdit
        if self.filename:
            pte = self.ui.plainTextEdit
            font = 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.plainTextEdit
        match a0.key():
            # 上一章
            case Qt.Key.Key_Left:
                nextrow = self.ui.listWidget.currentRow() - 1
                if nextrow > -1:
                    self.ui.listWidget.setCurrentRow(nextrow)
                    # self.verBar = 0
                    pte.verticalScrollBar().setValue(0)
                a0.accept()

            # 下一章
            case Qt.Key.Key_Right:
                nextrow = self.ui.listWidget.currentRow() + 1
                if nextrow < self.ui.listWidget.count():
                    self.ui.listWidget.setCurrentRow(nextrow)
                    self.verBar = 0
                    pte.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 用

代码就不发了, 发链接吧

小说爬虫

2024年3月7日

写了半天 ,这些小说都让我大失所望啊, 大师流失的时代, 这种小说也能看???

  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PyQt6是一个用于Python编程语言的GUI工具包,它允许开发人员在Windows、Mac和Linux等多个平台上创建功能强大的图形用户界面(GUI)应用程序。对于那些希望快速开发和实战的开发人员来说,PyQt6是一个非常有用的工具。 首先,你可以从官方网站上下载PyQt6的源码和文档。官方网站提供了最新的稳定版本和先前版本的下载选项。你可以根据自己的操作系统选择适当的版本进行下载。 在下载完成后,你可以按照文档中的说明进行安装。通常情况下,你只需要运行安装程序并按照步骤完成安装过程。一旦安装完成,你就可以开始在PyQt6中进行快速开发和实战。 PyQt6提供了丰富的GUI组件和工具,使得开发过程更加简单和高效。它包括了各种各样的控件,如按钮、文本框、标签、列表框等等,你可以通过简单的代码来创建和布局这些控件。此外,PyQt6还提供了各种丰富的功能,如绘图、动画、数据存储等,使得你可以轻松地开发出具有复杂功能和交互性的应用程序。 另外,PyQt6还提供了丰富的文档和示例代码。这些文档和示例代码将指导你如何正确地使用PyQt6的各种功能和组件。你可以通过阅读文档和运行示例代码来学习和掌握PyQt6的开发技巧和最佳实践。 总之,PyQt6是一个功能强大的GUI工具包,可以帮助你快速开发和实战。你可以从官方网站下载并安装PyQt6,并通过阅读文档和运行示例代码来学习和掌握它的使用方法。无论是初学者还是有经验的开发人员,PyQt6都是一个优秀的选择。 ### 回答2: 在进行PyQt6快速开发与实战之前,首先需要下载并安装PyQt6库。PyQt6是一个功能强大的Python框架,它提供了丰富的GUI编程工具和功能,可以帮助开发者快速构建界面丰富、交互性强的应用程序。 要下载PyQt6库,可以通过以下步骤进行: 1. 打开Python的官方网站(https://www.python.org/),并下载和安装Python解释器。请确保安装的是Python 3.x版本,因为PyQt6只支持Python 3。 2. 打开命令行终端(Windows可以使用Cmd、PowerShell等;Mac和Linux可以使用终端),输入以下命令安装PyQt6: ``` pip install PyQt6 ``` 这会自动从Python包索引下载并安装PyQt6库。如果你使用的是conda环境,也可以使用类似的命令进行安装。 3. 安装完成后,你就可以在Python脚本中导入并使用PyQt6库了。只需在脚本中加入以下语句即可: ```python from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel # 在这里写下你的代码 ``` 这样,你就可以根据自己的需要使用PyQt6工具和功能进行开发了。 需要注意的是,PyQt6的下载速度可能会受到网络环境的影响。如果下载速度较慢,可以尝试更换源镜像,或者使用代理服务器来加快下载速度。 总之,通过以上步骤,你可以快速下载并安装PyQt6库,为实战开发提供强大的GUI编程工具和功能。希望这个回答对你有所帮助! ### 回答3: PYQT6是一种用于创建图形用户界面(GUI)应用程序的Python库。它基于了Qt框架,并提供了丰富的功能和工具,可以快速开发和部署可视化的应用程序。 要下载PYQT6快速开发与实战的内容,可以采取以下几个步骤: 1. 在互联网浏览器中搜索"PYQT6快速开发与实战",可以找到相关的网站或在线资源。 2. 访问可信赖的网站,如官方文档、编程论坛或云存储平台。 3. 在网站中搜索PYQT6快速开发与实战的下载链接或资源。 4. 点击下载链接,根据提示选择合适的版本和操作系统。 5. 下载完成后,可以解压缩文件并阅读其中的文档。通常会包含示例代码、教程和其他辅助材料。 6. 根据实际需要,可以在自己的开发环境中调用PYQT6库并开始实际的应用程序开发。 请注意,在下载PYQT6快速开发与实战的过程中要保持警惕,避免从不可靠的来源下载,以防止安全风险和恶意软件的感染。 最后,推荐在学习和使用PYQT6过程中,多查阅相关的文档和教程,充分利用PYQT6提供的功能和工具,以便更好地进行快速开发和实战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值