导语
正好最近收到我之前在某论坛上发表的爬取小说网站的文章回帖,便乘着有闲暇时间重写一遍,贴上分析过程发表于此,技术尚浅,大佬勿喷,如有问题欢迎留言评论。
正文
变量声明
这里博主采用的是面向对象的编程方法。
首先,我们先定义__init__方法,声明接下来我们可能用到的一些基本的变量。
在此,我们初始化novel_data、headers两个字典,并声明字符串start_url。其中字典novel_data用于收集存储爬取到的小说名称/作者/小说地址等信息,start_url为爬虫最开始工作的网址。
def __init__(self):
self.novel_data = {}
self.start_url = "https://www.ddxsku.com/full.html"
self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36"}
小说信息采集
初始化基本的变量之后,我们开始对小说网站进行分析,大致思路是先收集小说基本信息(小说名称/作者/地址等),然后在根据返回的数据爬取小说内容,最后保存即可。
通过观察网站 https://www.ddxsku.com/full.html 我们发现小说还挺多,网页不止一页,于是想要爬取全站的完结小说的话,我们首先需要获取网站的页数,然后打入循环,循环遍历每一页收集小说数据。
通过查看元素我们发现,定义页数的元素是唯一的(Html中 ID属性值一般情况是唯一的),我们只需要通过简单的re正则表达式匹配提取即可。
start_response = requests.get(self.start_url, headers=self.headers)
total_page = re.search(r'<em id="pagestats">1/(.+)</em>', start_response.text).group(1)
当然了,在开始页数匹配之前别忘了打开网页。但是光有页数可不行(不然咋打开网址呢?),之后我们得分析url的变化规律。
通过简单的几页网址分析我们不难发现,除了参数page其余都是不变的,而page正是页数,所以我们现在定义一个列表来存储每一页小说导航的地址。
novel_navigation_urls = [fr"http://www.ddxsku.com/modules/article/articlelist.php?fullflag=1&page={i}" for i in range(1, int(total_page)+1)]
这里博主使用的是列表推导式,这里不展开多讲,有兴趣可以自行百度。当然了,你也可以直接使用for循环页数,将页数放到网址字符串相应的位置即可。
for page in range(1, int(total_page)+1):
url = fr"http://www.ddxsku.com/modules/article/articlelist.php?fullflag=1&page={page}"
pass
于此,我们便到了最令人激动的环节,逐页的打开小说导航网址,并收集小说基本内容。
通过分析刚才打开的网页元素我们发现,小说的目录页网址是存储在一个有规律的元素下的,博主依旧打算使用正则表达式来匹配。
novel_index_urls = re.findall(r'<td class="L"><a href="(.+)" title=".+" target="_blank">.+</a></td>', novel_navigation_response.text)
收集到小说目录页的网址,我们再逐项打开小说目录页网址,小说的名称/作者/每一章的标题与网址即可。
for novel_index_url in novel_index_urls:
novel_index_response = requests.get(novel_index_url, headers=self.headers)
novel_index_response.encoding = "utf-8"
novel_name = re.search(fr'.+<a href="http://www.ddxsku.com/xiaoshuo/\d+\.html">(.+)</a>.+', novel_index_response.text).group(1)
novel_author = re.search(r'<dd><h3>作者:(.+)</h3><br>.+</h3></dd>', novel_index_response.text).group(1)
self.novel_data = {novel_name: [("novel_author", novel_author)]}
print("Collecting novel: 《%s》--%s" % (novel_name, novel_author))
index_soup = BeautifulSoup(novel_index_response.text, "html.parser")
novel_text_urls = index_soup.find_all("td", class_="L")
for each in novel_text_urls:
chapters_title = each.text
chapters_url = each.a["href"]
self.novel_data[novel_name].append((chapters_title, chapters_url))
sleep(1)
收集的方法与代码跟之前的大同小异,大多的都是使用的正则表达式匹配的(这足以见得正则表达式对于一个合格的爬客是多那么的重要!)。
独有收集小说章节标题与网址不同,博主这里使用的是bs4库中的Beautifulsoup进行匹配章节标题与网址的。这同样见得,Beautifulsoup也是作为一个合格的爬客所必须掌握的。这里不做多讲,具体用法,请自行百度(或许博主有时间会写一篇关于这两门技术的文章)。
至于novel_index_response.encoding = "utf-8"的作用,则是将网页转换为utf-8编码,避免带来由于编码带来的不变要的麻烦(我们需要提取的存在中文字符)
并且博主是将收集到的数据以字典的形式存储小说信息,以小说名称为健,小说作者与章节名称/网址作为值,其中章节名称与网址以元组(章节名称,章节地址)的形式存储。
小说内容下载
通过上面的代码,我们已经能够收集小说的一些基本信息,接下来我们就只需要下载保存即可。
为了方便查看我们下载到的小说,我们需要更改一下Python的运行工作路径。
for name in self.novel_data:
count = 0
print("Downloading: 《%s》" % name, end="\n"*2)
work_path = r"C:/Users/Administrator/Desktop/NovelCopy/%s-%s" % (name, self.novel_data[name][0][1])
if not os.path.exists(work_path):
os.makedirs(work_path)
os.chdir(work_path)
else:
os.chdir(work_path)
对于self.novel_dataname[1],不知道客官可否还记得之前我们的小说数据保存形式
所以你大概知道self.novel_dataname[1]索引的结果是什么了吧?
需要下载小说自然的,我们需要获取小说的文本内容。通过查看元素我们知道,文本内容都是保存在ID为contents的元素下的,为了能够简单快速的获取文本,博主这里选择的是使用Beautifulsoup来定位元素,然后通过方法 text 获取文本即可。
而至于count的作用,则是方便小说文本文件的排序(鬼晓得有没有懒作者连第几章都不写呢?)
for chapter_data in self.novel_data[name][1:]:
count += 1
chapter_response = requests.get(chapter_data[1], headers=self.headers)
chapter_response.encoding = "utf-8"
chapter_soup = BeautifulSoup(chapter_response.text, "html.parser")
chapter_text = chapter_soup.find("dd", id="contents")
with open("%d-%s.txt" % (count, chapter_data[0]), "w", encoding="utf-8") as f:
f.write(chapter_text.text)
结语
至此,一篇简单的小说网站爬取分析就算是结束了。由于博主之前少有写关于过程分析的,都是闷声自己干,所以写得不好之处还望见谅,有建议请在下方评论区留言,当然有问题也欢迎留言哦!
5 comments
博主太厉害了!
叼茂SEO.bfbikes.com
看一看
看看
我就来看看不说话。