python爬虫实战(1)——网站小说
整本小说的爬取保存
- 目标
- 大致思路
- 页面的爬取解析—XPath
- 请求网页内容
- 解析网页内容
- 正文爬取与解析
- 单个页面数据获取
- 爬取所有页面
- 数据清洗
经过学习基础,我们学以致用一下子,爬取小说,注意这个小说本身是免费的哦,以后再进阶。
本次为实战记录,笔者很多碎碎念可忽略
目标
基于requests库和lxml中的xpath编写的爬虫,目标小说网站域名http://www.365kk.cc/
这是网上找的,小说网址很多,而且没有很多反扒机制,新手友好!
大致思路
其实也可以不写,梳理一下吧,虽然是空话
主要分三步(如同大象进冰箱hh)
- 获取网页内容,通过requests库实现;
- 解析网页内容,得到其中我们想要的部分,通过xpath实现;
- 将解析出的内容储存到文本文档中;
我们把要做的具体化,首先选取一本自己喜欢的小说,笔者以此为例,是的就是这么重口。
《我是一具尸体》

页面的爬取解析—XPath
要干嘛明确一下:
- 获取目标书籍的基本信息,包括书籍的书名、作者、简介——这些信息应该都在同一个页面中获取,即上面展示的页面;
- 获取目标书籍每一章节的标题和内容——不同章节在不同的页面,不同页面之间可以通过下一页按序跳转;
- 正文部分的存储格式应便于阅读,不能把所有文字都堆积在一起,也不能包括除了正文之外的其他无关内容;
因此,我们首先尝试请求书籍的主页,获取基本信息;紧接着再从书籍的第一章开始,不断地请求“下一页”,直到爬取整本书,并将它们以合适的格式储存在文本文档中。
笔者是用Google Chrome,在首页点击右键 —> 检查:

可以看出,浏览器下方弹出了一个窗口,这里显示的就是该页面的源代码,我们选中的内容位于一个<h1>标签中。点击右键 -> 复制 -> 复制 XPath,即可得到书名的XPath路径,也就是书名在网页中的位置。
书名:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1
从书籍的首页中,同理我们可以获取的信息主要包括:
- 书名 作者 最后更新时间
作者:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]
最后更新时间:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]
简介:/html/body/div[4]/div[1]/div/div/div[2]/div[2]
请求网页内容
使用基础的python爬虫网页请求方法: requests 库直接请求。
在请求网页时,我们需要将我们的爬虫伪装成浏览器,具体通过添加请求头 headers 实现。
请求头以字典的形式创建,可以包括很多内容,这里只设置四个字段:User-Agent, Cookie, Host 和 Conection。
在刚才打开的页面中,点击 网络(英文版是Network),刷新页面,找到其中的第一个文件 1053/ ,打开 标头 -> 请求标头 ,即可得到想要的字段数据。

import requests# 请求头,添加你的浏览器信息后才可以正常运行
headers= {'User-Agent': '...','Cookie': '...','Host': 'www.365kk.cc','Connection': 'keep-alive'
}
# 小说主页
main_url = "http://www.365kk.cc/1/1053/"
# 使用get方法请求网页
main_resp = requests.get(main_url, headers=headers)
# 将网页内容按utf-8规范解码为文本形式
main_text = main_resp.content.decode('utf-8')
print(main_text)
可以看出,我们成功请求到了网站内容,接下来只需对其进行解析,即可得到我们想要的部分。
解析网页内容
我们使用 lxml 库来解析网页内容,具体方法为将文本形式的网页内容创建为可解析的元素,再按照XPath路径访问其中的内容,代码如下:
import requests
from lxml import etree# 请求头
headers= {'User-Agent': '...','Cookie': '...','Host': 'www.365kk.cc','Connection': 'keep-alive'
}# 小说主页
main_url = "http://www.365kk.cc/1/1053/"
# 使用get方法请求网页
main_resp = requests.get(main_url, headers=headers)
# 将网页内容按utf-8规范解码为文本形式
main_text = main_resp.content.decode('utf-8')# 将文本内容创建为可解析元素
main_html = etree.HTML(main_text)# 依次获取书籍的标题、作者、最近更新时间
# main_html.xpath返回的是列表,因此需要加一个[0]来表示列表中的首个元素
# /text() 表示获取文本
bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]
# 输出结果以验证
print(bookTitle)
print(author)
print(update)
print(introduction)
输出结果如下:

至此,使用基本的网页请求get方法获取目标页面中的特定内容结束,接下来就是正文解析了,
go go go
正文爬取与解析
开始爬取正文。首先尝试获取单个页面的数据,再尝试设计一个循环,依次获取所有正文数据
单个页面数据获取
打开第一章,获取章节标题和正文的XPath路径如下:

