文章目录
- [1 简介](#1 简介)
- [2 DrissionPage](#2 DrissionPage)
- [3 阅读界面](#3 阅读界面)
-
- [3.1 Qt Designer](#3.1 Qt Designer)
- [3.2 界面类](#3.2 界面类)
- 总结
1 简介
看到一本小说 《无敌六皇子》 【https://www.xsobiquge.org/book/178299/】看小说简介觉得挺有意思的,想读一读。浏览器阅读 不能保存进度,就好烦。想着就爬下来,本地看。
看着挺简单的网站,requests 就只能爬个目录页,分章内容总是被拒,UA Refer Cookie 以及所有的请求头都加了,也没有效果,最终还是回到浏览器去,此时 DrissionPage 就用上了。
2 DrissionPage
DrissionPage 号称同时实现"写得快"和"跑得快",试过以后还真是巨方便。
DrissionPage 的教程我就略过不谈了,网上有总结的,感谢哪些前辈们的分享。
我就直接分享一下 具体的爬虫代码吧, 代码量真的很少
demo
python
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 界面类
界面类,这一部分也没啥东西,直接上代码看吧
python
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 真的很方便啊,能随手写写小工具