《新笔趣阁》之全站爬虫
身为十年老书虫,学习爬虫不爬取全站小说网都对不起这个身份。我相信,对于很多人来说,小说是人的第二精神世界,是某些时刻精神慰问的佳肴。小编最喜欢的作者是实验小白鼠的作品,鼠大的作品主打玄幻和都市,若碰巧遇到鼠哥的粉丝可私信闲聊,哈哈。扯远了,回归正题,这次对《新笔趣阁》全站爬取小说并存入txt文件!!!
操作环境: Windows10、Python3.6、Pycharm、谷歌浏览器
目标网址: http://www.xbiquge.la/xiaoshuodaquan/ (新笔趣阁)
爬虫目录
================================================
1、分析网页
首先对目标网站进行分析你需要爬取的内容。
从图片可看出此属性mian标签下包含了六对同等级标签,经过分析了解clear属性的div标签是空白符,然而novellist属性的div标签才是包含各类小说信息的主要标签。每本小说对应着一个 li 标签,本站共有3000本小说。如下图:
2、编码方式
网页的编码方式品种多样,但主流还是utf-8,gbk,gb2312等等,如果源网页是GBK字节流,在程序端接收时的inputstream得到的字节数组的编码方式肯定是GBK字节流,即源网页是什么编码方式的字节流,程序端接收到的字节流的编码方式肯定是相同的。因此,只要在程序端解析出该流实际的编码方式即可将该流获得的源网页的字节数组转化成正常的编码显示形式。即算“解码–解析编码”的过程。
查看字节流编码的方式这里介绍两种:
一:通过F12或者右键点击检查进入开发者模式,进入控制台(console)下面输入document.charset,回车即可查看。如下图:
二:右键点击“查看网页源码”,在源码网页的最上面找到meta的charset来获得编码。如下图:
请求目的网站:
headers = { # 请求头,伪造身份
"Content-Encoding":"gzip",
"Content-Type":"text/html",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
}
# 目标链接
url = 'http://www.xbiquge.la/xiaoshuodaquan/'
response = requests.get(url=url,headers=headers) # 请求目的网址
print(response) # <Response [200]>
print(response.status_code) # 返回状态码 200
2.1、两种解码类型
一、response.text
类型: 返回str
作用: 主要用于网页字符串的编码
二、response.content
类型: 返回bytes
作用: 主要用于二进制数据类型,例:音频、视频、图片等
2.2、万能编码
自动识别网页的编码格式,但不建议使用这个。
response.encoding = response.apparent_encoding # 万能编码
2.3、指定编码
手动查看网页编码格式,并写入代码中,建议使用这种方法。
text = response.content.decode('utf-8') # 指定编码格式
print(text)
三种编码格式输出结果都是一致:
3、xpath提取数据
导入解析库parsel:import parsel #pip install 库名 (下载安装第三方库)
import random
import requests
import parsel
import os
import time
headers = { # 请求头,伪造身份
"Content-Encoding":"gzip",
"Content-Type":"text/html",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
}
# 目标链接
url = 'http://www.xbiquge.la/xiaoshuodaquan/'
response = requests.get(url=url,headers=headers) # 请求目的网址
text = response.content.decode('utf-8') # 指定编码格式
# parsel将网页源码转化为Selector对象,以便后续的语法提取数据
selector = parsel.Selector(text)
lis = selector.xpath('//div[@id="main"]/div[@class="novellist"]/ul/li')
print(lis)
代码执行结果为:
提取完整站小说的标签,遍历循环取出每一本小说详情链接。
selector = parsel.Selector(text)
lis = selector.xpath('//div[@id="main"]/div[@class="novellist"]/ul/li')
for li in lis: # 循环遍历取出li标签
title = li.xpath('./a/text()').extract_first() # 小说名
href_url = li.xpath('./a/@href').extract_first() # 小说详情链接
print(title,href_url)
代码执行结果一一对应,数据无误,如下图:
以小说名创建文件夹
format # 填充函数
os.path.exists() # 判断括号里的文件是否存在的意思,括号内的可以是文件路径。
os.makedirs() # 创建多层目录
注:为后续存入txt文件做路径铺垫
# 以小说名命名文件夹
path = r'D:\爬虫\小说\全站小说\{}'.format(title)
if not os.path.exists(path):
os.makedirs(path) # 创建多层目录
提取完每本小说的详情链接,接下来便请求它,继续进行下一步提取每本小说的基本信息。这里以第一本小说《牧神记》为例,如下图:
extract_first()和get()同等性质:获取一个(返回字符串数据)
extract()和getall()同等性质:获取全部(返回列表数据)
re.sub:正则替换
resp_href = requests.get(url=href_url,headers=headers,allow_redirects=False) # 请求小说详情链接
href_text = resp_href.content.decode('utf-8') # 详情页编码
select = parsel.Selector(href_text)
resp_href = requests.get(url=href_url,headers=headers,allow_redirects=False) # 请求小说详情链接
href_text = resp_href.content.decode('utf-8') # 详情页编码
select = parsel.Selector(href_text)
fiction_name = select.xpath('//div[@id="info"]/h1/text()').extract_first() # 小说名
author = select.xpath('//div[@id="info"]/p[1]/text()').extract_first() # 作者
author = re.sub(r'[作者:\t\s\n]','',author)
update_time = select.xpath('//div[@id="info"]/p[3]/text()').extract_first() # 最后更新时间
update_time = re.sub(r'[最后更新时间:\t\s\n]', '', update_time)
Introduction = select.xpath('//div[@id="intro"]/p[2]/text()').extract_first() # 简介
print('小说名:',fiction_name,'\n','作者:',author,'\n','最后更新时间:',update_time,'\n','简介:',Introduction,'\n')
代码执行部分结果为:
继续提取小说章节标题与链接,但获取到的章节链接只有下半部分,需要自行填充前半部分的内容。如下图:
代码如下:
resp_href = requests.get(url=href_url,headers=headers,allow_redirects=False) # 请求小说详情链接
href_text = resp_href.content.decode('utf-8') # 详情页编码
select = parsel.Selector(href_text)
dds = select.xpath('//div[@id="list"]/dl/dd') # 获取节点标签
for dd in dds:
chapter_title = dd.xpath('./a/text()').extract_first() # 小说章节名
detail_url = dd.xpath('./a/@href').extract_first() # 小说章节详情链接
detail_url = 'http://www.xbiquge.la' + detail_url # 拼接完整链接
print(chapter_title,detail_url)
代码执行结果如下:(判断其完善的链接是否成功,点开链接,能显示对应的内容则成功,反之链接填充失败。)
4、处理反爬
获取到小说章节详细链接,继续提取小说内容,写完提取规则后运行发现中间有些章节出现503状态码错误即(服务器请求错误)。
‘’.join(): 将列表数据转化为字符串数据。(合并)
resp_chapter = requests.get(url=detail_url,headers=headers,allow_redirects=False) # 请求小说章节链接
# 万能编码
resp_chapter.encoding = resp_chapter.apparent_encoding
sele = parsel.Selector(resp_chapter.text)
chapter_title=sele.xpath('//div[@class="bookname"]/h1/text()').extract_first() # 章节标题
content = '\n'.join(sele.xpath('//div[@id="content"]//text()').extract()) # 小说内容
print(chapter_title,content)
代码执行结果如下:
可以看出报出503错误的章节获取不到小说内容,一开始以为是请求头参数没加齐和状态码为304重定向的原因,测试了一两个小时,依然出现上面503返回的情况。最后还是加了time.sleep睡眠才解决了问题,原来是爬取速度快,被对方服务器识别到是爬虫了!
在请求详情章节内容链接的后面加上time.sleep睡眠延迟爬虫即可。
# 设置随机睡眠时间 防止被识别到是爬虫
time.sleep(random.uniform(1.1,1.5))
5、保存txt
一般爬取小说、新闻等等文字较多的数据,我一般都是保存txt文件,这样浏览起来比较方便,也可以保存到excel文件,看自身的需求,但是这种文字较多的不建议保存csv,若是获取的文本中带有空白字符等等,保存到csv打开的时候,浏览效果不佳。
path: 保存的文件路径
mode: 写入的模式
encoding: 编码格式
writer: 写入文件
# 打开txt文件
with open(path + '/' + f'{chapter_title}' + '.txt',mode='w',encoding='utf-8')as f:
f.write(chapter_title + '\n')
f.write(content + '\n')
代码执行的部分结果:
6、代码总结
import random
import re
import requests
import parsel
import os
import time
headers = { # 请求头,伪造身份
"Content-Encoding":"gzip",
"Content-Type":"text/html",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
}
# 目标链接
url = 'http://www.xbiquge.la/xiaoshuodaquan/'
response = requests.get(url=url,headers=headers) # 请求目的网址
# print(response) # <Response [200]>
# print(response.status_code) # 返回状态码 200
text = response.text # 返回字符串类型的数据即网页源码
# text = response.content.decode('utf-8') # 指定编码格式
# response.encoding = response.apparent_encoding # 万能编码
# print(response.text)
# parsel将网页源码转化为Selector对象,以便后续的语法提取数据
selector = parsel.Selector(text)
lis = selector.xpath('//div[@id="main"]/div[@class="novellist"]/ul/li')
for li in lis:
title = li.xpath('./a/text()').extract_first() # 小说名
href_url = li.xpath('./a/@href').extract_first() # 小说详情链接
# print(title,href_url)
# print(f'正在爬取小说:《{title}》!!!!!!!')
# print('-'*150)
# 以小说名命名文件夹
path = r'D:\爬虫\小说\全站小说\{}'.format(title)
if not os.path.exists(path):
os.makedirs(path) # 创建多层目录
resp_href = requests.get(url=href_url,headers=headers,allow_redirects=False) # 请求小说详情链接
href_text = resp_href.content.decode('utf-8') # 详情页编码
select = parsel.Selector(href_text)
# fiction_name = select.xpath('//div[@id="info"]/h1/text()').extract_first() # 小说名
# author = select.xpath('//div[@id="info"]/p[1]/text()').extract_first() # 作者
# author = re.sub(r'[作者:\t\s\n]','',author)
# update_time = select.xpath('//div[@id="info"]/p[3]/text()').extract_first() # 最后更新时间
# update_time = re.sub(r'[最后更新时间:\t\s\n]', '', update_time)
# Introduction = select.xpath('//div[@id="intro"]/p[2]/text()').extract_first() # 简介
# print('小说名:',fiction_name,'\n','作者:',author,'\n','最后更新时间:',update_time,'\n','简介:',Introduction,'\n')
dds = select.xpath('//div[@id="list"]/dl/dd') # 获取节点标签
for dd in dds:
chapter_title = dd.xpath('./a/text()').extract_first() # 小说章节名
detail_url = dd.xpath('./a/@href').extract_first() # 小说章节详情链接
detail_url = 'http://www.xbiquge.la' + detail_url # 拼接完整链接
# print(chapter_title,detail_url)
resp_chapter = requests.get(url=detail_url,headers=headers,allow_redirects=False) # 请求小说章节链接
time.sleep(random.uniform(1.1,1.5)) # 设置随机睡眠时间 防止被识别到是爬虫
# print(chapter_title,resp_chapter.status_code,detail_url)
resp_chapter.encoding = resp_chapter.apparent_encoding
sele = parsel.Selector(resp_chapter.text)
content = '\n'.join(sele.xpath('//div[@id="content"]//text()').extract()) # 小说内容
print(chapter_title,content)
#
# 打开txt文件
with open(path + '/' + f'{chapter_title}' + '.txt',mode='w',encoding='utf-8')as f:
f.write(chapter_title + '\n')
f.write(content + '\n')
若此代码有什么问题,还请各位大佬多多指教··· 若代码有不懂的小伙伴可在评论区留言,小编看到会一一回复,感谢观看。。。
注:此项目仅用于学习用途,若用于商业用途,请自行负责!!!