【记录爬虫实战过程】入门学习详细过程·用爬虫实现小说爬取2
文章目录
前言
要做一个项目,所以先学习熟练应用爬虫。
在此记录学习过程,供他人参考,也督促自己坚持学习。
大致路线:
- 模仿他人
- 自己练习
- 总结
这是用爬虫实现小说爬取的第一部分,主要是根据别人的例子进行模仿和练习。
这篇为第二和三部分:自己练习+总结,在原来的基础上进行扩展:添加了请求头、设置爬取时间间隔、添加异常处理机制
背景:
已掌握一些基础的相关知识,运行环境为vs code
需要安装一些爬虫所用的库文件:可以使用pip获取,例如获取request库文件:pip install request
(如果不知道如何在vscode里面导入python的库可以参考这篇文章 在vscode环境里导入python库(三种方法) | 详细过程)
我的扩展
1.添加了请求头(headers):一个/随机
可以通过添加请求头来防反爬,如果不写请求头headers有可能会被封IP。
在初始化里面加上 self.headers
#防反爬
self.headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.204 Safari/537.36',
'content-type':'charst=uft8' #设置接收数据的编码格式
}
这里我只写了一个headers来抓到信息,一般来讲,一个请求头就行。
如果不能,我们可以升级一下,写n个请求头,然后通过随机模块来随机选择一个headers。
当然,这个做法比较笨。在查阅资料后,我发现python有一个专门的库fake_useragent来生成随机headers(相关资料)
关于如何在vscode里面导入python的库可以参考这篇文章 在vscode环境里导入python库(三种方法) | 详细过程
#生成随机headers
from fake_useragent import UserAgent
导入库后:
self.headers={
'User-Agent':UserAgent().random,
'content-type':'charst=uft8' #设置接收数据的编码格式
}
2.设置爬取时间间隔
很多网站的反爬虫机制都设置了访问间隔时间,一个IP如果短时间内超过了指定的次数就会进入“冷却CD”。
所以除了轮换IP和user_agent,还可以通过设置访问的时间间隔,比如每抓取一个页面休眠一个随机时间。(不过这个只用来爬小说好像用处不大,这里进行扩展主要是方便以后用于其他爬虫项目)
需要导入time库
#设置爬取时间间隔
import time
import random #可以设置随机睡眠时间
在主函数的下载部分添加time.sleep(0.5)函数,设置sleep时间间隔 ,参数可以为固定值或用random获得随机数
#设置sleep时间间隔
time.sleep(0.5)
#或者可以设置随机睡眠时间
#time.sleep(random.randint(5,10))
3.添加异常处理机制
#处理exception情况
from requests.exceptions import *
修改 get_download_url(self) 函数
try:
req=requests.get(url=self.target,headers=self.headers) #有改动,加上了headers
if req.status_code==200:
req.encoding = 'utf-8'
html=req.text
else:
print('No response')
return None
except ReadTimeout:
print("ReadTimeout!")
return None
except RequestException:
print("请求页面出错!")
return None
4.多线程爬虫:提高效率
需要导入threading模块,这是python中专门提供的,用来做多线程编程的模块。threading模块中最常用的类是Thread。
不过查了很多资料发现,下载单本小说用多线程其实并不方便,多线程适合同时下载多部小说,所以这里就不多讲了。
以下是自己练习的全部代码:(在“笔趣阁”网站爬的《解药》)
import requests
import sys
from bs4 import BeautifulSoup
from requests.models import Response
#处理exception情况
from requests.exceptions import *
#生成随机headers
from fake_useragent import UserAgent
#设置爬取时间间隔
import time
import random
#多线程
import threading
time.sleep(random.random()*3)
class downloader(threading.Thread):
def __init__(self):
#防反爬
# self.headers={
# 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.204 Safari/537.36',
# 'content-type':'charst=uft8' #设置接收数据的编码格式
# }
self.headers={
'User-Agent':UserAgent().random, #获得随机headers
'content-type':'charst=uft8' #设置接收数据的编码格式
}
self.server='https://www.tianxiabachang.cn'
self.target='https://www.tianxiabachang.cn/5_5147/'
self.names=[] #存放章节名
self.urls=[] #存放章节链接
self.nums=0 #存放章节数
#函数说明:获取下载链接
def get_download_url(self):
try:
req=requests.get(url=self.target,headers=self.headers) #有改动,加上了headers
if req.status_code==200:
req.encoding = 'utf-8'
html=req.text
else:
print('No response')
return None
except ReadTimeout:
print("ReadTimeout!")
return None
except RequestException:
print("请求页面出错!")
return None
div_bf=BeautifulSoup(html)
div=div_bf.find_all('div',id='list')
a_bf=BeautifulSoup(str(div[0])) #div中有多个元素,比如包括网站。此处选择第0项元素
a=a_bf.find_all('a')
self.nums=len(a[9:13]) #剔除不必要的章节,并统计章节数
for each in a[9:13]:
self.names.append(each.string)
self.urls.append(self.server+each.get('href'))
#函数说明:获取章节内容
def get_contents(self,target): #有改动
req=requests.get(url=target)
req.encoding = 'utf-8'
html=req.text
bf=BeautifulSoup(html)
texts=bf.find_all('div',id='content')
if texts[0]:
texts=texts[0].text.replace('\xa0'*8,'\n\n')
return texts
#函数说明:将爬取的文章内容写入文件
def writer(self,name,path,text):
write_flag=True
with open(path,'a',encoding='utf-8') as f:
f.write(name+'\n') #小说名字
f.writelines(text) #小说内容
f.write('\n\n')
#主函数
if __name__=="__main__":
dl=downloader()
dl.get_download_url()
print('《解药》开始下载:')
for i in range(dl.nums):
dl.writer(dl.names[i],'解药(测试).txt',dl.get_contents(dl.urls[i]))
print(" 已下载第"+str (i+1)+ "章:%.3f%%" % float(i/dl.nums)+'\r') #改进
sys.stdout.write(" 已下载:%.3f%%" % float(i/dl.nums)+'\r')
sys.stdout.flush()
#设置sleep时间间隔
time.sleep(0.5)
#time.sleep(random.randint(5,10)) # 产生 5 到 10 的一个整数型随机数填入sleep函数
print("《解药》下载完毕")
5. 补充资料
这个视频 可以帮助梳理过程和理解一些代码的运用
总结爬取小说的大致步骤
3.1.确定爬取的url地址
3.2.发送请求
3.3.数据解析,得到目标数据
3.3.1 解析目录
3.3.2解析内容
3.3.3 处理内容
3.3.4 保存数据
3.4 完善主函数,将数据存入txt文件
1.确定爬取的url地址(分析网页性质<静态/动态>)
target = 'https://www.bqkan8.com/1_1094/'
2.发送请求
req = requests.get(url=target)
#解决中文乱码问题!
Response.encoding=Response.apparent_encoding #自动识别响应体的编码
#req.encoding = 'GB2312'
#req.encoding = 'utf-8'
html =req.text
3.数据解析,得到目标数据
(可能涉及正则表达式)
3.1 解析目录
例如,当网页检查结果是这样时:
方法一:
解析代码为:
#需要先导入re库
import re
result_list=re.findall('<dd><a href="(.*?)">.*</a></dd>',html)
#若有必要可以使用以下代码检查输出结果
# print("输出结果\n")
# print(result_list)
()表示精确匹配;
’ . '代表匹配除了换行符以外的任意字符;
’ * '代表匹配前一个字符的0次或者多次;
‘ ? ’'代表非贪婪模式,即匹配一次返回一次
方法二:
也就是之前提取小说用的方法,使用BeautifulSoup
div_bf=BeautifulSoup(html)
div=div_bf.find_all('div',id='list')
3.2解析内容
方法一:
html =req.text
#print(html)
result_list=re.findall('<div id="content">(.*?)</div>',html,re.S)
#若有必要可以使用以下代码检查输出结果
# print("输出结果\n")
# print(result_list)
最后的参数 re.S让‘ . ’ 能够匹配到换行符,不然输出结果不正确
方法二:
bf=BeautifulSoup(html)
texts=bf.find_all('div',id='content')
3.3 处理内容
将一些字符替换成换行
方法一:
#因为div中有多个元素,比如包括网站。
#所以此处选择第0项元素
text=result[0].replace(' ','').replace('<br />','\n')
print(text)
方法二:
print(texts[0].text.replace('\xa0'*8,'\n\n'))
3.3.4 保存数据
#函数说明:将爬取的文章内容写入文件
def writer(self,name,path,text):
write_flag=True
with open(path,'a',encoding='utf-8') as f:
f.write(name+'\n') #小说名字
f.writelines(text) #小说内容
f.write('\n\n')
3.4 完善主函数,将数据存入TXT文件
#主函数
if __name__=="__main__":
dl=downloader()
dl.get_download_url()
print('《解药》开始下载:')
for i in range(dl.nums):
dl.writer(dl.names[i],'解药(测试).txt',dl.get_contents(dl.urls[i]))
print(" 已下载第"+str (i+1)+ "章:%.3f%%" % float(i/dl.nums)+'\r') #改进
sys.stdout.write(" 已下载:%.3f%%" % float(i/dl.nums)+'\r')
sys.stdout.flush()
#设置sleep时间间隔
time.sleep(0.5)
#time.sleep(random.randint(5,10)) # 产生 5 到 10 的一个整数型随机数填入sleep函数
print("《解药》下载完毕")
4.遇到的问题
4.1.出现这个报错:list index out of range
翻译: 列表超出最大范围。
意思:假如一个列表 list 的长度为 2,你通过下标去取第三位( list[2] ),就会报错,超出范围。
这里通过下标[0]取第一个,报错了没有取到,说明这句正则表达式没有匹配到指定内容。
若解析元素不当,导致列表里面的元素为空事,就出现这种错误。
解决方法:再次检查自己的正则表达式或者解析元素是否正确,并且可以加一个判断语句:
if result[0]:
text=result[0].replace(' ','').replace('<br />','')
或者
if texts[0]:
print(texts[0].text.replace('\xa0'*8,'\n\n'))
还可以加异常捕获(这里就不详细阐述了)
try:
{} #代码块
except Exception as e:
print(e)
4.2. 小说不换行!
小说页面元素时这样的,但是用原来的代码输出的时候就没有换行
虽然原来的代码对 
进行了替换。但是爬取的小说还是没有换行
texts=texts[0].text.replace('\xa0'*8,'\n\n')
解决方法:
自己设置将两个空格替换成换行
texts=texts.replace('\u3000\u3000','\n\n') #将两个空格替换成换行
结果:
爬取小说的学习过程就到此为止了,撒花完结~~