python爬虫学习笔记1简易小说爬虫

学了 python 语法之后在 b 站搜索练手的小项目,发现了这个视频:Python 实用练手小项目(超简单)

视频里面讲解了一个爬取图片网站图片的小爬虫。后面用到了我还没学的数据库,不过前面的部分是已经学了的,于是我就打算写一个不用数据库的,爬取某个盗版小说内容的爬虫。

声明:本人不会将得到的小说内容作任何商业用途,也请阅读此文章的各位读者遵纪守法,此文章只用作学习交流,原创内容,转载请注明出处。

项目描述

爬虫,在我理解中就是模拟人的浏览行为来获取网站上的信息的脚本,爬虫能得到的信息,一般情况下人也有权限可以得到。

盗版小说网站,不需要登录就可以看到小说内容,内容是写死在 html 文件里面的,通过右键菜单的查看源代码就能够查看到小说内容,很适合拿来练手。

再次声明:本人不会将得到的小说内容作任何商业用途,也请阅读此文章的各位读者遵纪守法,此文章只用作学习交流,原创内容,转载请注明出处。

思路

爬虫的思路是向服务器发出请求,并收到服务器回复的数据,接着从获取的数据中取得想要的信息,保存在数据库中。

由于是小说,就直接保存在文本文件当中。

所以分为以下几步:

  1. 发出请求
  2. 接收数据
  3. 提取信息
  4. 保存数据

编程原理

发出请求和接收数据

发出请求需要一个库,名字叫做requests,它是基于 python 自带的urllib库写的第三方库,差不多就是升级版的意思吧。

要注意是requests,不是request,结尾有个 s,确实存在一个不带 s 的库,注意区分。

可以使用下面的命令进行安装:

1
pip install requests

pip 是 Python 包管理工具,总之有了这个玩意,你不用管它从哪里下载,在哪里安装,总之就告诉它要安装啥,它就帮你安排得明明白白的。以后会遇到很多这样的东西,比如 npm 啥的。

命令在 cmd 里面输就行了,如果电脑上没有这东西就百度一下怎么下载,一般来说安装了 python 应该就有了。

如果使用的是 pyCharm 这种 IDE,那就可以直接在代码 import 这个库,等库的名字变红再在旁边找安装按钮,很方便的。

这个库里面有个 get 函数,是采用 get 的方式(除此之外还有 post 方式,学 html 表单的时候应该有学到)来向服务器发出访问请求,并将获得的数据作为返回值。

1
2
3
4
import requests
#省略代码
r = requests.get(url)#url是你要访问的网址
print(r)#如果输出是<Response [200]>,那么就是访问成功了

此时返回变量是请求对象,要从中获取数据,就需要使用它的两个属性textcontent

r.text是数据的 html 形式,r.content是字节流的形式。二者的区别

前者返回文本格式(即二进制格式经过编码后),后者返回二进制格式。后者一般用于图片的保存。

我们需要获取的是文本内容,因此需要前者。

1
html=r.text

提取信息

我们打开笔趣阁(一个盗版小说网站)的一个小说页面,随便选一章点进去,查看源代码,发现小说的内容是放在一个<div>里面的:

1
2
3
4
<div class="content" id="booktext">
小说内容
<center>翻页信息</center>
</div>

其他章节也是如此,所以就可以利用这个规律将其提取出来,用的就是正则表达式。

正则表达式

使用正则表达式需要使用一个内置的库re,根据上面的规律可以写出下面的正则表达式:

1
2
3
4
5
6
7
8
9
import re
reg = r'<div class="content" id="booktext">(.*?)<center>'#正则表达式
reg = re.compile(reg)#将字符串转换为正则表达式对象,加快匹配速度
content= re.findall(reg, html)#返回一个列表,列表项为匹配到的内容

if content==[]:#未匹配到小说内容
print("获取失败!")
else:
content=str(content[0])#将列表转换为字符串

re.compile()函数

编码转换

但是我写到这里的时候遇到了一个问题,就是获取到的内容是乱码。一看到乱码就应该想到是编码出了问题。

右键菜单查看网页编码,是GBK编码,需要转换编码。现在的情况是,网页利用GBK的编码来“加密”了小说文本,而我们需要用同样的方式来“解码”。需要用到decode函数

1
html=r.content.decode("GBK", "ignore")#转换编码

将获得的二进制数据按照网页原本的编码GBK来解码,就能获取到正确的内容了。

去除分隔字符

此时提取到的内容还有这很多 HTML 实体,比如&nbsp;<br />,注意到它们的分布也有规律:

1
2
3
4
<div class="content" id="booktext">
&nbsp;&nbsp;&nbsp;&nbsp;小说内容<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;小说内容<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;……省略……<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;大雪落下,悄然覆盖着这一切。<br />
<center></center>
</div>

除了开头和结尾之外,都是以<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;进行分隔的。

可以利用split()函数将其分割之后重新组合,

也可以使用字符串的替换函数replace()

1
content=content.replace("<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;","\n\n    ")

保存数据

保存在文本文件中就 ok 了:

1
2
3
with open(fileName,'w') as fout:#fileName为保存路径加文件名
fout.write('\n\n=====================\n\n' + fileName + '\n\n=====================\n\n')
fout.write(content)

获取单章节内容代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests
import re
import os
def getNovelByURL(url,fileName):
'''
:param url: 网页的url
:param fileName: 保存数据的文件的名字
:return: -1为失败,0为成功
'''
#筛选文件名内非法字符
#调试的时候前面几百章都行突然一章不行,发现是因为章节名字里面有非法字符
reg=r'[\/:*?"<>|]'
fileName=re.sub(reg,"",fileName)#利用正则表达式去除非法字符

# 获取网页
r = requests.get(url)
html = r.content
html = html.decode("GBK", "ignore")
# 获取网页中小说内容
reg = '<div class="content" id="booktext">\n&nbsp;&nbsp;&nbsp;&nbsp;(.*?)<br />\n<center>'
reg = re.compile(reg)#预编译
content = re.findall(reg, html)

#保存到文件
if content==[]:
print("获取失败!")
return -1
else:
content=str(content[0])#转换为字符串
content=content.replace("<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;","\n\n ")

with open(fileName,'w') as fout:
fout.write('\n\n=====================\n\n' + fileName + '\n\n=====================\n\n')
fout.write(content)

print("成功爬取({}),存储在{}".format(url,os.path.dirname(__file__)+'/'+fileName))
return 0

获取全部章节内容的思路

盗版小说网站章节的 url 有个规律,就是 url 的最后一串数字是连续的,照这个规律,知道第一章的 url,就可以获得后续章节的 url。于是我着手写这么个函数:

1
2
3
4
5
6
7
8
9
def getNovelByIndexInc(url, number=1):
'''
此函数用于通过已知的起始url来获取仅有尾部索引不同且连续的一系列网页内的小说,
不连续时会跳过获取失败的网址,不过有可能连续几千个网址都是无效网址,所以慎用此函数
或改用getNovelByContentPage函数
:param url:起始章节的url
:param number: 要获取的章节数
:return:无
'''

从我写的注释里面也可以看出,我失败了。

一开始的一百多章还是没什么问题的,只有偶尔几个网址是无效网址,但是后面爬取的时候等了十分钟还没爬取到下一章,一直输出“无效网址”,我查看了那断片的两个连续章节之后才发现,最后的一串数字差了几万。不会是因为作者断更吧!

这种方式不可靠,还是换一种方式。

那么要如何可以改进呢?

我写了另一个函数:

1
2
3
4
5
6
7
def getNovelByContentPage(url,path='novel'):
'''
通过获取目录页面链接与标题,进一步调用获取已知链接页面的函数来保存页面内容
:param url: 书籍目录页面
:param path:保存路径,默认为同目录下的novel文件夹
:return:-1为失败,0为成功
'''

网站的书籍页面会有一个目录,而目录下隐藏的就是我需要的全部章节的链接呀!

这个函数用到的内容上面也都讲到了,就直接放代码吧。

获取全部章节内容的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
import re
import os
def getNovelByContentPage(url,path='novel'):
'''
通过获取目录页面链接与标题,进一步调用获取已知链接页面的函数来保存页面内容
:param url: 书籍目录页面
:param path:保存路径,默认为同目录下的novel文件夹
:return:-1为失败,0为成功
'''

# 获取网页
r = requests.get(url)
html = r.content#获取网页二进制内容
html = html.decode("GBK", "ignore")#转换编码
# 获取网页中小说内容
reg = '<dd><a href="(.*?)" title="(.*?)">.*?</a></dd>'#获取链接和标题
reg = re.compile(reg, re.S)
info= re.findall(reg, html)
#由于是分组匹配,得到的列表中每个元素的[0]是链接,[1]是标题
#保存到文件
if info==[]:
print("获取章节目录失败")
return -1
else:
if not os.path.exists(path):#检查目录是否已经存在
os.makedirs(path)
for i in info:
realpath=path+"\\"+i[1]+".txt"
if os.path.exists(realpath):#避免重复爬取
continue
else:
getNovelByURL(i[0],realpath)#调用获取单页面内容的函数

return 0

作者

憧憬少

发布于

2019-02-08

更新于

2019-02-08

许可协议