书名://*[@id="container"]/div/div/div[2]/h1
内容://*[@id="content"]
按照与上文一致的方法请求并解析网页内容,代码如下:
注意到在用这个xpath路径的时候我们是只要里面的文字部分所以要多加一个/text()
import requests
from lxml import etree# 请求头
headers= {'User-Agent': '...','Cookie': '...','Host': 'www.365kk.cc','Connection': 'keep-alive'
}# 当前页面链接
url = 'http://www.365kk.cc/1/1053/10094192.html'
resp = requests.get(url, headers)
text = resp.content.decode('utf-8')html = etree.HTML(text)title = html.xpath('//*[@id="container"]/div/div/div[2]/h1/text()')[0]
contents = html.xpath('//*[@id="content"]/text()')print(title)
for content in contents:print(content)
可以看出,我们成功获取了小说第一章第一页的标题和正文部分

接下来我们将它储存在一个txt文本文档中,关于文件读取类型主要有(并不全,只是本代码用到的)
'w': 清空原文档,重新写入文档 open(filename, ‘w’)'r': 仅读取文档,不改变其内容 open(filename, ‘r’)'a': 在原文档之后追加内容 open(filename, ‘a’)
文档命名为之前获取的书名 bookTitle.txt,完整的代码如下:
import requests
from lxml import etree# 请求头
headers= {'User-Agent': '...','Cookie': '...','Host': 'www.365kk.cc','Connection': 'keep-alive'
}# 小说主页
main_url = "http://www.365kk.cc/1/1053/"# 使用get方法请求网页
main_resp = requests.get(main_url, headers=headers)# 将网页内容按utf-8规范解码为文本形式
main_text = main_resp.content.decode('utf-8')# 将文本内容创建为可解析元素
main_html = etree.HTML(main_text)bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]# 第一章页面链接
url = 'http://www.365kk.cc/1/1053/10094192.html'resp = requests.get(url, headers)
text = resp.content.decode('utf-8')
html = etree.HTML(text)
title = html.xpath('//*[@id="container"]/div/div/div[2]/h1/text()')[0]
contents = html.xpath('//*[@id="content"]/text()')with open(bookTitle + '.txt', 'w', encoding='utf-8') as f:f.write(title)for content in contents:f.write(content)# 在储存文件时,每储存一段,就写入两个换行符 `\n`,避免大段文字堆积使格式更便于阅读f.write('\n\n')f.close()
运行结束后,可以看到在代码文件的同路径中,已经生成了一个文本文档。至此,我们已经完成了单个页面的数据爬取和存储,接下来只要设计循环,实现顺序爬取所有页面即可。

爬取所有页面
我们注意到,正文的每个页面底部,都有一个按钮下一页,其在网页中的结构为:

在XPath路径的末尾添加 @href 用于获取属性 href 的值:
//*[@id="container"]/div/div/div[2]/div[3]/a[3]/@href
注意到如果一章结束,下一页和下一章的链接就会有些差别

观察不同页面的链接,可以看出前缀是一致的,区别仅在后缀上,比如第一章第一页和第一章第二页的链接分别为:
http://www.365kk.cc/1/1053/10094192.html
http://www.365kk.cc/1/1053/10094192_2.html
因此,我们只需要获取下一页的链接后缀,再与前缀拼接,即可获得完整的访问链接。代码如下:
# 获取下一页链接的函数
def next_url(next_url_element):nxturl = 'http://www.365kk.cc/1/1053/'# rfind('/') 获取最后一个'/'字符的索引index = next_url_element.rfind('/') + 1nxturl += next_url_element[index:]return nxturlurl1 = '/1/1053/10094192_2.html'
url2 = '10094193.html'print(next_url(url1))
print(next_url(url2))
在爬取某一页面的内容后,我们获取下一页的链接,并请求该链接指向的网页,重复这一过程直到全部爬取完毕为止,即可实现正文的爬取。

