一、引言
在一篇文章带你入门爬虫并编写自己的第一个爬虫程序中,我们初步学习了爬虫,了解了爬虫的基本概念、爬虫的基本流程,并自己上手编写了一个简单的爬取豆瓣top250的爬虫程序。在这篇文章中,我们将提升一点难度,来编写一个对网络小说网站上的小说进行爬取的爬虫程序,实现一个能将网络小说资源下载到本地的脚本程序。
如果您觉得文字讲解有些许枯燥或者不太容易理解,您也可以移步我的b站视频:Python爬虫Lesson5 实战:爬取笔趣阁网络热门小说_哔哩哔哩_bilibili进行本实战训练的学习。
二、环境准备:
Python=3.10
requests
bs4 - Beautifulsoup
re
tqdm - tqdm
三、爬虫分析
首先,我们需要爬取的网络小说网站为:https://www.bqgui.cc/
我们试图实现的功能为:将用户需要下载的小说的全部章节保存到本地。
于是这个程序的输入和输出就都很清楚了——对于输入,我们需要用户提供对应网络小说的信息;对于输出,我们的程序需要将对应的网络小说下载到本地。
那么我们不妨让用户输入对应小说在这个网页里的url,如:
庆余年最新章节_庆余年全文在线阅读_猫腻的小说_笔趣阁 => 对应 《庆余年》这本小说
那么我们就得到了这个网页
接下来我们需要对这个界面下的所有章节进行爬取。
我们来分析一下实现这样的下载功能的爬虫程序应该如何编写:
第一步,我们需要向用户提供的主网页发送请求,在这个页面上,我们可以获取如小说标题、小说作者、最新章节等信息。
第二步,我们需要向小说的所有章节页面进行跳转,来获取所有章节的内容。
第三步,当我们跳转到章节页面后(如下图),我们需要获取网页中展示出来的章节标题与章节正文内容,这一步的实现与之前的爬取豆瓣top250并没有太大的区别。
第四步,将获得到的文本内容保存到本地。
分析完了爬虫程序的主要过程,我们就可以着手代码的编写了。
四、爬虫程序编写
首先,我们先导入需要的python库,准备好发送网络请求需要的目标url和请求头headers。
接着我们就可以向小说主页面发送request.get请求,获取网页的文本信息,并使用Beautifulsoup将获取到的网页文本信息以HTML文件格式读入,然后去其中的小说名称、小说作者。
我们先回到主页面,分析网页元素,找到定位到小说名称、小说作者的方法。
根据网页源代码,我们不难找到对应元素的定位方法:
小说名称: div,class:info => 第一个h1
小说作者: div,class:info => div,class:small => 第一个span
所以我们不妨先定位到div,class:info,然后在此基础上去定位到小说名称和小说作者:
我们运行程序,看看能否成功抓取到对应小说的标题与作者:
发现已经成功抓取,congratulations!
然后就来到了这个项目相对有难度的一步:实现主界面向各个章节页面的跳转。
我们不妨思考一下,我们要实现小说主界面向各个章节页面跳转,其中最关键的是什么?是不是我们需要得到章节页面的链接?那么现在我们的问题就转变为了——如何获得子章节页面的链接。
那么如何获得子章节网页的链接呢?有两个途径:
第一种方法:我们可以看见小说的主页面分布着链向各个章节的超链接,我们可以对这些超链接进行抓取,得到链接后再依次对各个章节页面进行跳转。
但我们发现,这个网页的源代码中存在着一个玄机,在章节列表中存在着”dd_hide”这样一个span,这个span下的超链接是被刻意“隐藏”起来的,因此当我们使用Beautifulsoup对章节页面的超链接进行抓取时会不那么方便,会给我们的爬虫程序带来一定的麻烦。
所以我们不妨转变思路,不再去尝试抓取子章节页面的超链接,而是去尝试找到子章节页面的规律(就像我们上次爬取豆瓣top250时那样)。
我们在网页源代码中直接观察各个章节对应的超链接:
我们不难发现,这些章节对应的超链接有很强的规律性:即第一章对应/1.html,第二章对应/2.html,以此类推。所以我们如果想要找到所有章节的链接,其实就只需要得到总章节数(也即最后一章的链接),然后写一个for循环依次爬取即可。
那么现在问题就转变成——我们如何获取总章节数/最后一章的链接呢?还记得我们在分析小说主页面的时候提到的“最新章节”的信息吗?我们看一看这个最新章节的url:
可以看出,我们在小说主页面可以直接得到最新章节的url,且我们可以在获取小说标题、小说作者的时候同时把小说的最新章节url得到,然后我们只需要对小说的url字符串进行一个正则匹配即可获得总章节数,操作如下:
我们运行一下程序,得到输出:
说明我们已经成功获得了小说的总章节数,接下来我们只需要通过for循环来实现向各个子章节页面的跳转,并获得子章节页面的网页文本信息:
接下来我们就进入了对子章节页面的标题及正文内容进行爬取的环节。(以第一章为例)
首先,我们先对章节标题进行爬取:
我们可以看见章节标题存储在一个h1,class:wap_none下,我们直接进行定位,爬取章节标题的文本:
接下来,我们就要对章节的正文内容进行爬取:
我们发现章节的正文内容存储在一个div,id:chaptercontent下,我们之前提到过id相当于元素的身份证,一个id对应一个元素,于是我们可以直接通过id对这个div进行抓取,并将其转为python的字符串:
接下来我们就要对正文内容进行正则抓取,我们不妨先把chapter_content_str存储到本地,然后进行正则表达式的书写:
然后我们打开chapter_1.txt文档进行观察分析:
我们的目的是抓取正文内容并不妨除去章节末尾的广告内容,于是得到以下正则表达式:
接下来我们对得到的文本进行一定处理,如把网页中的<br><br/>标签换成换行符,这样便于我们进行阅读:
然后我们运行程序,把获取到的内容保存到本地,得到如下文档:
说明已经抓取成功了,接下来只需要在起始位置加上章节的标题,然后通过for循环进行爬取即可。为了方便用户了解爬取网络小说的进度,我们可以使用tqdm库中的tqdm函数来实现进度条的功能,整体代码如下:
如此我们便实现了逐章节的爬取,运行程序,得到:
那么我们便完成了这样一个爬取网络小说的爬虫程序!Congratulations!
五、源代码
最后附上源代码:
import requests # 发送网络请求
from bs4 import BeautifulSoup # 分析网页 爬取信息
import re # 正则表达式 文本定位 文本处理
from tqdm import tqdm # 进度条
book_url = input('请输入目标小说url:\n')
# 为了保证url以 / 结尾
if book_url[-1] !='/':
book_url+='/'
headers_ = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
}
book_main_response = requests.get(url=book_url,headers=headers_)
book_html_text = book_main_response.text
book_html_soup = BeautifulSoup(book_html_text,'html.parser')
book_info_find = book_html_soup.find('div',attrs={'class':'info'})
# print(book_info_find)
book_title = book_info_find.find('h1').text
book_author = book_info_find.find('div',attrs={'class':'small'}).find('span').text
book_latest_href = book_info_find.find('div',attrs={'class':'small'}).find('a').get('href')
book_latest_num = int(re.findall(r'/book/\d+/(\d+).html',str(book_latest_href))[0])
print(book_title)
print(book_author)
print(f'共需爬取{book_latest_num}章')
with open(f'{book_title},{book_author}.txt','w',encoding='utf-8') as book_output:
for i in tqdm(range(1,book_latest_num+1,1),desc=f'正在爬取:{book_title},{book_author}'):
chapter_url = book_url+f'{i}.html'
# print(chapter_url)
chapter_response = requests.get(url=chapter_url,headers=headers_)
chapter_html_text = chapter_response.text
chapter_html_soup = BeautifulSoup(chapter_html_text,'html.parser')
chapter_title = chapter_html_soup.find('h1',attrs={'class':'wap_none'}).text
chapter_content_find = chapter_html_soup.find('div',attrs={'id':'chaptercontent'})
chapter_content_str = str(chapter_content_find)
re_chapter_content = str(re.findall(r'id="chaptercontent">(.*)<br/><br/>.*<br/><br/>',chapter_content_str)[0])
clean_chapter_content = re_chapter_content.replace('<br/><br/>','\n')
# with open('chapter_get_1.txt','w',encoding='utf-8') as chapter_out:
# chapter_out.write(clean_chapter_content)
# print(clean_chapter_content)
book_output.write(chapter_title+'\n')
book_output.write(clean_chapter_content+'\n\n')