前言
学了python语法之后在b站搜索练手的小项目,发现了这个视频:Python实用练手小项目(超简单)
视频里面讲解了一个爬取图片网站图片的小爬虫。后面用到了我还没学的数据库,不过前面的部分是已经学了的,于是我就打算写一个不用数据库的,爬取某个盗版小说内容的爬虫。
声明:本人不会将得到的小说内容作任何商业用途,也请阅读此文章的各位读者遵纪守法,此文章只用作学习交流,原创内容,转载请注明出处。
项目描述
爬虫,在我理解中就是模拟人的浏览行为来获取网站上的信息的脚本,爬虫能得到的信息,一般情况下人也有权限可以得到。
盗版小说网站,不需要登录就可以看到小说内容,内容是写死在html文件里面的,通过右键菜单的查看源代码
就能够查看到小说内容,很适合拿来练手。
再次声明:本人不会将得到的小说内容作任何商业用途,也请阅读此文章的各位读者遵纪守法,此文章只用作学习交流,原创内容,转载请注明出处。
思路
爬虫的思路是向服务器发出请求,并收到服务器回复的数据,接着从获取的数据中取得想要的信息,保存在数据库中。
由于是小说,就直接保存在文本文件当中。
所以分为以下几步:
- 发出请求
- 接收数据
- 提取信息
- 保存数据
编程原理
发出请求和接收数据
发出请求需要一个库,名字叫做requests
,它是基于python自带的urllib
库写的第三方库,差不多就是升级版的意思吧。
要注意是requests
,不是request
,结尾有个s,确实存在一个不带s的库,注意区分。
可以使用下面的命令进行安装:
pip install requests
pip 是 Python 包管理工具,总之有了这个玩意,你不用管它从哪里下载,在哪里安装,总之就告诉它要安装啥,它就帮你安排得明明白白的。以后会遇到很多这样的东西,比如npm啥的。
命令在cmd里面输就行了,如果电脑上没有这东西就百度一下怎么下载,一般来说安装了python应该就有了。
如果使用的是pyCharm这种IDE,那就可以直接在代码import这个库,等库的名字变红再在旁边找安装按钮,很方便的。
这个库里面有个get函数,是采用get的方式(除此之外还有post方式,学html表单的时候应该有学到)来向服务器发出访问请求,并将获得的数据作为返回值。
import requests
#省略代码
r = requests.get(url)#url是你要访问的网址
print(r)#如果输出是<Response [200]>,那么就是访问成功了
此时返回变量是请求对象,要从中获取数据,就需要使用它的两个属性text
和content
r.text
是数据的html形式,r.content
是字节流的形式。二者的区别
前者返回文本格式(即二进制格式经过编码后),后者返回二进制格式。后者一般用于图片的保存。
我们需要获取的是文本内容,因此需要前者。
html=r.text
提取信息
我们打开笔趣阁(一个盗版小说网站)的一个小说页面,随便选一章点进去,查看源代码,发现小说的内容是放在一个<div>
里面的:
<div class="content" id="booktext">小说内容<center>翻页信息</center></div>
其他章节也是如此,所以就可以利用这个规律将其提取出来,用的就是正则表达式。
正则表达式
使用正则表达式需要使用一个内置的库re
,根据上面的规律可以写出下面的正则表达式:
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])#将列表转换为字符串
编码转换
但是我写到这里的时候遇到了一个问题,就是获取到的内容是乱码。一看到乱码就应该想到是编码出了问题。
右键菜单查看网页编码,是GBK
编码,需要转换编码。现在的情况是,网页利用GBK
的编码来“加密”了小说文本,而我们需要用同样的方式来“解码”。需要用到decode
函数
html=r.content.decode("GBK", "ignore")#转换编码
将获得的二进制数据按照网页原本的编码GBK
来解码,就能获取到正确的内容了。
去除分隔字符
此时提取到的内容还有这很多HTML实体,比如
和<br />
,注意到它们的分布也有规律:
<div class="content" id="booktext">
小说内容<br /><br /> 小说内容<br /><br /> ……省略……<br /><br /> 大雪落下,悄然覆盖着这一切。<br />
<center>
除了开头和结尾之外,都是以<br /><br />
进行分隔的。
可以利用split()
函数将其分割之后重新组合,
也可以使用字符串的替换函数replace()
content=content.replace("<br /><br /> ","\n\n ")
保存数据
保存在文本文件中就ok了:
with open(fileName,'w') as fout:#fileName为保存路径加文件名
fout.write('\n\n=====================\n\n' + fileName + '\n\n=====================\n\n')
fout.write(content)
获取单章节内容代码
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 (.*?)<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 /> ","\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。于是我着手写这么个函数:
def getNovelByIndexInc(url, number=1):
'''
此函数用于通过已知的起始url来获取仅有尾部索引不同且连续的一系列网页内的小说,
不连续时会跳过获取失败的网址,不过有可能连续几千个网址都是无效网址,所以慎用此函数
或改用getNovelByContentPage函数
:param url:起始章节的url
:param number: 要获取的章节数
:return:无
'''
从我写的注释里面也可以看出,我失败了。
一开始的一百多章还是没什么问题的,只有偶尔几个网址是无效网址,但是后面爬取的时候等了十分钟还没爬取到下一章,一直输出“无效网址”,我查看了那断片的两个连续章节之后才发现,最后的一串数字差了几万。不会是因为作者断更吧!
这种方式不可靠,还是换一种方式。
那么要如何可以改进呢?
我写了另一个函数:
def getNovelByContentPage(url,path='novel'):
'''
通过获取目录页面链接与标题,进一步调用获取已知链接页面的函数来保存页面内容
:param url: 书籍目录页面
:param path:保存路径,默认为同目录下的novel文件夹
:return:-1为失败,0为成功
'''
网站的书籍页面会有一个目录,而目录下隐藏的就是我需要的全部章节的链接呀!
这个函数用到的内容上面也都讲到了,就直接放代码吧。
获取全部章节内容的代码
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