在这一过程中,需要注意的问题有:
某一章节的内容可能分布在多个页面中,每个页面的章节标题是一致的,这一标题只需存储一次;
请求网页内容的频率不宜过高,频繁地使用同一IP地址请求网页,会触发站点的反爬虫机制,禁止你的IP继续访问网站;
爬取一次全文耗时较长,为了便于测试,我们需要先尝试爬取少量内容,代码调试完成后再爬取全文;
爬取的起点为第一章第一页,爬取的终点可以自行设置;
按照上述思想,爬取前4个页面作为测试,完整的代码如下:
import requests
from lxml import etree
import time
import random# 获取下一页链接的函数
def next_url(next_url_element):nxturl = 'http://www.365kk.cc/1/1053/'# rfind('/') 获取最后一个'/'字符的索引index = next_url_element.rfind('/') + 1nxturl += next_url_element[index:]return nxturl# 请求头,需要添加你的浏览器信息才可以运行
headers= {'User-Agent': '...','Cookie': '...','Host': 'www.365kk.cc','Connection': 'keep-alive'
}# 小说主页
main_url = "http://www.365kk.cc/1/1053/"# 使用get方法请求网页
main_resp = requests.get(main_url, headers=headers)# 将网页内容按utf-8规范解码为文本形式
main_text = main_resp.content.decode('utf-8')# 将文本内容创建为可解析元素
main_html = etree.HTML(main_text)bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]# 调试期间仅爬取4个页面
maxPages = 4
cnt = 0
# 记录上一章节的标题
lastTitle = ''# 爬取起点
url = 'http://www.365kk.cc/1/1053/10094192.html'
# 爬取终点
endurl = 'http://www.365kk.cc/1/1053/10094194.html'while url != endurl:cnt += 1 # 记录当前爬取的页面if cnt > maxPages:break # 当爬取的页面数超过maxPages时停止resp = requests.get(url, headers)text = resp.content.decode('utf-8')html = etree.HTML(text)title = html.xpath('//*[@class="title"]/text()')[0]contents = html.xpath('//*[@id="content"]/text()')# 输出爬取进度信息print("cnt: {}, title = {}, url = {}".format(cnt, title, url))with open(bookTitle + '.txt', 'a', encoding='utf-8') as f:if title != lastTitle: # 章节标题改变f.write(title) # 写入新的章节标题lastTitle = title # 更新章节标题for content in contents:f.write(content)f.write('\n\n')f.close()# 获取"下一页"按钮指向的链接next_url_element = html.xpath('//*[@class="section-opt m-bottom-opt"]/a[3]/@href')[0]# 传入函数next_url得到下一页链接url = next_url(next_url_element)sleepTime = random.randint(2, 5) # 产生一个2~5之间的随机数time.sleep(sleepTime) # 暂停2~5之间随机的秒数print("complete!")
运行结果如下:

