python梨视频爬虫下载,反反爬:AJAX异步请求+防盗链Referer
前言
通过b站学习爬虫后尝试爬取梨视频过程中,试验发现,发送AJAX请求后会响应“文章已经下线”——{'resultCode': '5', 'resultMsg': '该文章已经下线!', 'systemTime': '1643266074614'}
最终发现问题在于请求头中防盗链Referer的使用
本文章通过对AJAX和Referer的简单讲解,完成对梨视频的爬取
正则处理一下就能愉快的下载视频了
提示:以下是本篇文章正文内容
一、AJAX请求
在b站教学视频中提到,直接对视频页URL发送请求并解析,响应只会得到一个空列表。原因在于视频页是一个动态页面,需要发送AJAX请求。
这一点可以通过检查网络,捕获XHR包检验。
通过XHR包中的标头可以发现视频页接收AJAX请求的服务器的URL:
https://www.pearvideo.com/videoStatus.jsp?contId=1750756&mrd=0.4060840459273962
通过以上URL得知,发送AJAX请求需要设置两个参数:contid和mrd。contid是视频编号,可以通过解析主页获取;mrd是0-1之间的一个随机小数,可以通过random()函数随机生成
# 前半部分代码如下
from os import name
import requests, re, random, time
from lxml import etree
from multiprocessing.dummy import Pool
session = requests.Session()
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69'
}
url = 'https://www.pearvideo.com/category_5' # 梨视频首页
page_text = session.get(url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="category-top"]//li')
urls = []
for li in li_list: # 解析得到每个视频页的url
href = li.xpath('.//a/@href')
video_index = re.sub('video_','',href[0])
# 伪URL:https://www.pearvideo.com/video_1750756
# 真URL:https://video.pearvideo.com/mp4/third/20220127/cont-1750756-10008579-095059-hd.mp4
videoPage_url = 'https://www.pearvideo.com/videoStatus.jsp'
# 设置请求头中防盗链参数
Referer = 'https://www.pearvideo.com/'+href[0]
mrd = random.random()
params = {
'contId':video_index,
'mrd':mrd
}
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69',
}
# 设置视频名称
name = li.xpath('./div/a/div[2]/text()')[0]+'.mp4'
print(videoPage_url, name)
# 对真URL发送AJAX请求
detail_json = session.post(videoPage_url,params,headers = headers).json()
print(detail_json)
运行上述代码,部分结果如下:
https://www.pearvideo.com/videoStatus.jsp 女子跳桥轻生,公园保安跳江救人.mp4
{'resultCode': '5', 'resultMsg': '该文章已经下线!', 'systemTime': '1643284204531'}
意外显示“文章已经下线”,这显然不是预期结果。原因见下一节
二、防盗链Referer
引:防盗链Referer是HTTP 请求header的一部分,当浏览器(或者模拟浏览器行为)向web服务器发送请求的时候,头信息里有包含Referer。将包含referer的http请求发给服务器后,如果服务器要求必须是某个地址或者某几个地址才能访问,而你发送的referer不符合他的要求,就会拦截或者跳转到他要求的地址,然后再通过这个地址进行访问。
参考见以下文章:《http请求头中Referer的含义和作用》
通过以上内容可以了解到,出现无法访问的原因很可能是原代码没有输入referer。
同样通过网络检查,在XHR包中的请求头中可以找到我们需要的referer:
改进代码,在headers中添加防盗链(以下是完整的代码)
from os import name
import requests, re, random, time
from lxml import etree
from multiprocessing.dummy import Pool
session = requests.Session()
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69'
}
url = 'https://www.pearvideo.com/category_5' # 梨视频首页
page_text = session.get(url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="category-top"]//li')
urls = []
for li in li_list: # 解析得到每个视频页的url
href = li.xpath('.//a/@href')
video_index = re.sub('video_','',href[0])
# 伪URL:https://www.pearvideo.com/video_1750756
# 真URL:https://video.pearvideo.com/mp4/third/20220127/cont-1750756-10008579-095059-hd.mp4
videoPage_url = 'https://www.pearvideo.com/videoStatus.jsp'
# 设置请求头中防盗链参数
Referer = 'https://www.pearvideo.com/'+href[0]
mrd = random.random()
params = {
'contId':video_index,
'mrd':mrd
}
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69',
'Referer':Referer
}
# 设置视频名称
name = li.xpath('./div/a/div[2]/text()')[0]+'.mp4'
print(videoPage_url, name)
# 对真URL发送AJAX请求
detail_json = session.post(videoPage_url,params,headers = headers).json()
print(detail_json)
mp4_url=detail_json['videoInfo']['videos']['srcUrl']
#print(mp4_url)
# 拼接出真实的视频地址
mp4_url=re.sub('/(\d*?)-','/cont-'+video_index+'-', mp4_url)
print(mp4_url)
# 对真实的视频地址进行存储
dic = {
'name':name,
'TrueUrl':mp4_url
}
urls.append(dic)
def get_videoUrl(urls):
name = ''.join(urls['name'].split('|')) # 对标题进行重组,以便视频存储
url = urls['TrueUrl']
data = requests.get(url,headers=headers).content
print(name,'开始下载')
with open(name, 'wb') as fp:
fp.write(data)
fp.close()
print(name,'下载成功')
# 使用线程池对视频数据进行请求
StartTime = time.time()
pool = Pool(4)
pool.map(get_videoUrl, urls)
EndTime = time.time()
print('运行结束,耗时{}s'.format(EndTime-StartTime))
pool.close() # 关闭线程池
pool.join() # 主线程等待子线程结束后加入
总结
在代码书写过程中要注意检验,观察响应信息,判断是否是AJAX请求,是GET请求还是POST请求,并灵活地对不同的反爬机制应用相应的反反爬策略