好友
阅读权限 10
听众
最后登录 1970-1-1
we37
发表于 2021-2-8 19:45
本帖最后由 we37 于 2021-2-9 20:15 编辑
前言 所需要的库 准备工作 面向对象-书源 类编写 面向对象,Ui 类书写 整合--main 完整源码
前言
我又双叒叕来了,这段时间一直在看深度学习,今天才想起我还有个这个,没错这次是自动化阅读小说3.0,使用了面向对象,正则,pyside2,来优化整个代码,使得添加书源,阅读更加方便。3.0版本基本上算是对之前的全部重做了,这次可能是小说阅读的最后一个版本/(ㄒoㄒ)/~~(太多了太多了)。话不多说,开始整理思路~ps:如果没有看过我之前的帖子一键下载小说(一)(二),建议先看这两个,方便理解思路,练手等。刚入门建议自己手打一遍。
所需要用到的库
[Python] 纯文本查看 复制代码
import re #正则
import requests as rq #爬虫
import json #用来load本地文档
from random import choice #随即处理
from time import sleep #休眠处理
from PySide2.QtWidgets import QApplication ##gui所用库
from PySide2.QtUiTools import QUiLoader
from threading import Thread, Lock
准备工作
useragent
首先准备fake-User Agent,之前有吾友发过的fake-ua本地版(把fake_useragent的所有useragent存在本地使用https://www.52pojie.cn/thread-1245683-1-1.html (出处: 吾爱破解 论坛)),
我收集起来存在本地供平时调用,这个等会儿会在文章底部发出来。先看代码。
[Python] 纯文本查看 复制代码
with open('fake_ua.json', 'r') as fa:
UA = json.load(fa)
Ua = choice(UA)
header = {
'Referer': '',
'User-Agent': Ua
}
正则
这个在这里就不罗嗦了,大家自己在网上搜一搜就有很多教程。
熟悉QTSide2
掌握面向对象编程,了解魔法方法
了解一点线程知识
准备工作差不多就这些,下面我们正式进入正题。
面向对象-书源 类编写
在一键下载小说(一)和(二)中,我们都是直接将书源编写为一个个字典和函数,而这次我们将书源的属性和方法封装成类,来帮助我们减少代码量和方便后期增删改查书源。
我们以字典的形式创建实例对象,因为不怎么会讲,所以类的创建我们以代码加注释的方式过一遍。有什么问题的直接看代码。
[Python] 纯文本查看 复制代码
class xiaoshuo_url(object):
def __init__(self, dict):
self.dict = dict # 这里将这个作为一个属性是为了展示书源的直接str(self.dict)
self.name = dict['search_name'] #书源名
self.booksourceUrl = dict['booksourceUrl'] #书源官网url
self.search_url = dict['search_url'] #书源搜索接口
self.send_way = dict['send_way'] #发送方式 get/post
self.encode = dict['encode'] #页面编码 gbk/utf8
self.book_compile = re.compile(dict['book_compile'], re.S) #搜索页面正则
self.chapter_compile = re.compile(dict['chapter_compile'], re.S) #章节页面正则
self.text_compile = re.compile(dict['text_compile'], re.S) # text页面正则
#下面四个都是bool类型
self.urladd1 = dict['urladd1'] #单个章节url前是否需要添加booksourceUrl
self.urladd2 = dict['urladd2'] #单个章节url前是否需要添加搜索界面获得的chapter_Url
self.urladd3 = dict['urladd3'] #在搜索界面获得的书籍url是否需要在前添加booksourceUrl
self.redirect = dict['redirect'] #是否重定向
def get_search(self, bookname):
def search(bookname):
# 获取搜索结果页面
global html
if self.send_way == 'get':
html = rq.get(self.search_url + bookname, headers=header)
elif self.send_way == 'post':
data = {'searchkey': bookname,
'name': bookname}
html = rq.post(self.search_url, data=data, headers=header)
html.encoding = None
found_result = re.findall(self.book_compile, html.text)
# 判断是否有这个小说
for i in range(len(found_result)):
if found_result[i][1] == bookname:
chapter_url = found_result[i][0]
if self.urladd3 == True:
chapter_url = self.booksourceUrl + chapter_url
return chapter_url
print('查无此book')
return '查无此book'
def main(bookname):
chapter_url = search(bookname)
if chapter_url == '查无此book':
return None
return chapter_url
return main(bookname)
def get_chapter(self, chapter_url):
def real_url(fake_url):
try:
url_redirects = rq.get(fake_url, headers=header, allow_redirects=False)
#这里换了一种获取重定向后url的方法,因为有个书源反爬比较严重,加载了两次重定向
#所以会有一次301,在这里检测了是否301,如果response 301,则再进行一次重定向 if url_redirects.status_code == 301:
url_redirects.headers['Location'] = real_url(url_redirects.headers['Location'])
url_redirects.close()
return str(url_redirects.headers['Location'])
except:
print('重定向这出错了')
# 获取章节目录和对应的url写成字典
# chapter_html指获取到的页面
# tuple_chapter指正则查找到的url、章节名组成的元组
# chapter_dict指要装入章节名:url的字典
# singlebook_url指重新填装的url
def get_chapter(chapter_url):
try:
chapter_html = rq.get(chapter_url, headers=header)
chapter_html.encoding = self.encode
tuple_chapter = re.findall(self.chapter_compile, chapter_html.text)
chapter_dict = {}
if self.urladd2 == True:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = chapter_url + tuple_chapter[i][0]
elif self.urladd2 == False and self.urladd1 == True:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = self.booksourceUrl + tuple_chapter[i][0]
else:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = tuple_chapter[i][0]
return chapter_dict
except:
print('获取章节这出错了')
return None
if self.redirect == True: # 判断是否需要重定向
chapter_url = real_url(chapter_url)
return get_chapter(chapter_url)
#获取单个章节内容函数
def get_book(self, chapter_url):
html = rq.get(chapter_url, headers=header)
booktext = re.findall(self.text_compile, html.text)
print(booktext)
return booktext[0]
def __str__(self):
return str(self.dict)
书源的类写完了,剩下的就是加载书源,大家可以自己去找一些优质的书源,按照格式填写加载,或者使用我找的四个书源。 自己写完后甚至可以用shelve持久化到本地存储起来。这里给大家一个模板。我写的书源会写成txt放在文章末尾。
ui类书写:
这里我写了四个ui
分别是
然后。。。接着看代码!
[Python] 纯文本查看 复制代码
class Ui(object):
def __init__(self):
# 搜索界面的加载和各个按钮的定义链接
self.search_ui = QUiLoader().load('search.ui')
self.search_ui.button.clicked.connect(self.__search) # 这里的按钮被命名为button
self.search_ui.chapterbutton.clicked.connect(self.enter_chapter)
# 目录界面的加载和各个按钮的定义链接
self.chapter_ui = QUiLoader().load('chapter.ui')
self.chapter_ui.button.clicked.connect(self.__chapter_button) # 这里的按钮被命名为button
# 警告界面的加载和各个按钮的定义链接
self.warnner_ui = QUiLoader().load('warnner.ui')
self.warnner_ui.backbutton.clicked.connect(self.search_show) # 这里的按钮被命名为backbutton返回按钮
# text界面的加载和各个按钮的定义链接
self.singlebook_ui = QUiLoader().load('singlebook.ui')
self.singlebook_ui.next_button.clicked.connect(self.__next_chapter) # 这里的按钮被命名为next_button下一章按钮
self.singlebook_ui.last_button.clicked.connect(self.__last_chapter) # 这里的按钮被命名为last_button上一章按钮
# 中间变量的定义
self.chapter = [] # 使用字典的方式,用于存放获取到的所有章节及url
self.booklist = [] # 用于存放多线程搜索到的各个站点章节url
self.lock = Lock()
self.__i = []
self.i = 0
# 主方法入口,其他方法均为私有方法,只有这个能被外部调用
def search_show(self):
self.search_ui.show()
# 搜索接口
def __search(self):
def thread_job(self, i, search_name):
chapter_url = s[i].get_search(search_name)
if chapter_url != None:
self.lock.acquire()
self.search_ui.listWidget.addItem(chapter_url)
# self.booklist.append(chapter)
chapter = s[i].get_chapter(chapter_url)
self.chapter.append(chapter)
self.__i.append(i)
self.lock.release()
search_name = self.search_ui.lineEdit.text()
thread_list = []
for i in range(len(s)):
thread = Thread(target=thread_job, args=(self, i, search_name))
thread.start()
thread_list.append(thread)
for single_thread in thread_list:
single_thread.join()
# if self.booklist == []:
# self.warnner_ui.show()
def enter_chapter(self):
j = self.search_ui.listWidget.currentRow()
self.current_chapter = self.chapter[j]
self.i = self.__i[j]
self.chapter_ui.listWidget.clear()
self.chapter_ui.listWidget.addItems(self.current_chapter.keys())
self.chapter_ui.show()
# self.chapter = s[i].xiaoshuo_look(search_name)
# print(i)
# # 这里的None比较重要,有着承上启下的作用,因为在书源的类里面如果没有找到或者出错,返回值均是None
# if self.chapter != None:
# # addItems() 将列表中所有的值 append 到 listwidget 中
# self.chapter_ui.listWidget.addItems(self.chapter.keys())
# self.chapter_ui.show()
# break
# else:
# self.warnner_ui.show()
# 将获取到的文本添加到text1文本框内,并展示出来
def __show_text(self, key):
# 先清楚文本框内的text
self.singlebook_ui.text1.clear()
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
text = s[self.i].get_book(key)
# 文本框使用append方法,括号内必须是str
self.singlebook_ui.text1.append(text)
self.singlebook_ui.show()
# chapter_ui 的按钮的入口
def __chapter_button(self):
key = self.chapter_ui.listWidget.currentItem().text()
singlechapter_url = self.current_chapter[key]
self.__show_text(singlechapter_url)
self.g = self.__key_next()
self.h = self.__key_last()
# 增加章节生成器 这里的方法比较取巧和勉强,是我自己琢磨出来的,应该还有其他更好的方法
def __key_next(self):
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
for value in list(self.current_chapter.values())[self.chapter_ui.listWidget.currentRow() + 1:]:
# for i in range(len(list(self.current_chapter.keys())[self.key:])):
yield value
# 减去章节生成器
def __key_last(self):
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
for value in list(self.current_chapter.values())[:self.chapter_ui.listWidget.currentRow() - 1]:
yield value
# 下一章
def __next_chapter(self):
# currentRow()方法指的是获取当前行的行数,从1开始
# self.key = self.chapter_ui.listWidget.currentRow()
# next()是含有yield的专有方法
jia = next(self.g)
self.__show_text(jia)
# 上一章
def __last_chapter(self):
jian = next(self.h)
self.__show_text(jian)
这里不仅要load各个ui,还要将各个ui中的按钮和功能通过connect()连接起来,注意,这里的button.connect() 返回的是一个bool值,他不能返回函数的返回值,
且 ※
connect()的()中只能填函数的名字,不能带括号,不然会直接调用。
整合
最后我们通过main()直接生成 if __name__ == '__main__':
(小技巧:直接在pycharm中打main会有提示框,回车就会直接帮我们打出 if __name__ == '__main__':)
[Python] 纯文本查看 复制代码
if __name__ == '__main__':
app = QApplication([])
ui = Ui()
ui.search_show()
app.exec_()
完整源码:
[Python] 纯文本查看 复制代码
import re
import requests as rq
import json
from random import choice
from time import sleep
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
from threading import Thread, Lock
with open('../fake_ua.json', 'r') as fa:
UA = json.load(fa)
Ua = choice(UA)
header = {
'Referer': '',
'User-Agent': Ua
}
class xiaoshuo_url(object):
def __init__(self, dict):
self.dict = dict
self.name = dict['search_name']
self.booksourceUrl = dict['booksourceUrl']
self.search_url = dict['search_url']
self.send_way = dict['send_way']
self.encode = dict['encode']
self.book_compile = re.compile(dict['book_compile'], re.S)
self.chapter_compile = re.compile(dict['chapter_compile'], re.S)
self.text_compile = re.compile(dict['text_compile'], re.S)
self.urladd1 = dict['urladd1']
self.urladd2 = dict['urladd2']
self.urladd3 = dict['urladd3']
self.redirect = dict['redirect']
def get_search(self, bookname):
def search(bookname):
# 获取搜索结果页面
global html
if self.send_way == 'get':
html = rq.get(self.search_url + bookname, headers=header)
elif self.send_way == 'post':
data = {'searchkey': bookname,
'name': bookname}
html = rq.post(self.search_url, data=data, headers=header)
html.encoding = None
found_result = re.findall(self.book_compile, html.text)
# 判断是否有这个小说
for i in range(len(found_result)):
if found_result[i][1] == bookname:
chapter_url = found_result[i][0]
if self.urladd3 == True:
chapter_url = self.booksourceUrl + chapter_url
return chapter_url
print('查无此book')
return '查无此book'
def main(bookname):
chapter_url = search(bookname)
if chapter_url == '查无此book':
return None
return chapter_url
return main(bookname)
def get_chapter(self, chapter_url):
def real_url(fake_url):
try:
url_redirects = rq.get(fake_url, headers=header, allow_redirects=False)
if url_redirects.status_code == 301:
url_redirects.headers['Location'] = real_url(url_redirects.headers['Location'])
url_redirects.close()
return str(url_redirects.headers['Location'])
except:
print('重定向这出错了')
# 获取章节目录和对应的url写成字典
# chapter_html指获取到的页面
# tuple_chapter指正则查找到的url、章节名组成的元组
# chapter_dict指要装入章节名:url的字典
# singlebook_url指重新填装的url
def get_chapter(chapter_url):
try:
chapter_html = rq.get(chapter_url, headers=header)
chapter_html.encoding = self.encode
tuple_chapter = re.findall(self.chapter_compile, chapter_html.text)
chapter_dict = {}
if self.urladd2 == True:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = chapter_url + tuple_chapter[i][0]
elif self.urladd2 == False and self.urladd1 == True:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = self.booksourceUrl + tuple_chapter[i][0]
else:
for i in range(len(tuple_chapter)):
chapter_dict[tuple_chapter[i][1]] = tuple_chapter[i][0]
return chapter_dict
except:
print('获取章节这出错了')
return None
if self.redirect == True: # 判断是否需要重定向
chapter_url = real_url(chapter_url)
return get_chapter(chapter_url)
def get_book(self, chapter_url):
html = rq.get(chapter_url, headers=header)
booktext = re.findall(self.text_compile, html.text)
print(booktext)
return booktext[0]
def __str__(self):
return str(self.dict)
# 这是一个对象模板,照着这个里面写就行,有什么也要往里面加嗷(●'◡'●)
dict_Template = {
'search_name': '',
'booksourceUrl': '',
'search_url': '',
'encode': '',
'book_compile': '',
'chapter_compile': '',
'text_compile': '',
'send_way': '', # 在进行搜索时的发送方式:get or post
'urladd1': False, # 获取到的每个章节页面里的href里是否不包含booksourceUrl(即原站点Url),若有,urladd1就是False
'urladd2': False, # 获取到的每个章节的href里面是否不包含章节url即chapter_url,若有,urladd2就是False,反之Ture
'urladd3': False, # 在搜索页面里的href里是否需要添加booksourceUrl,需要则True,反之。。。
'redirect': False, # 是否需要重定向
}
s1 = xiaoshuo_url({
'search_name': '顶点',
'booksourceUrl': 'https://www.xsbooktxt.com',
'search_url': 'https://so.biqusoso.com/s1.php?ie=utf-8&&siteid=booktxt.net&q=',
'encode': 'gbk',
'book_compile': '<span.*?s2"><a href="(.*?)".*?>(.*?)<.*?>',
'chapter_compile': '<dd><a href ="(.*?)">(.*?)</a>',
'text_compile': '<div id="content"><br /><br /><br /><br /><br />(.*?)<script>',
'send_way': 'get',
'urladd1': False,
'urladd2': True,
'urladd3': False,
'redirect': True
})
s2 = xiaoshuo_url({
'search_name': '新笔趣阁',
'booksourceUrl': 'http://www.xbiquge.la',
'search_url': 'http://www.xbiquge.la/modules/article/waps.php',
'encode': 'utf8',
'book_compile': '<td class="even"><a href="(.*?)".*?>(.*?)</a>',
'chapter_compile': "<dd><a href='(.*?)' >(.*?)</a></dd>",
'text_compile': '<div id="content">(.*?)<br /><br /><p>',
'send_way': 'post',
'urladd1': True,
# 有点奇怪这个站,在章节页面这的href里面直接包含的是小说后缀,也就是说直接加booksourceUrl就行,有点烦
'urladd2': False,
'urladd3': False,
'redirect': False,
})
s3 = xiaoshuo_url({
'search_name': '笔趣阁5200',
'booksourceUrl': 'http://www.b5200.net',
'search_url': 'http://www.b5200.net/modules/article/search.php?searchkey=',
'encode': 'gbk',
'book_compile': '<td class="odd"><a href="(.*?)">(.*?)</a></td>',
'chapter_compile': '<dd><a href="(.*?)">(.*?)</a></dd>',
'text_compile': '<div id="content"><p>(.*)</p>.*?</div>',
'send_way': 'get', # 在进行搜索时的发送方式:get or post
'urladd1': False,
'urladd2': False,
'urladd3': False,
'redirect': False, # 是否需要重定向
})
s4 = xiaoshuo_url({
'search_name': 'xxx笔趣阁',
'booksourceUrl': 'https://www.xxxbiquge.com',
'search_url': 'https://www.xxxbiquge.com/index.php?s=/web/index/search',
'encode': 'utf8',
'book_compile': '<span class="s2 wid"><a href="(.*?)" ta.*?>(.*?)</a></span>',
'chapter_compile': '<dd> <a.*?ef="(.*?)">(.*?)</a></dd>',
'text_compile': ' <div id="content" deep="3">.*?<p>.*?</p>(.*?)<div ',
'send_way': 'post', # 在进行搜索时的发送方式:get or post
'urladd1': True,
'urladd2': False,
'urladd3': True,
'redirect': False, # 是否需要重定向
})
s = [s1, s2, s3, s4]
class Ui(object):
def __init__(self):
# 搜索界面的加载和各个按钮的定义链接
self.search_ui = QUiLoader().load('search.ui')
self.search_ui.button.clicked.connect(self.__search) # 这里的按钮被命名为button
self.search_ui.chapterbutton.clicked.connect(self.enter_chapter)
# 目录界面的加载和各个按钮的定义链接
self.chapter_ui = QUiLoader().load('chapter.ui')
self.chapter_ui.button.clicked.connect(self.__chapter_button) # 这里的按钮被命名为button
# 警告界面的加载和各个按钮的定义链接
self.warnner_ui = QUiLoader().load('warnner.ui')
self.warnner_ui.backbutton.clicked.connect(self.search_show) # 这里的按钮被命名为backbutton返回按钮
# text界面的加载和各个按钮的定义链接
self.singlebook_ui = QUiLoader().load('singlebook.ui')
self.singlebook_ui.next_button.clicked.connect(self.__next_chapter) # 这里的按钮被命名为next_button下一章按钮
self.singlebook_ui.last_button.clicked.connect(self.__last_chapter) # 这里的按钮被命名为last_button上一章按钮
# 中间变量的定义
self.chapter = [] # 使用字典的方式,用于存放获取到的所有章节及url
self.booklist = [] # 用于存放多线程搜索到的各个站点章节url
self.lock = Lock()
self.__i = []
self.i = 0
# 主方法入口,其他方法均为私有方法,只有这个能被外部调用
def search_show(self):
self.search_ui.show()
# 搜索接口
def __search(self):
def thread_job(self, i, search_name):
chapter_url = s[i].get_search(search_name)
if chapter_url != None:
self.lock.acquire()
self.search_ui.listWidget.addItem(chapter_url)
# self.booklist.append(chapter)
chapter = s[i].get_chapter(chapter_url)
self.chapter.append(chapter)
self.__i.append(i)
self.lock.release()
search_name = self.search_ui.lineEdit.text()
thread_list = []
for i in range(len(s)):
thread = Thread(target=thread_job, args=(self, i, search_name))
thread.start()
thread_list.append(thread)
for single_thread in thread_list:
single_thread.join()
# if self.booklist == []:
# self.warnner_ui.show()
def enter_chapter(self):
j = self.search_ui.listWidget.currentRow()
self.current_chapter = self.chapter[j]
self.i = self.__i[j]
self.chapter_ui.listWidget.clear()
self.chapter_ui.listWidget.addItems(self.current_chapter.keys())
self.chapter_ui.show()
# self.chapter = s[i].xiaoshuo_look(search_name)
# print(i)
# # 这里的None比较重要,有着承上启下的作用,因为在书源的类里面如果没有找到或者出错,返回值均是None
# if self.chapter != None:
# # addItems() 将列表中所有的值 append 到 listwidget 中
# self.chapter_ui.listWidget.addItems(self.chapter.keys())
# self.chapter_ui.show()
# break
# else:
# self.warnner_ui.show()
# 将获取到的文本添加到text1文本框内,并展示出来
def __show_text(self, key):
# 先清楚文本框内的text
self.singlebook_ui.text1.clear()
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
text = s[self.i].get_book(key)
# 文本框使用append方法,括号内必须是str
self.singlebook_ui.text1.append(text)
self.singlebook_ui.show()
# chapter_ui 的按钮的入口
def __chapter_button(self):
key = self.chapter_ui.listWidget.currentItem().text()
singlechapter_url = self.current_chapter[key]
self.__show_text(singlechapter_url)
self.g = self.__key_next()
self.h = self.__key_last()
# 增加章节生成器 这里的方法比较取巧和勉强,是我自己琢磨出来的,应该还有其他更好的方法
def __key_next(self):
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
for value in list(self.current_chapter.values())[self.chapter_ui.listWidget.currentRow() + 1:]:
# for i in range(len(list(self.current_chapter.keys())[self.key:])):
yield value
# 减去章节生成器
def __key_last(self):
# ※ 由于dict.values() 和 dict.keys() 返回的都不能算是列表,所以这里需要使用list()转换一下
for value in list(self.current_chapter.values())[:self.chapter_ui.listWidget.currentRow() - 1]:
yield value
# 下一章
def __next_chapter(self):
# currentRow()方法指的是获取当前行的行数,从1开始
# self.key = self.chapter_ui.listWidget.currentRow()
# next()是含有yield的专有方法
jia = next(self.g)
self.__show_text(jia)
# 上一章
def __last_chapter(self):
jian = next(self.h)
self.__show_text(jian)
if __name__ == '__main__':
app = QApplication([])
ui = Ui()
ui.search_show()
app.exec_()
2020.2.9晚更新:
新增了多线程,修改了ui文件,重写了部分函数,修复了重新搜索小说后,显示章节不符的bug。
附件:
书源.txt
(3.1 KB, 下载次数: 19)
ui文件.rar
(3.1 KB, 下载次数: 20)
免费评分
查看全部评分