数据清洗
观察我们得到的文本文档,可以发现如下问题:
- 缺乏书籍信息,如之前获取的书名、作者、最后更新时间;
- 切换页面时,尤其是同一章节的不同页面之间空行过多;
- 每章节第一段缩进与其他段落不一致;
- 不同章节之间缺乏显眼的分隔符;
为了解决这些问题,我们编写一个函数 clean_data() 来实现数据清洗,清洗后的文档中,每段段首无缩进,段与段之间仅空一行,不同章节之间插入20个字符 - 用以区分,问题得以解决。
完整代码,需要自取哦
小说爬虫
相关文章:
python爬虫实战(1)——网站小说
整本小说的爬取保存 目标大致思路页面的爬取解析—XPath请求网页内容解析网页内容正文爬取与解析单个页面数据获取爬取所有页面 数据清洗 经过学习基础,我们学以致用一下子,爬取小说,注意这个小说本身是免费的哦,以后再进阶。 本次…...
git: ‘lfs‘ is not a git command. see ‘git --help‘
在克隆hugging face里面的项目文件的时候,需要用到git lfs,本文介绍安装git lfs方法 在Ubuntu下 curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash sudo apt-get install git-lfs在Windows下 到这个链…...
python案例
这猜单词游戏。具体步骤如下: 导入random模块,用于随机选择单词。 设置初始生命次数为3。 创建一个单词列表words,其中包含了一些单词。 使用random.choices()函数从单词列表中随机选择一个单词作为秘密单词secret_word。 创建一个clue列表&a…...
leetcode 63. 不同路径 II
2023.8.9 这题是不同路径I的升级版,在路径上增加了障碍物,有障碍物的地方无法通过。 我的思路依然还是使用动态规划,dp[i][j]的含义依然是到(i,j)这个位置的路径个数。只需要在dp数组中将有障碍物的地方赋为…...
c语言每日一练(5)
前言:每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,暑假时三天之内必有一更,到了开学之后,将看学业情…...
pycharm配置conda虚拟环境
📕作者简介:热编程的贝贝,致力于C/C、Java、Python等多编程语言,热爱跑步健身,喜爱音乐的一位博主。 📗本文收录于贝贝的日常汇报系列,大家有兴趣的可以看一看 📘相关专栏深度学习、…...
ubuntu 如何命令行打开系统设置(Wifi,网络,应用程序...)
关于GNOME GNOME 是一个自由、开放源代码的桌面环境,它运行在 Linux 和其他类 UNIX 操作系统上。它是 GNU 项目的一部分,旨在为 Linux 操作系统提供一个现代化、易于使用的用户界面。 GNOME 桌面环境包括许多应用程序,例如文件管理器、文本编…...
MySQL DQL 数据查询
文章目录 1.SELECT 语句2.SELECT 子句3.FROM 子句4.WHERE 子句5.GROUP BY 子句6.HAVING 子句7.ORDER BY 子句8.LIMIT 子句9.DISTINCT 子句10.JOIN 子句11.UNION 子句12.查看数据表记录数13.检查查询语句的执行效率14.查看 SQL 执行时的警告参考文献 1.SELECT 语句 MySQL 的 SE…...
深度学习基础知识笔记
深度学习要解决的问题 1 深度学习要解决的问题2 应用领域3 计算机视觉任务4 视觉任务中遇到的问题5 得分函数6 损失函数7 前向传播整体流程8 返向传播计算方法1 梯度下降 9 神经网络整体架构11 神经元个数对结果的影响12 正则化和激活函数1 正则化2 激活函数 13 神经网络过拟合…...
怎么系统的学习机器学习、深度学习?当然是看书了
目录 前言 内容简介 学完本书,你将能够 作者简介 本书目录 京东自购链接 前言 近年来,机器学习方法凭借其理解海量数据和自主决策的能力,已在医疗保健、 机器人、生物学、物理学、大众消费和互联网服务等行业得到了广泛的应用。自从Ale…...
无涯教程-Perl - binmode函数
描述 此函数设置在区分两者的操作系统上以二进制形式读取和写入FILEHANDLE的格式。非二进制文件的CR LF序列在输入时转换为LF,在LF时在输出时转换为CR LF。这对于使用两个字符分隔文本文件中的行的操作系统(MS-DOS)至关重要,但对使用单个字符的操作系统(Unix,Mac OS,QNX)没有影…...
Spring Boot Maven package时显式的跳过test内容
在pom.xml的编译插件部分显式的增加一段内容: <plugin> <!-- maven打包时,显式的跳过test部分 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.…...
排序算法————基数排序(RadixSort)
基数排序的概念: 什么是基数排序???基数排序是一种和快排、归并、希尔等等不一样的排序...它不需要比较和移动就可以完成整型的排序。它是时间复杂度是O(K*N),空间复杂度是O(KM&…...
leetcode做题笔记75颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sort 函数的情况下解决…...
聊一下互联网开源变现
(点击即可收听) 互联网开源变现其实是指通过开源软件或者开放源代码的方式,实现收益或盈利。这种方式越来越被广泛应用于互联网行业 在互联网开源变现的模式中,最常见的方式是通过捐款、广告、付费支持或者授权等方式获利。 例如,有些开源软件…...
PHP日期差计算器,计算两个时间相差 年/月/日
1. 计算两个日期相隔多少年,多少月,多少天示例:laravel框架实现 /*** 天数计算* return \Illuminate\Http\JsonResponse*/public function loveDateCal(){$start_date $this->request(start_date);$end_date $this->request(end_date…...
20230812在WIN10下使用python3将SRT格式的字幕转换为SSA格式
20230812在WIN10下使用python3将SRT格式的字幕转换为SSA格式 2023/8/12 20:58 本文的SSA格式以【Batch Subtitles Converter(批量字幕转换) v1.23】的格式为准! 1、 缘起:网上找到的各种各样的字幕转换软件/小工具都不是让自己完全满意! 【都…...
matlab使用教程(13)—稀疏矩阵创建和使用
使用稀疏矩阵存储包含众多零值元素的数据,可以节省大量内存并加快该数据的处理速度。sparse 是一种属性,可以将该属性分配给由 double 或 logical 元素组成的任何二维 MATLAB 矩阵。通过 sparse 属性,MATLAB 可以: • 仅存储矩…...
UI美工设计的主要职责(合集)
UI美工设计的主要职责1 职责: 1、执行公司的规章制度及专业管理办法; 2、 负责重点项目的原型设计和产品流程设计、视觉设计,优化网站和移动端的设计流程和规范,制定产品 UI/UE规范及文档编写; 3、负责使用PS、AI、illustrator、MarkMan、…...
【前端二次开发框架关于关闭eslint】
前端二次开发框架关于关闭eslint 方法一方法二方法三方法四:以下是若想要关闭项目中的部分代码时: 方法一 在vue.config.js里面进行配置: module.exports {lintOnSave:false,//是否开启eslint保存检测 ,它的有效值为 true || false || err…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
