项目背景:作为一个标准肥宅,周末看小说成了打发时间的标配,结果逛了几个小时的小说圈,发现居然无书可看,顿时怒从心头起,恶向胆边生,随即写一个爬虫,爬取镇魂小说网的一些小说,诸位看官请坐,且听我一 一道来。
目标网址:https://www.zhenhunxiaoshuo.com
代码思路:分为三步走,
首先:获取到站点分类下的书本链接
其次:请求书籍的链接,获取书籍章节
最后:请求章节内容,保存为txt文件
接下来上菜
代码详细情况如下:
搞python,无法避免的第一味开胃汤,导入需要用到的包
import re #正则表达式专用
import os #写入文件
import requests #发送请求
import traceback #异常处理
import multiprocessing #综合处理线程包
import lxml.etree #解析html
from lxml import etree
from multiprocessing.dummy import Pool #多线程加速
接下来,开始上主菜,由本大厨为各位顾客介绍一下咱们的菜品,上的第一道菜,是一个初始化方法,它采用__init__方法构造函数,用于初始化示例对象,当创建这个类的新实例时,这个方法就会被自动调用。然后设置实例变量_root_url,用来存储爬虫的基础url。最后分别设置_chunai_path、_yanqing_path、_priest_path三个变量用来作为存放文件的目录。
def __init__(self):
#设置根URL
self._root_url = 'https://www.zhenhunxiaoshuo.com'
#设置头信息
self._headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39',
}
#设置目标文件存放目录
self._chunai_path = './纯爱小说类'
self._yanqing_path = './言情小说类'
self._priest_path = './priest小说集'
第一道菜虽然味美,但是快乐的时光总是短暂的,接下来,我们在期望的目光中迎来了第二道菜品。
def get_pagination(self, url) -> int:
'''
获取当前分类共有多少页
'''
response = requests.get(url=url, headers=self._headers)
response.close()
tree = etree.HTML(response.text)
try:
th_list = tree.xpath('//div[@class="content"]/article/table[2]//tr/th')
if th_list:
pagination = len(th_list) - 2
return pagination
else:
return 0
except Exception as e:
return 0
看到这里,是不是有一些初学者准备开口骂人了,说好的期望目光呢,结果每个字母都认识,但是连在一起就不认识了。
且听我一一道来,这味菜之所以好,好在哪里?
首先,说明一下,这个函数的主要功能是获取我们需要爬取的数据的页数有多少。request和etree的组合,request请求数据,etree将数据转换成我们熟悉的html格式,然后再通过xpath强大的功能将页数提取出来,在顾客的口中留下与众不同的味道。
接下来的几道菜品,是为了弥补顾客上一道菜吃得不过硬,特意为大家加上的,和上一道异曲同工,request和etree的组合,request请求数据,etree将数据转换成html格式,然后再通过xpath提取数据,然后删除0号位置。
def parse_outline(self, url: str) -> list:
'''
tr标签的列表tr_list
'''
response = requests.get(url=url, headers=self._headers)
response.close()
tree = etree.HTML(response.text)
tr_list = tree.xpath('//article[@class="article-content"]/table[1]//tr')
del tr_list[0]
# 返回tr标签的列表tr_list
return tr_list
这一类的菜品上齐了,接下来给大家展示一道不一样的菜品,用熟悉的配方,熟悉的调料,制作出的不一样风味的美食。
def get_novel_data(self, tr: lxml.etree) -> tuple:
'''
解析tr标签中对应的小说url,小说名字,小说作者
'''
try:
novel_name = tr.xpath('./td/a/strong/text()')[0]
novel_author = tr.xpath('./td[2]/text()')[0]
novel_url = self._root_url + tr.xpath('./td/a/@href')[0]
path_novel = f'{self._chunai_path}/{novel_name}---{novel_author}'
if not os.path.exists(path_novel):
os.mkdir(path_novel)
return path_novel, novel_name, novel_url
except Exception as e:
pass
每一行代码眼熟吧!
开局王炸,采用大火将xpath进行烹饪,一开始就进行解析,烹饪出名字、路径以及url,然后创建存储路径,这样,高端的美食往往只需要简单的处理,一道色香味俱全的美食就出炉了。
接下来上一道硬菜,
def get_section_data(self, novel_data: dict) -> None:
'''
通过novel_data获取每一章节对应的内容
'''
novel_path = novel_data[0]
novel_url = novel_data[2]
novel_name = novel_data[1]
response = requests.get(url=novel_url, headers=self._headers)
response.close()
tree = etree.HTML(response.text)
article_list = tree.xpath('//div[@class="excerpts"]/article')
for article in article_list:
section_name = article.xpath('./a/text()')[0]
section_url = article.xpath('./a/@href')[0]
try:
r = re.findall('[¥#%*&@]', section_name)
for i in r:
section_name = section_name.replace(i, '')
except Exception as e:
pass
path = novel_path + f'/{section_name}.txt'
name = novel_name + f'---{section_name}'
response = requests.get(url=section_url, headers=self._headers)
response.close()
tree = etree.HTML(response.text)
content = tree.xpath('//article[@class="article-content"]//text()')
content = ''.join(content)
# 保存
self.save_data(path=path, name=name, content=content)
return
怎么样,看到代码吓到了吧。但是,总是有那么几个大哥是喜欢啃硬骨头的,那么接下来,我带大家品尝一下这一味大骨汤。
首先从整体来看这段Python代码定义了一个名为get_section_data的方法,该方法用于从一个小说网站获取小说的每一章节的内容。
然后切块处理,5-8行代码提取小说路径、URL和名称,10-13行代码使用requests库发送GET请求到小说URL,并使用etree.HTML解析返回的HTML内容,使用XPath查询HTML文档,找到所有class为"excerpts"的div元素下的article元素。接下来遍历每一个章节。15-16行代码提取章节名称和URL,17-20行代码表示如果章节名称包含特定的字符(如'¥#%*&@'),则删除这些字符。
23-24行就进入尾声了,构建保存路径和文件名,25行到29行代码表示,发送请求获取章节的内容,提取章节内容,保存章节内容。
最后return结束这道美食。
事实证明,硬菜都是需要细嚼慢咽才能品尝出味道。
下一道是小甜品,作为中场休息补充能量专用。
def save_data(self, path: str, name: str, content: str) -> None:
'''
保存数据
'''
if not os.path.exists(path):
with open(file=path, mode='w', encoding='utf-8') as f:
f.write(content)
print(f'下载完成---{name}')
else:
print(f'已经下载---{name}')
return
该方法根据提供的path、name和content参数,将章节内容保存到指定的文件路径。如果文件已存在,则不会重新写入内容,并打印一条消息表示已经下载。
慢慢的慢慢的,开始渐入佳境,开始回味上面菜品的味道,开始调用上面定义的函数,tr标签,tr标签对应的内容,然后把内容报错。
def spider(self, url: str) -> None:
'''
开始爬虫
'''
pool = Pool(15)
# 解析第一页大纲每本小说的tr标签
tr_list = self.parse_outline(url=url)
# 通过tr标签解析对应的小说url, 小说名字,小说作者
novel_data_list = pool.map(self.get_novel_data, tr_list)
# 获取小说内容并保存
pool.map(self.get_section_data, novel_data_list)
return
最后按照类别,将纯爱小说类、言情小说类、priest小说集等批量保存到本地文件中。
def chunai(self):
if not os.path.exists(self._chunai_path):
os.mkdir(self._chunai_path)
url_outline = self._root_url + '/chunai'
# 获取页数
pagination = self.get_pagination(url=url_outline)
if pagination == 0:
print('纯爱类小说---共1页')
self.spider(url=url_outline)
else:
print(f'纯爱类小说---共{pagination}页')
# 爬取第一页
self.spider(url=url_outline)
# 爬取以后的:
for i in range(2, pagination + 1):
page_url = self._root_url + f'/chunai{i}'
self.spider(url=page_url)
def yanqing(self):
if not os.path.exists(self._yanqing_path):
os.mkdir(self._yanqing_path)
url_outline = self._root_url + '/yanqing'
# 获取页数
pagination = self.get_pagination(url=url_outline)
if pagination == 0:
print('言情类小说---共1页')
self.spider(url=url_outline)
else:
print(f'言情类小说---共{pagination}页')
# 爬取第一页
self.spider(url=url_outline)
# 爬取以后的:
for i in range(2, pagination + 1):
page_url = self._root_url + f'/yanqing{i}'
self.spider(url=page_url)
def priest(self):
if not os.path.exists(self._priest_path):
os.mkdir(self._priest_path)
url_outline = self._root_url + '/priest'
# 获取页数
pagination = self.get_pagination(url=url_outline)
if pagination == 0:
print('priest小说集---共1页')
self.spider(url=url_outline)
else:
print(f'priest小说集---共{pagination}页')
# 爬取第一页
self.spider(url=url_outline)
# 爬取以后的:
for i in range(2, pagination + 1):
page_url = self._root_url + f'/priest{i}'
self.spider(url=page_url)
这里不做详细解释,注释已经写好,并且每类的逻辑基本一致。
最后搞一点宵夜。
if __name__ == '__main__':
try:
zh = ZhenHun()
zh.chunai()
zh.yanqing()
zh.priest()
input('全部小说获取完成')
except Exception as e:
print(traceback.format_exc())
input('有异常')
果然在变胖的路上一去不回。