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:

https://www.pearvideo.com/video_1750756

改进代码,在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请求,并灵活地对不同的反爬机制应用相应的反反爬策略

posted on 2022-01-27 21:18    阅读(204)  评论(0)    收藏  举报

导航