在开始之前,我们需要对小说网站(八一中文网)进行相应的分析。进入斗破苍穹第一章,右键点击检查,查看文章内容的具体位置。
从上图能看出文章内容直接存在于网页当中,是一个静态网页,直接提取对应标签内的文本即可。下面我利用单线程爬虫和多线程爬虫分别对斗破苍穹进行爬取。
一. 单线程爬虫
单线程爬虫适合爬取此网站任何小说,但是爬取的速度相对较慢。引用requests库和bs4库,对文章内容进行抓取,并定义文章的链接和请求头。请求头将爬虫模拟成浏览器请求,防止网站的反爬机制。
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
baseurl='https://www.81zw.com/'
url=baseurl+'/book/7574/18096.html'
headers={'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36'}
首先获取对应链接的网页内容并对网页内容进行处理。其中 r.text 为对应网页的源代码,使用bs4库对源代码进行规范化处理。
r = requests.get(url=url, headers=headers)
text = r.text
soup = BeautifulSoup(text,'html.parser')
在获取网页的源代码之后,我们要获取有文章内容的标签的内容。从此图我们可以看到,文章内容存放在一个<div>标签中,故获取这个标签的内容即可。
使用bs4库的find方法查找对应id的<div>标签的内容
text = soup.find('div',id='content').text #文章内容
此时的text的内容输出结果下图所示,换行和每一段的缩进需要进行额外处理。
将text(文章内容)放入列表中,对列表进行遍历,若遇到4个'\xa0',即上图红框标注的4个空位符,将第2个空位符改为换行符'\n',最后将列表转换成字符串,即可正确排版。
text1=list(text) #排版处理
for i in range(len(text1)):
if text1[i]=='\xa0' and text1[i+1]=='\xa0' and text1[i+2]=='\xa0' and text1[i+3]=='\xa0':
text1[i+1]='\n'
i=i+2
text ="".join(text1)
正确排版的结果:
接下来我们需要获取文章的题目,在文章标题处右键检查,得到下图结果,获取方法和上面获取内容相同。
title为获取的文章题目
title = soup.find('div',class_='bookname').h1.text
下面我们获取下一章的url,在源代码中找到此处,会发现除了最后的那个数字变化,其他的地方没有变化,故只需更新url的后半段就可以,然后通过新的url进行下一章的爬取。
通过查找所有的a标签并比对a标签的文本得到新url。
for i in soup.find_all('a'):
if i.text=="下一章":
url=baseurl+i['href']
最后进行文件的保存,我将所有的文章保存到一个txt文件中,便于在手机上打开。
text_path = 'D:\\小说\\斗破苍穹.txt'
with open(text_path,'a+',encoding='utf-8') as f:
f.writelines('\n')
f.writelines('\n')
f.writelines(title)
f.writelines('\n')
f.writelines(text)
最终得到的结果:
最后整理一下上述代码
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
baseurl='https://www.81zw.com/'
url=baseurl+'/book/7574/18096.html'
headers={'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36'}
while True:
try:
r = requests.get(url=url, headers=headers)
text = r.text
soup = BeautifulSoup(text,'html.parser')
title = soup.find('div',class_='bookname').h1.text
title1=list(title)
title2=[]
for i in range(len(title1)): #处理文章标题中可能出现的特殊符号
if title1[i]!='"':
title2.append(title1[i])
else:
pass
title="".join(title2)
text = soup.find('div',id='content').text
text1=list(text)
for i in soup.find_all('a'):
if i.text=="下一章":
url=baseurl+i['href']
for i in range(len(text1)):
if text1[i]=='\xa0' and text1[i+1]=='\xa0' and text1[i+2]=='\xa0' and text1[i+3]=='\xa0':
text1[i+1]='\n'
i=i+2
text ="".join(text1)
text_path = 'D:\\小说\\斗破苍穹.txt'
with open(text_path,'a+',encoding='utf-8') as f:
f.writelines('\n')
f.writelines('\n')
f.writelines(title)
f.writelines('\n')
f.writelines(text)
print(title)
except:
print(1)
二. 多线程爬虫
单线程爬虫虽然较为稳定,但是抓取文章的时间过长,cpu效率过低,故我在查阅相关资料之后又写了一个多线程爬虫,可以在半分钟内抓取整本小说。
多线程爬虫首先需要将整本书的各个章节的url集合到urls列表中。斗破苍穹这本书的各章url正好是连续的,方便使用多线程爬虫爬取。(有很多小说的各章节url是不连续的,可以爬取小说的url到一个数组,其他操作相同,小伙伴们可以自己尝试一下)。
import requests
from bs4 import BeautifulSoup
import threading
import time
urls=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(18096,19728)
]
urls1=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(19857006,19857011)
]
urls2=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(20222594,20222612)
]
urls.extend(urls1)
urls.extend(urls2)
在此代码中我为每个章节都定义了一个线程,故爬取的结果是无序的。我在保存文章内容的时候没有将所有的文章保存在一个txt文件中,而是每个章节保存成一个txt文件,并将文件名改为对应的章节号,便于在后面合并所有章节。
多线程爬虫的函数主体和单线程爬虫差不多,只是在文件名的处理和文件保存处有细微的差别。
def spider(url):
r = requests.get(url=url)
text = r.text
soup = BeautifulSoup(text,'html.parser')
title = soup.find('div',class_='bookname').h1.text
title1=list(title)
title2=[]
a=0
b=0
for i in range(len(title1)):
if title1[i]=='第':
a=i
if title1[i]=='章':
b=i
break
for i in range(a+1,b):
title2.append(title1[i])
title="".join(title2)
a3=chinese2digits(title)
a4=str(a3)
a5='第'+a4+'章'
title2=[]
title2.append(a5)
for i in range(b+1,len(title1)):
title2.append(title1[i])
title="".join(title2)
text = soup.find('div',id='content').text
text1=list(text)
for i in range(len(text1)):
if text1[i]=='\xa0' and text1[i+1]=='\xa0' and text1[i+2]=='\xa0' and text1[i+3]=='\xa0':
text1[i+1]='\n'
i=i+2
text ="".join(text1)
text_path = 'D:\\斗破苍穹2\\'+a4+'.txt'
with open(text_path,'a+',encoding='utf-8') as f:
f.writelines('\n')
f.writelines('\n')
f.writelines(title)
f.writelines('\n')
f.writelines(text)
# print(title)
定义多线程执行函数(每个章节定义一个线程):
def mul_spider():
threads=[]
for url in urls:
threads.append(
threading.Thread(target=spider, args=(url,))
)
for thread in threads:
thread.start()
定义文字数字转阿拉伯数字函数(文件名保存为阿拉伯数字便于排序)
def chinese2digits(uchars_chinese):
total = 0
r = 1 #表示单位:个十百千...
for i in range(len(uchars_chinese) - 1, -1, -1):
val = common_used_numerals_tmp.get(uchars_chinese[i])
if val >= 10 and i == 0: #应对 十三 十四 十*之类
if val > r:
r = val
total = total + val
else:
r = r * val
elif val >= 10:
if val > r:
r = val
else:
r = r * val
else:
total = total + r * val
return total
最后整理一下上述代码:
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import threading
urls=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(18096,19728)
]
urls1=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(19857006,19857011)
]
urls2=[
f"https://www.81zw.com/book/7574/{page}.html"
for page in range(20222594,20222612)
]
urls.extend(urls1)
urls.extend(urls2)
common_used_numerals_tmp ={'零':0, '一':1, '二':2, '两':2, '三':3, '四':4, '五':5, '六':6, '七':7, '八':8, '九':9, '十':10, '百':100, '千':1000}
common_used_numerals= dict(zip(common_used_numerals_tmp.values(), common_used_numerals_tmp.keys())) #反转
def chinese2digits(uchars_chinese):
total = 0
r = 1 #表示单位:个十百千...
for i in range(len(uchars_chinese) - 1, -1, -1):
val = common_used_numerals_tmp.get(uchars_chinese[i])
if val >= 10 and i == 0: #应对 十三 十四 十*之类
if val > r:
r = val
total = total + val
else:
r = r * val
elif val >= 10:
if val > r:
r = val
else:
r = r * val
else:
total = total + r * val
return total
def spider(url):
r = requests.get(url=url)
text = r.text
soup = BeautifulSoup(text,'html.parser')
title = soup.find('div',class_='bookname').h1.text
title1=list(title)
title2=[]
a=0
b=0
for i in range(len(title1)):
if title1[i]=='第':
a=i
if title1[i]=='章':
b=i
break
for i in range(a+1,b):
title2.append(title1[i])
title="".join(title2)
a3=chinese2digits(title)
a4=str(a3)
a5='第'+a4+'章'
title2=[]
title2.append(a5)
for i in range(b+1,len(title1)):
title2.append(title1[i])
title="".join(title2)
text = soup.find('div',id='content').text
text1=list(text)
for i in range(len(text1)):
if text1[i]=='\xa0' and text1[i+1]=='\xa0' and text1[i+2]=='\xa0' and text1[i+3]=='\xa0':
text1[i+1]='\n'
i=i+2
text ="".join(text1)
text_path = 'D:\\斗破苍穹\\'+a4+'.txt'
with open(text_path,'a+',encoding='utf-8') as f:
f.writelines('\n')
f.writelines('\n')
f.writelines(title)
f.writelines('\n')
f.writelines(text)
# print(title)
def mul_spider():
threads=[]
for url in urls:
threads.append(
threading.Thread(target=spider, args=(url,))
)
for thread in threads:
thread.start()
if __name__ == "__main__":
mul_spider()
运行结果:31秒爬完整部小说并保存到对应文件夹
下面我们使用一段代码将这个文件夹中的文件按顺序合并
text_path=[
f"D:\\斗破苍穹\\{page}.txt"
for page in range(1,1624)
]
text_path2="D:\\斗破苍穹.txt"
for i in range(0,1624):
try:
f=open(text_path[i],"r",encoding='utf-8')
str=f.read()
f.close()
with open(text_path2,'a+',encoding='utf-8') as f:
f.writelines(str)
except:
print(i)
代码运行完成后在对应的位置就可以看到合并后的结果了
然后将文件发到手机上,愉快的看小说吧,再也不用怕断网看不了小说了!