python實現小說分割器(python實現小說閱讀器)
2023-06-03 08:23:13 3
示例簡介本文使用python語言開發了一個小說閱讀器,通過小說書號抓取全部章數的內容,並保存到計算機上,同時也可以通過閱讀器讀取相應章數的內容;
預覽效果:根據填寫的小說書號,分兩種方式顯示抓取的小說內容;
開發環境:Windows7 python3.7 pycharm2018.2.4(開發工具);
目錄結構:
Tips:注意不要一次性抓取太多數據,給伺服器環境造成太大壓力。
實現過程一、閱讀器UI設計
1、安裝所需的第三方模塊PyQt5和pyqt5-tools(文件-設置),直接使用右邊「 」安裝就可以,如無法安裝,可在命令界面使用「pip install XXX」進行安裝(注意使用的是pycharm2018版本);
2、配置工具QtDesigner(設計器)和pyUIC(轉化為py代碼,Arguments設置「$FileName$ -o $FileNameWithoutExtension$.py」);
3、運行工具QtDesigner(圖1)後,利用QtDesigner工具箱設計出圖2的界面效果(所需要的控制項可查看右邊區域),保存效果為文件fiction_reader.ui;
4、對文件fiction_reader.ui執行pyUIC(ui轉化為py代碼),執行完生成文件fiction_reader.py;
二、代碼設計
1、添加內置模塊(下面代碼使用)和主方法(用於運行後彈出閱讀器);
# 添加代碼from PyQt5.QtWidgets import QMessageBox, QFileDialogimport osimport sysimport requestsimport re
方法(添加代碼)if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow # 創建窗體對象 ui = Ui_MainWindow # 創建PyQt設計的窗體對象 ui.setupUi(MainWindow) # 調用PyQt窗體的方法對窗體對象進行初始化設置 MainWindow.show # 顯示窗體 sys.exit(app.exec_) # 程序關閉時退出進程
2、函數setupUi,添加代碼(圖1)來修改第一個table顯示兩列(列表顯示);添加代碼(圖2)來修改第二個table顯示方式(圖表顯示),使用setViewMode設置圖表顯示方式,數字405為table的寬度;
self.tableWidget.setColumnCount(2) # 修改成兩列self.tableWidget.setRowCount(0)# 添加代碼(第一個tab分成兩列)item = QtWidgets.QTableWidgetItemself.tableWidget.setHorizontalHeaderItem(0, item)item = QtWidgets.QTableWidgetItemself.tableWidget.setHorizontalHeaderItem(1, item)self.tableWidget.setColumnWidth(0, 130) # 設置第一列寬度self.tableWidget.horizontalHeader.setStretchLastSection(True) # 設置自動填充容器self.tableWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # 垂直滾動條
# 添加代碼self.listWidget.setViewMode(QtWidgets.QListView.IconMode) # 圖標格式顯示self.listWidget.setIconSize(QtCore.QSize(50, 50)) # 圖標大小self.listWidget.setMaximumWidth(405) # 最大寬度self.listWidget.setSpacing(15) # 間距大小self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # 垂直滾動條
3、修改函數retranslateUi;
注釋:self.lineEdit.setText用來設置小說書號的默認值,self.lineEdit_2.setText設置保存路徑為當前路徑的file下,self.pushButton.clicked.connect為選擇按鈕綁定事件(點擊選擇彈出計算機選擇窗口),self.pushButton_2.clicked.connect點擊確定開始獲取數據;
def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "閱讀器")) self.groupBox.setTitle(_translate("MainWindow", "抓取設置")) self.label.setText(_translate("MainWindow", "請填寫小說書號:")) # 添加代碼(設置默認書號) book_number = '5_5871' self.lineEdit.setText(_translate("MainWindow", book_number)) # 設置默認書號 self.label_2.setText(_translate("MainWindow", "請選擇保存路徑:")) # 添加代碼(設置默認路徑為當前程序路徑下的file文件夾下) self.lineEdit_2.setText(_translate("MainWindow", os.getcwd '\\file')) self.label_3.setText(_translate("MainWindow", "(比如5_5871)")) self.pushButton.setText(_translate("MainWindow", "選擇")) self.pushButton_2.setText(_translate("MainWindow", "確定")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "列表顯示")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "圖表顯示")) # 添加代碼(設置列表標題) item = self.tableWidget.horizontalHeaderItem(0) # 獲取表格的第一列 item.setText(_translate("MainWindow", "書號")) # 設置表格第一列的標題 item = self.tableWidget.horizontalHeaderItem(1) # 獲取表格的第二列 item.setText(_translate("MainWindow", "名稱")) # 設置表格第二列的標題 self.pushButton.clicked.connect(self.msg) # 為選擇按鈕綁定事件 self.pushButton_2.clicked.connect(self.getDatas) # 點擊確定獲取數據
4、實現選擇保存路徑功能,定義函數msg;
注釋:os.getcwd用於彈出選擇窗口默認到該路徑,self.lineEdit_2.setText顯示選擇的路徑;
def msg(self): try: # dir_path即為選擇的文件夾的絕對路徑,第二形參為對話框標題,第三個為對話框打開後默認的路徑 self.dir_path = QFileDialog.getExistingDirectory(None, "選擇路徑", os.getcwd) self.lineEdit_2.setText(self.dir_path) # 顯示選擇的保存路徑 except Exception as e: print(e)
5、分析抓取數據的原理,先獲取小說首頁章數的網址信息,然後循環這些網址獲取相應章數的內容,並保存到本地;
注釋:
封裝函數urlTotext,根據傳入的URL獲取網頁數據,注意response.encoding要設置抓取網站的編碼方式,不然會顯示亂碼;
封裝函數getData,根據獲取到的網址分別獲取對應網址下章數的內容,並保存到本地:
1)查看小說首頁源碼圖,發現章數網址都在.*?', html, re.S)[0]獲取到html信息,然後使用re.findall(r'', dl)過濾出網址信息;
2)查看小說首頁源碼,可以看出前八章是最新部分,為了過濾掉使用for item in links[8:20],從8開始循環;
3)serial_number = item[0:-5]獲取網址的號碼,後面用來排序顯示章數;
4)查看小說章數源碼,發現內容都在(.*?)', articleHtml, re.S)[0]獲取到內容html信息,獲取到的內容包含間隔符和換行符等,需要進行過濾;
函數getDatas用來抓取所有數據,保存到本地後,再顯示到閱讀器上;
# 抓取所有數據def getDatas(self): try: try: while True: # 無限循環(執行這個,才能爬取完顯示) self.book_number = self.lineEdit.text # 記錄用戶設置的書號 self.baseurl = 'https://www.booktxt.net/' self.book_number '/' # 設置書本初始地址 self.getData(self.baseurl, self.lineEdit_2.text) # 執行主方法 except Exception: pass self.getFiles # 獲取所有文件 self.bindList # 對列表進行綁定 self.bindTable # 對表格進行綁定 self.listWidget.itemClicked.connect(self.itemClick) # 綁定列表單擊方法 self.tableWidget.itemClicked.connect(self.tableClick) # 綁定表格單擊方法 except Exception: QMessageBox.warning(None, "警告", "沒有數據,請重新設置書號……", QMessageBox.Ok) return# 抓取數據(根據小說地址返回的內容分析)def getData(self, url, path): html = self.urlTotext(url) dl =.*?', html, re.S)[0] links = re.findall(r'', dl) path = path "\\" self.book_number "\\" # 設置文章存儲路徑 if not os.path.isdir(path): # 判斷路徑是否存在 os.mkdir(path) # 創建路徑 for item in links[8:20]: # 遍歷文章列表 # print(item) serial_number = item[0:-5] print(serial_number) articleUrl = self.baseurl item # 獲取遍歷到的具體文章地址 articleHtml = self.urlTotext(articleUrl) # 提取章節內容 article_content =>(.*?)', articleHtml, re.S)[0] # 過濾掉內容的間隔符、換行符等 article_content = article_content.replace('', '') article_content = article_content.replace('', '') article_content = article_content.replace(' ', '') title = re.findall(r'(.*?)', articleHtml, re.S)[0] # 獲取文章標題 fileName = path serial_number title '.txt' # 設置文章保存路徑(包括文章名) newFile = open(fileName, "w") # 打開或者創建文件 newFile.write("<>\n\n") # 向文件中寫入標題並換行 newFile.write(article_content) # 向文件中寫入內容 newFile.close # 關閉文件 QMessageBox.Information(None, "提示", self.book_number "的小說保存完成", QMessageBox.Ok)# 從網頁提取數據def urlTotext(self, url): response = requests.get(url) # 編碼方式 response.encoding = 'gbk' html = response.text return html
6、實現獲取本地所有文件的功能,定義函數getFiles;
注釋:使用sorted進行排序,有利於閱讀器可以根據章數順序閱讀;
def getFiles(self): self.list = os.listdir(self.lineEdit_2.text '\\' self.lineEdit.text) # 列出文件夾下所有的目錄與文件 self.list = sorted(self.list) # 排序
7、實現把文件顯示到第一個table,並能點擊彈出對應章數的txt進行閱讀;
注釋:第一列顯示書號內容self.lineEdit.text,第二列顯示章數標題self.list[i];if 'txt' in item.text解決點擊書號退出閱讀器問題;
# 將文件顯示在Table中(列表顯示)def bindTable(self): for i in range(0, len(self.list)): # 遍歷文件列表 self.tableWidget.insertRow(i) # 添加新行 # 設置第一列的值為書號 self.tableWidget.setItem(i, 0, QtWidgets.QTableWidgetItem(self.lineEdit.text)) # 設置第二列的值為文件名 self.tableWidget.setItem(i, 1, QtWidgets.QTableWidgetItem(self.list[i]))# 表格單擊方法,用來打開選中的項def tableClick(self, item): if 'txt' in item.text: # 點擊文件名才彈出 os.startfile(self.lineEdit_2.text '\\' self.lineEdit.text '\\' item.text)
8、實現把文件顯示到第二個table,並能點擊彈出對應章數的txt進行閱讀;
注釋:self.list[i])[7:13]為了不顯示章數名前面的序號;
# 將文件顯示在List列表中(圖表顯示)def bindList(self): for i in range(0, len(self.list)): # 遍歷文件列表 self.item = QtWidgets.QListWidgetItem(self.listWidget) # 創建列表項 self.item.setIcon(QtGui.QIcon('images/fiction.png')) # 設置列表項圖標 self.item.setText(str(self.list[i])[7:13] '...') # 截取字符串(不顯示序號) self.item.setToolTip(self.list[i]) # 設置提示文字 self.item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) # 設置選中與否# 列表單擊方法,用來打開選中的項def itemClick(self, item): os.startfile(self.lineEdit_2.text '\\' self.lineEdit.text '\\' item.toolTip)
9、最終代碼如下圖:
# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'fiction_reader.ui'## Created by: PyQt5 UI code generator 5.13.0## WARNING! All changes made in this file will be lost!from PyQt5 import QtCore, QtGui, QtWidgets# 添加代碼from PyQt5.QtWidgets import QMessageBox, QFileDialogimport osimport sysimport requestsimport reclass Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(500, 480) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.groupBox = QtWidgets.QGroupBox(self.centralwidget) self.groupBox.setGeometry(QtCore.QRect(39, 20, 421, 131)) self.groupBox.setObjectName("groupBox") self.label = QtWidgets.QLabel(self.groupBox) self.label.setGeometry(QtCore.QRect(20, 36, 101, 16)) self.label.setObjectName("label") self.label_2 = QtWidgets.QLabel(self.groupBox) self.label_2.setGeometry(QtCore.QRect(20, 86, 101, 16)) self.label_2.setObjectName("label_2") self.label_3 = QtWidgets.QLabel(self.groupBox) self.label_3.setGeometry(QtCore.QRect(282, 36, 101, 20)) self.label_3.setObjectName("label_3") self.lineEdit = QtWidgets.QLineEdit(self.groupBox) self.lineEdit.setGeometry(QtCore.QRect(120, 31, 161, 28)) self.lineEdit.setObjectName("lineEdit") self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox) self.lineEdit_2.setGeometry(QtCore.QRect(120, 81, 161, 28)) self.lineEdit_2.setObjectName("lineEdit_2") self.pushButton = QtWidgets.QPushButton(self.groupBox) self.pushButton.setGeometry(QtCore.QRect(288, 83, 51, 23)) self.pushButton.setObjectName("pushButton") self.pushButton_2 = QtWidgets.QPushButton(self.groupBox) self.pushButton_2.setGeometry(QtCore.QRect(350, 83, 51, 23)) self.pushButton_2.setObjectName("pushButton_2") self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setGeometry(QtCore.QRect(39, 175, 421, 231)) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget self.tab.setObjectName("tab") self.tableWidget = QtWidgets.QTableWidget(self.tab) self.tableWidget.setGeometry(QtCore.QRect(5, 5, 405, 197)) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(2) # 修改成兩列 self.tableWidget.setRowCount(0) # 添加代碼(第一個tab分成兩列) item = QtWidgets.QTableWidgetItem self.tableWidget.setHorizontalHeaderItem(0, item) item = QtWidgets.QTableWidgetItem self.tableWidget.setHorizontalHeaderItem(1, item) self.tableWidget.setColumnWidth(0, 130) # 設置第一列寬度 self.tableWidget.horizontalHeader.setStretchLastSection(True) # 設置自動填充容器 self.tableWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # 垂直滾動條 self.tabWidget.addTab(self.tab, "") self.tab_2 = QtWidgets.QWidget self.tab_2.setObjectName("tab_2") self.listWidget = QtWidgets.QListWidget(self.tab_2) self.listWidget.setGeometry(QtCore.QRect(5, 5, 405, 197)) self.listWidget.setObjectName("listWidget") # 添加代碼 self.listWidget.setViewMode(QtWidgets.QListView.IconMode) # 圖標格式顯示 self.listWidget.setIconSize(QtCore.QSize(50, 50)) # 圖標大小 self.listWidget.setMaximumWidth(405) # 最大寬度 self.listWidget.setSpacing(15) # 間距大小 self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # 垂直滾動條 self.tabWidget.addTab(self.tab_2, "") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 23)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "閱讀器")) self.groupBox.setTitle(_translate("MainWindow", "抓取設置")) self.label.setText(_translate("MainWindow", "請填寫小說書號:")) # 添加代碼(設置默認書號) book_number = '5_5871' self.lineEdit.setText(_translate("MainWindow", book_number)) # 設置默認書號 self.label_2.setText(_translate("MainWindow", "請選擇保存路徑:")) # 添加代碼(設置默認路徑為當前程序路徑下的file文件夾下) self.lineEdit_2.setText(_translate("MainWindow", os.getcwd '\\file')) self.label_3.setText(_translate("MainWindow", "(比如5_5871)")) self.pushButton.setText(_translate("MainWindow", "選擇")) self.pushButton_2.setText(_translate("MainWindow", "確定")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "列表顯示")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "圖表顯示")) # 添加代碼(設置列表標題) item = self.tableWidget.horizontalHeaderItem(0) # 獲取表格的第一列 item.setText(_translate("MainWindow", "書號")) # 設置表格第一列的標題 item = self.tableWidget.horizontalHeaderItem(1) # 獲取表格的第二列 item.setText(_translate("MainWindow", "名稱")) # 設置表格第二列的標題 self.pushButton.clicked.connect(self.msg) # 為選擇按鈕綁定事件 self.pushButton_2.clicked.connect(self.getDatas) # 點擊確定獲取數據 # 添加代碼(選擇保存路徑) def msg(self): try: # dir_path即為選擇的文件夾的絕對路徑,第二形參為對話框標題,第三個為對話框打開後默認的路徑 self.dir_path = QFileDialog.getExistingDirectory(None, "選擇路徑", os.getcwd) self.lineEdit_2.setText(self.dir_path) # 顯示選擇的保存路徑 except Exception as e: print(e) # 抓取所有數據 def getDatas(self): try: try: while True: # 無限循環(執行這個,才能爬取完顯示) self.book_number = self.lineEdit.text # 記錄用戶設置的書號 self.baseurl = 'https://www.booktxt.net/' self.book_number '/' # 設置書本初始地址 self.getData(self.baseurl, self.lineEdit_2.text) # 執行主方法 except Exception: pass self.getFiles # 獲取所有文件 self.bindList # 對列表進行綁定 self.bindTable # 對表格進行綁定 self.listWidget.itemClicked.connect(self.itemClick) # 綁定列表單擊方法 self.tableWidget.itemClicked.connect(self.tableClick) # 綁定表格單擊方法 except Exception: QMessageBox.warning(None, "警告", "沒有數據,請重新設置書號……", QMessageBox.Ok) return # 抓取數據 def getData(self, url, path): html = self.urlTotext(url) dl =.*?', html, re.S)[0] links = re.findall(r'', dl) path = path "\\" self.book_number "\\" # 設置文章存儲路徑 if not os.path.isdir(path): # 判斷路徑是否存在 os.mkdir(path) # 創建路徑 for item in links[8:20]: # 遍歷文章列表 # print(item) serial_number = item[0:-5] print(serial_number) articleUrl = self.baseurl item # 獲取遍歷到的具體文章地址 articleHtml = self.urlTotext(articleUrl) # 提取章節內容 article_content =>(.*?)', articleHtml, re.S)[0] # 過濾掉內容的間隔符、換行符等 article_content = article_content.replace('', '') article_content = article_content.replace('', '') article_content = article_content.replace(' ', '') title = re.findall(r'(.*?)', articleHtml, re.S)[0] # 獲取文章標題 fileName = path serial_number title '.txt' # 設置文章保存路徑(包括文章名) newFile = open(fileName, "w") # 打開或者創建文件 newFile.write("<>\n\n") # 向文件中寫入標題並換行 newFile.write(article_content) # 向文件中寫入內容 newFile.close # 關閉文件 QMessageBox.Information(None, "提示", self.book_number "的小說保存完成", QMessageBox.Ok) # 從網頁提取數據 def urlTotext(self, url): response = requests.get(url) # 編碼方式 response.encoding = 'gbk' html = response.text return html # 獲取所有文件 def getFiles(self): self.list = os.listdir(self.lineEdit_2.text '\\' self.lineEdit.text) # 列出文件夾下所有的目錄與文件 self.list = sorted(self.list) # 排序 print(self.list) # 將文件顯示在Table中(列表顯示) def bindTable(self): for i in range(0, len(self.list)): # 遍歷文件列表 self.tableWidget.insertRow(i) # 添加新行 # 設置第一列的值為書號 self.tableWidget.setItem(i, 0, QtWidgets.QTableWidgetItem(self.lineEdit.text)) # 設置第二列的值為文件名 self.tableWidget.setItem(i, 1, QtWidgets.QTableWidgetItem(self.list[i])) # 表格單擊方法,用來打開選中的項 def tableClick(self, item): if 'txt' in item.text: # 點擊文件名才彈出 os.startfile(self.lineEdit_2.text '\\' self.lineEdit.text '\\' item.text) # 將文件顯示在List列表中(圖表顯示) def bindList(self): for i in range(0, len(self.list)): # 遍歷文件列表 self.item = QtWidgets.QListWidgetItem(self.listWidget) # 創建列表項 self.item.setIcon(QtGui.QIcon('images/fiction.png')) # 設置列表項圖標 self.item.setText(str(self.list[i])[7:13] '...') # 截取字符串(不顯示序號) self.item.setToolTip(self.list[i]) # 設置提示文字 self.item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) # 設置選中與否 # 列表單擊方法,用來打開選中的項 def itemClick(self, item): os.startfile(self.lineEdit_2.text '\\' self.lineEdit.text '\\' item.toolTip)# 主方法(添加代碼)if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow # 創建窗體對象 ui = Ui_MainWindow # 創建PyQt設計的窗體對象 ui.setupUi(MainWindow) # 調用PyQt窗體的方法對窗體對象進行初始化設置 MainWindow.show # 顯示窗體 sys.exit(app.exec_) # 程序關閉時退出進程
,