前言:
原标题----运用Python多线程爬虫下载纵横中文网上的小说
这篇博文之所以取一个这样的标题,除了是想吸引读者的眼球之外,更重要的是最后的确实在wps等工具上阅读小说的。
文章目录
1.做这个项目所需要的模块
python自带模块:os、threading
需要额外安装的模块:bs4、urllib、requests
怎样安装:
bs4 :pip install bs4
requests:pip install requests
urllib:pip install urllib3
2.怎样实现
2.1.得到小说的网址
我们首先要进入这个网址:
http://www.zongheng.com/
在搜索框中输入自己想看的小说的类型,点击搜索
如下:我输入的是 三国
这就是匹配到的小说名称了。
我们点击其中一本小说进入来到这个界面:
可以发现,这个界面的网址为:http://book.zongheng.com/book/591854.html
回到原来的那个界面,点击鼠标右键,然后点击检查,我们可以发现这个网址在上一个网址的这里。
代码:
def matching_book(): # 得到所有匹配到的小说
keyword = input('请输入你想下载的小说标题:')
key_word = parse.urlencode({'keyword': keyword})
url='http://search.zongheng.com/s?%s'%(key_word)
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
html=requests.get(url=url,headers=headers)
info=html.text # 网页信息
soup=BeautifulSoup(info,'lxml')
list_1=soup.select('div.search-tab>div.search-result-list.clearfix') # 小说信息
book_url=[]
book_name=[]
for i in range(len(list_1)):
str_1=list_1[i].select('div.fl.se-result-infos>h2>a')[0]['href'] # 小说的网址
name_1=list_1[i].select('div.fl.se-result-infos>h2>a')[0].get_text() # 匹配到的小说名称
book_url.append(str_1)
book_name.append(name_1)
print('【{}】-{}'.format(i+1,name_1))
id=int(input('请输入你想看的小说序号:'))
return book_url[id-1],book_name[id-1]
这样我们就得到了自己想看的小说网址了。
2.2. 得到小说目录的网址
我们来到这个界面,跟上面一样,进行检查。
发现这个网址在div.fr.link-group>a.all-catalog里面
为了使读者对这篇小说更加理解,也可以将小说的简介和类型也爬取下来。
代码:
def get_catalog_url(url):
def get_info(list_1):
str_1=''
for html_1 in list_1:
str_1+=html_1.get_text()+','
return str_1
# url='http://book.zongheng.com/book/591854.html'
html_1=requests.get(url=url)
info_1=html_1.text
soup_1=BeautifulSoup(info_1,'lxml')
info_book=soup_1.select('div.book-info')
book_label_1=get_info(info_book[0].select('div.book-label>a'))
book_label_2=get_info(info_book[0].select('div.book-label>span>a'))
book_label=book_label_1+book_label_2
jian_jie=(info_book[0].select('div.book-dec.Jbook-dec.hide>p'))[0].get_text() #小说的简介
book_mu_url=info_book[0].select('div.btn-group>div.fr.link-group>a.all-catalog')[0]['href'] # 小说目录的网址
print('---->{}'.format(book_label))
print('---->{}'.format(jian_jie))
return book_mu_url
代码中需要传入的url就是上面2.1得到那个url
2.3.得到小说所有章节的网址
我们点击全部目录,来到这个界面,我们只需将这个界面里的所有小说章节的网址爬到,并进入这个网址就可以得到自己想看的小说内容了。
代码:
def get_catalog(url):
# url='http://book.zongheng.com/showchapter/591854.html'
headers_1={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
html_2=requests.get(url=url,headers=headers_1)
soup_2=BeautifulSoup(html_2.text,'lxml')
list_3=soup_2.select('div.volume-list>div')
title_list=[]
ti_url_list=[]
for soup_3 in list_3:
info_text=soup_3.select('div.volume ')[0].get_text().strip('\n') #去掉左右换行
title_list.append(info_text)
list_text=soup_3.select('ul.chapter-list.clearfix>li>a')
for i in range(len(list_text)):
list_text[i]=[list_text[i].get_text(),list_text[i]['href']]
ti_url_list.append(list_text)
for i in range(len(title_list)):
print(title_list[i])
for j in range(len(ti_url_list[i])):
print(ti_url_list[i][j][0])
return ti_url_list
这个函数跟上面一样,传入参数是2.2得到的那个网址,返回的是一个列表,也就是小说的所有章节的网址。
2.4 爬取小说所有章节的内容
我们随便点击其中的一章阅读,本来想跟上面一样,结果不行,在这个网址下无法按鼠标右键,不过,可以按F12,可以发现所有内容都在div.content下面的p标签里,我们只需爬取相应内容即可。
def Write_To_Wps(ti_url_list,book_name):
def Thread_write(ti_url_list:list,book_name:str):
while True:
if len(ti_url_list)==0:
break
list_2=ti_url_list.pop()
while True:
if len(list_2)==0:
break
url_text=list_2.pop()
# url_1='http://book.zongheng.com/chapter/591854/38192586.html'
url_1=url_text[-1]
text_name=url_text[0]
headers_1={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
info_4=requests.get(url=url_1,headers=headers_1).text
soup_4=BeautifulSoup(info_4,'lxml')
text_info=soup_4.select('div.reader_box')
# text_name=text_info[0].select('div.title>div.title_txtbox')[0].get_text() #小说一章的标题
list_1=text_info[0].select('div.content>p') #小说一章的内容
path_1='./{}'.format(book_name)
try:
os.mkdir(path_1)
except:
pass
finally:
str_info=''
for str_1 in list_1:
str_info+=str_1.get_text()+'\n'
path_2=path_1+'/{}.doc'.format(text_name)
print('当前线程为{1},正在下载{0}'.format(text_name,threading.current_thread().getName()))
text_name=' '*30+text_name+'\n'
with open(path_2,'w',encoding='utf-8') as f:
f.write(text_name)
f.write(str_info)
threading_list=[]
for i in range(10):
threading_1=threading.Thread(target=Thread_write,args=(ti_url_list,book_name,))
threading_1.start()
threading_list.append(threading_1)
for i in threading_list:
i.join()
print('------------下载完毕!当前线程为{}'.format(threading.current_thread().getName()))
这个函数传入的参数就是***2.3***得到那个列表。
在这里,运用了多线程。不过,有个小问题,就是实际上用到的线程好像没有10个(原本创建了10个线程),不知道是不是我在代码中用到两个while循环的原因。
3.最终代码
import requests
from bs4 import BeautifulSoup
from urllib import parse
import os
import threading
def matching_book(): # 得到所有匹配到的小说
keyword = input('请输入你想下载的小说标题:')
key_word = parse.urlencode({'keyword': keyword})
url='http://search.zongheng.com/s?%s'%(key_word)
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
html=requests.get(url=url,headers=headers)
info=html.text # 网页信息
soup=BeautifulSoup(info,'lxml')
list_1=soup.select('div.search-tab>div.search-result-list.clearfix') # 小说信息
book_url=[]
book_name=[]
for i in range(len(list_1)):
str_1=list_1[i].select('div.fl.se-result-infos>h2>a')[0]['href'] # 小说的网址
name_1=list_1[i].select('div.fl.se-result-infos>h2>a')[0].get_text() # 匹配到的小说名称
book_url.append(str_1)
book_name.append(name_1)
print('【{}】-{}'.format(i+1,name_1))
id=int(input('请输入你想看的小说序号:'))
return book_url[id-1],book_name[id-1]
def get_catalog_url(url):
def get_info(list_1):
str_1=''
for html_1 in list_1:
str_1+=html_1.get_text()+','
return str_1
# url='http://book.zongheng.com/book/591854.html'
html_1=requests.get(url=url)
info_1=html_1.text
soup_1=BeautifulSoup(info_1,'lxml')
info_book=soup_1.select('div.book-info')
book_label_1=get_info(info_book[0].select('div.book-label>a'))
book_label_2=get_info(info_book[0].select('div.book-label>span>a'))
book_label=book_label_1+book_label_2
jian_jie=(info_book[0].select('div.book-dec.Jbook-dec.hide>p'))[0].get_text() #小说的简介
book_mu_url=info_book[0].select('div.btn-group>div.fr.link-group>a.all-catalog')[0]['href'] # 小说目录的网址
print('---->{}'.format(book_label))
print('---->{}'.format(jian_jie))
return book_mu_url
def get_catalog(url):
# url='http://book.zongheng.com/showchapter/591854.html'
headers_1={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
html_2=requests.get(url=url,headers=headers_1)
soup_2=BeautifulSoup(html_2.text,'lxml')
list_3=soup_2.select('div.volume-list>div')
title_list=[]
ti_url_list=[]
for soup_3 in list_3:
info_text=soup_3.select('div.volume ')[0].get_text().strip('\n') #去掉左右换行
title_list.append(info_text)
list_text=soup_3.select('ul.chapter-list.clearfix>li>a')
for i in range(len(list_text)):
list_text[i]=[list_text[i].get_text(),list_text[i]['href']]
ti_url_list.append(list_text)
for i in range(len(title_list)):
print(title_list[i])
for j in range(len(ti_url_list[i])):
print(ti_url_list[i][j][0])
return ti_url_list
def Write_To_Wps(ti_url_list,book_name):
def Thread_write(ti_url_list:list,book_name:str):
while True:
if len(ti_url_list)==0:
break
list_2=ti_url_list.pop()
while True:
if len(list_2)==0:
break
url_text=list_2.pop()
# url_1='http://book.zongheng.com/chapter/591854/38192586.html'
url_1=url_text[-1]
text_name=url_text[0]
headers_1={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}
info_4=requests.get(url=url_1,headers=headers_1).text
soup_4=BeautifulSoup(info_4,'lxml')
text_info=soup_4.select('div.reader_box')
# text_name=text_info[0].select('div.title>div.title_txtbox')[0].get_text() #小说一章的标题
list_1=text_info[0].select('div.content>p') #小说一章的内容
path_1='./{}'.format(book_name)
try:
os.mkdir(path_1)
except:
pass
finally:
str_info=''
for str_1 in list_1:
str_info+=str_1.get_text()+'\n'
path_2=path_1+'/{}.doc'.format(text_name)
print('当前线程为{1},正在下载{0}'.format(text_name,threading.current_thread().getName()))
text_name=' '*30+text_name+'\n'
with open(path_2,'w',encoding='utf-8') as f:
f.write(text_name)
f.write(str_info)
threading_list=[]
for i in range(10):
threading_1=threading.Thread(target=Thread_write,args=(ti_url_list,book_name,))
threading_1.start()
threading_list.append(threading_1)
for i in threading_list:
i.join()
print('------------下载完毕!当前线程为{}'.format(threading.current_thread().getName()))
if __name__ == '__main__':
tuple_1=matching_book() # 这个返回的是一个元组,第一个参数是小说的网址,第二个参数是小说的名称
url,book_name=tuple_1[0],tuple_1[1]
catalog_url=get_catalog_url(url) #小说目录的url
ti_url_list=get_catalog(catalog_url)
Write_To_Wps(ti_url_list=ti_url_list,book_name=book_name)
运行结果:
这个运行完之后,可以发现在当前文件夹下面多了一个文件夹,文件夹的名称就是自己选的小说名称,所下载的小说章节就在这个文件夹下面。
感觉这个效果好像不是很好,为了更加清晰看到自己下载的总章节和方便阅读,我们可以执行一下这个代码:
import os
path_1=input('输入路径:')
list_1=os.listdir(path_1)
dict_1 = {
'一': '1',
'二': '2',
'三': '3',
'四': '4',
'五': '5',
'六': '6',
'七': '7',
'八': '8',
'九': '9',
'零': '0'
}
list_2=[]
for i in range(len(list_1)):
str_1=list_1[i][list_1[i].find('第')+1:list_1[i].find('章 ')]
for j in dict_1:
str_1=str_1.replace(j,dict_1[j])
if '十' in str_1:
if len(str_1) == 1:
str_1 = '10'
elif str_1[-1]=='十':
str_1=str_1.replace('十','0')
elif str_1[0]=='十':
str_1=str_1.replace('十','1')
else:
str_1=str_1.replace('十','')
if '百' in str_1:
if len(str_1)==2:
str_1=str_1.replace('百','00')
else:
str_1=str_1.replace('百','')
str_1=list_1[i].replace(list_1[i][list_1[i].find('第')+1:list_1[i].find('章 ')],str_1)
list_2.append(str_1)
for i in range(len(list_1)):
print(list_1[i],list_2[i])
os.rename(path_1+'\\'+list_1[i],path_1+'\\'+list_2[i])
运行结果:
4.改进与总结
- 也许是访问次数过多的原因,服务器会封掉我们的ip地址,如果讲改进的话,希望用到代理ip;
- 有一些小说因为章节名称的原因,会包错,这个解决我已经在最终代码上进行改进了,但在2.4那里没有改,希望大家谅解;
- 本代码仅供娱乐和学习,切莫用于商业,一经发现,概不负责!
- 因为有的小说部分章节需要登陆,才能阅读所有内容,所以这些章节最终爬取得到的内容只有一点点,不知道大家有什么好的改进措施吗?欢迎留言。