前言
最近学了一部分网络爬虫的基础知识,于是就拿梨视频网站上面的视频做了一次尝试,起初觉得有一点简单,但是真正到了请求下载视频链接时,又发现无法下载。于是对比了我第一次拿到的url跟真正的视频url比较,存在不同的地方,最后发现了梨视频官方对自己的视频进行了链接的伪造以防止爬取。
最终,感觉梨视频的爬取难度还是适中的,对我这样的新手在理解网络爬虫方面还是有很大帮助的。
python环境
Python版本-3.11.5
import requests # 用于发送HTTP请求
from lxml import etree # 用于解析HTML
import random # 用于生成随机数
import os # 用于文件操作
from multiprocessing.dummy import Pool # 使用多线程池提高爬取效率
对相关页面的观察分析
首先,打开梨视频的官网链接:
梨视频官网-有故事的短视频-Pear Video
通过一些观察,我准备从视频量比较多的地方进行爬取,于是便找到了主页的 人物、万象
两个页面进行爬取。
-->人物页面所对应的URL地址:
人物热点短视频_-梨视频官网-Pear Video
-->万象页面所对应的URL地址:
万象热点资讯_万象热点新闻-梨视频官网-Pear Video
以返老还童?重庆百岁老人白发变青丝,最爱《封神榜》_身体-梨视频官网-Pear Video
为例
在人物界面,鼠标拖拽到一个图片上面,在浏览器左下角位置,我们可以看到其所对应URL链接,以返老还童?重庆百岁老人白发变青丝,最爱《封神榜》_身体-梨视频官网-Pear Video为例,我们可以看到例子中的1686640
应该具有某种作用,这里猜测为视频所对应的一个ID。
返老还童?重庆百岁老人白发变青丝,最爱《封神榜》_身体-梨视频官网-Pear Video
inner_url
所对应的 Referer
为 host_url
随便点击一个视频页面进入,我们打开开发者工具,找到视频对应的URL,可以发现请求表头里面包括Referer:https://www.pearvideo.com/category_1
检查页面元素代码,找到 inner_video
所对应的 src 链接
通过开发者工具,查看网页代码,我们可以看到element中的视频部分标签:
src="https://video.pearvideo.com/mp4/adshort/20200717/cont-1686640-15272298_adpkg-ad_hd.mp4"
https://video.pearvideo.com/mp4/adshort/20200717/cont-1686640-15272298_adpkg-ad_hd.mp4
分析页面源代码,发现 inner_video 是动态加载出来的,开始分析 ajax 文件
通过分析,我们可以发现视频的inner_url
详情页源代码中是不含有视频链接的,也就是说详情页中的视频是动态加载出来的,这时候就需要我们去分析ajax文件
了。
找到Fetch/XHR
中的网址请求
通过抓包工具,我们可以看到Fetch/XHR
中存在这样一个请求:https://www.pearvideo.com/videoStatus.jsp?contId=1686640&mrd=0.17203231944534259
打开请求网址,发现问题,证明视频链接进行了伪装,即设置了反爬机制
但是打开这个链接,发现并不是我们想要的视频,而是这样的页面:
使用到的反爬机制--->防盗链(Referer
)
其实这是网站使用的一种反爬机制,即——防盗链(`Referer`)。所以,我们所得到的这个https://www.pearvideo.com/videoStatus.jsp?contId=1686640&mrd=0.17203231944534259链接是一个伪装过的链接,不是真正的视频链接。
分析网址请求后的相应内容,对systemTime
和srcUrl
进行分析
紧接着,通过抓包工具,点击查看请求响应内容:
{
"resultCode":"1",
"resultMsg":"success", "reqId":"250bf9cf-31cc-492c-a71a-4f844e1b9acb",
"systemTime": "1697377523473",
"videoInfo":{
"playSta":"1",
"video_image":"https://image.pearvideo.com/cont/20200717/cont-1686640-12430737.png",
"videos":
{
"hdUrl":"",
"hdflvUrl":"",
"sdUrl":"",
"sdflvUrl":"",
"srcUrl":"https://video.pearvideo.com/mp4/adshort/20200717/1697377523473-15272298_adpkg-ad_hd.mp4"
}
}
}
打开这个"srcUrl":"https://video.pearvideo.com/mp4/adshort/20200717/1697377523473-15272298_adpkg-ad_hd.mp4" 会发现报错,证明这个也不是真正的视频链接。
我们之前在检查网页的元素代码时找到了src="https://video.pearvideo.com/mp4/adshort/20200717/cont-1686640-15272298_adpkg-ad_hd.mp4"
我们在浏览器中打开这个视频链接,会发现这个链接可以正常打开,证明这个链接所对应的内容就是i我们的视频。
对比下我们目前拿到的两个URL,不难发现其中的参数存在差异性,这里的1697377523473其实就是我们在相应内容中所获得到的"systemTime
": "1697377523473
" ,而1686640
就是视频所对应的contId
。
对比srcUrl 和video_url,分析差异性,分析参数的具体含义
"srcUrl":"https://video.pearvideo.com/mp4/adshort/20200717/1697377523473-15272298_adpkg-ad_hd.mp4"
"video_url":"https://video.pearvideo.com/mp4/adshort/20200717/cont-1686640-15272298_adpkg-ad_hd.mp4"
那么我们要拿到真正的视频链接video_url
,只需要提取出相应内容json文件里的srcUrl
,然后通过字符串的替换操作,将systemTime
替换成cont-contId
即可拿到真正的视频链接。
分析请求头中的防盗链Referer,同时分析请求参数中 mrd 的具体含义
通过抓包工具,我们可以看到该请求的Referer
是:https://www.pearvideo.com/video_1686640
,不难看出这个Referer地址就是我们的网页详情页面的URL
在Fetch/XHR
中存在这样一个请求:https://www.pearvideo.com/videoStatus.jsp?contId=1686640&mrd=0.17203231944534259
,我们不难猜测出视频链接前面的https://www.pearvideo.com/videoStatus.jsp?
应该是一致的,而链接后面所对应的是相关的参数(contId和mrd)。参数contId就是视频独有的ID,而参数mrd 是一个随机的浮点数,这个浮点数范围是0-1之间。
导入相应的模块
import requests # 用于发送HTTP请求
from lxml import etree # 用于解析HTML
import random # 用于生成随机数
import os # 用于文件操作
from multiprocessing.dummy import Pool # 使用多线程池提高爬取效率
为了提高视频爬取的效率,我采用了多线程的方法来下载视频。
创建视频的存储路径
# 检查视频存储目录是否存在,如果不存在则创建
if not os.path.exists('./梨视频'):
os.mkdir('./梨视频')
save_path = './梨视频/'
检查当前工作目录下是否存在名为"梨视频"的目录,如果不存在则创建它,然后将目录路径存储在save_path
变量中,以便后续操作可以使用这个路径。
实例--->主要操作
在页面的详情页面代码中进行下列操作:
1.构建用户可以选择某视频进行下载的页面:
# URL列表
host_url_list = ['https://www.pearvideo.com/category_1', 'https://www.pearvideo.com/panorama']
# 设置host_headers请求头
host_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63"
}
# 获取网站标题信息
site_titles = []
for host_url in host_url_list:
page_text = requests.get(host_url, headers=host_headers).text
tree = etree.HTML(page_text)
site_title = tree.xpath('//title/text()')[0]
site_titles.append(site_title)
# 用户选择URL
print("欢迎使用梨视频下载工具")
print("请选择要爬取的网站:")
for i, (url, title) in enumerate(zip(host_url_list, site_titles), 1):
print(f"{i}. {title}")
while True:
try:
choice = int(input("请输入数字 1 或 2 来选择: "))
if 1 <= choice <= 2:
break
else:
print("请输入有效的选择 (1 或 2) ")
except ValueError:
print("请输入有效的选择 (1 或 2)")
selected_url = host_url_list[choice - 1]
# 获取用户选择的URL
host_url = selected_url
# 发送GET请求获取主页host_url的HTML内容
page_text = requests.get(url=host_url, headers=host_headers).text
tree = etree.HTML(page_text)
# 提取host主页中的inner视频链接
inner_href_list = tree.xpath('//div[@class="vervideo-bd"]/a/@href')
# 输出inner_href链接对应的标题
print("\n以下是可供下载的视频标题:")
for i, inner_href in enumerate(inner_href_list, 1):
inner_url = 'https://www.pearvideo.com/' + inner_href
inner_text = requests.get(url=inner_url, headers=host_headers).text
title = etree.HTML(inner_text).xpath('//h1[@class="video-tt"]/text()')[0]
print(f"{i}. {title}")
# 用户选择要下载的inner_href视频
selected_indices = input("输入要下载的视频序号(用逗号分隔,如1,3,5),或者直接按回车下载所有视频:")
if selected_indices:
selected_indices = [int(i) - 1 for i in selected_indices.split(",")]
else:
selected_indices = range(len(inner_href_list))
2.提取出inner_href视频对应的标题title
---->xpath路径如下:
//h1[@class="video-tt"]/text()
3.提取inner_href视频所对应的contId
---->xpath路径如下:
//div[@id="poster"]/@data-cid
4.通过python代码生成一个mrd
随机浮点数
# 生成一个随机的mrd参数
mrd = random.random()
通过上面的操作,我们便可以得到视频所对应的两个参数contId
和mrd
构造出用于获取视频真实链接的ajax请求url
# 构造用于获取视频真实链接的ajax请求url
ajax_url = 'https://www.pearvideo.com/videoStatus.jsp?'
params = {
"contId": contId,
"mrd": mrd
}
# 设置ajax请求的头部,包括Referer
ajax_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
"Referer": inner_url
}
5.发送ajax
请求,获取视频信息的JSON
数据,提取JSON
中的systemTime
和srcUrl
# 发送ajax请求,获取视频信息的JSON数据
response = requests.get(url=ajax_url, headers=ajax_headers, params=params)
# 提取JSON中的systemTime和srcUrl
systemTime = response.json()['systemTime']
srcUrl = response.json()['videoInfo']['videos']['srcUrl']
6.替换伪装的视频链接中的systemTime
参数,获取真正的视频链接
srcUrl = str(srcUrl).replace(str(systemTime), f'cont-{contId}')
7.将视频内容保存到指定目录下,以视频标题命名
# 发送请求获取视频内容
content = requests.get(url=srcUrl, headers=ajax_headers).content
# 将视频内容保存到指定目录下,以视频标题命名
with open(save_path + title, mode='wb') as fp:
fp.write(content)
fp.close()
# 打印提示信息
print(f'{title} 下载完毕!!!')
8.遍历用户选择的视频链接,将每个链接加入到并行下载任务
# 遍历用户选择的视频链接,将每个链接加入到并行下载任务
pool.map(download_video, selected_indices)
# 等待所有任务完成
pool.close()
pool.join()
程序完整的代码
# 1.导包 和 设置视频存储路径
import requests # 用于发送HTTP请求
from lxml import etree # 用于解析HTML
import random # 用于生成随机数
import os # 用于文件操作
from multiprocessing.dummy import Pool # 使用多线程池提高爬取效率
# 检查视频存储目录是否存在,如果不存在则创建
if not os.path.exists('./梨视频'):
os.mkdir('./梨视频')
save_path = './梨视频/'
# 2.指定主页url,设置请求头信息
# URL列表
host_url_list = ['https://www.pearvideo.com/category_1', 'https://www.pearvideo.com/panorama']
# 设置host_headers请求头
host_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63"
}
# 获取host网站标题信息
host_site_titles = []
for host_url in host_url_list:
page_text = requests.get(host_url, headers=host_headers).text
tree = etree.HTML(page_text)
host_site_title = tree.xpath('//title/text()')[0]
host_site_titles.append(host_site_title)
# 用户选择URL
print("欢迎使用梨视频下载工具")
print("请选择要爬取的网站:")
for i, (url, title) in enumerate(zip(host_url_list, host_site_titles), 1):
print(f"{i}. {title}")
while True:
try:
choice = int(input("请输入数字 1 或 2 来选择: "))
if 1 <= choice <= 2:
break
else:
print("请输入有效的选择 (1 或 2) ")
except ValueError:
print("请输入有效的选择 (1 或 2)")
selected_url = host_url_list[choice - 1]
# 获取用户选择的URL
host_url = selected_url
# 发送GET请求获取主页的HTML内容
page_text = requests.get(url=host_url, headers=host_headers).text
tree = etree.HTML(page_text)
# 提取主页中的视频链接
inner_href_list = tree.xpath('//div[@class="vervideo-bd"]/a/@href')
# 输出链接对应的标题
print("\n以下是可供下载的视频标题:")
inner_url_list = []
for i, inner_href in enumerate(inner_href_list, 1):
inner_url = 'https://www.pearvideo.com/' + inner_href
inner_url_list.append(inner_url)
inner_text = requests.get(url=inner_url, headers=host_headers).text
title = etree.HTML(inner_text).xpath('//h1[@class="video-tt"]/text()')[0]
print(f"{i}. {title}")
# 用户选择要下载的视频
selected_indices = input("输入要下载的视频序号(用逗号分隔,如1,3,5),或者直接按回车下载所有视频:")
if selected_indices:
selected_indices = [int(i) - 1 for i in selected_indices.split(",")]
else:
selected_indices = range(len(inner_href_list))
# 使用多线程池提高爬取效率
pool = Pool(6)
# 定义一个下载视频的函数
def download_video(index):
inner_url = inner_url_list[index]
# 4.对详情页源代码进行分析,找出视频链接。
# 但是通过分析,我们可以发现视频的详情页源代码中是不含有视频链接的,也就是说详情页中的视频 是动态加载出来的,这时候就需要我们去分析ajax文件了。
# 通过抓包工具,我们可以看到Fetch/XHD中存在这样一个请求:https://www.pearvideo.com/videoStatus.jsp?contId=1156899&mrd=0.13727699405356097。但是打开这个链接,发现并不是我们想要的视频,而是这样的:
# {
# "resultCode":"5",
# "resultMsg":"该文章已经下线!",
# "systemTime": "1676187693726"
# }
# 其实这是网站使用的一种反爬机制,即——防盗链(Referer)
# 通过抓包工具,我们可以看到该请求的Referer是:https://www.pearvideo.com/video_1156899 这个链接就是我们的inner_url
# 5.找出ajax文件发出请求url,并找到参数 contId 和 mrd
# 经常分析,我们可以看出参数 mrd 是一个随机的浮点数,这个浮点数范围是0-1之间
# 获取详情页的HTML内容
inner_text = requests.get(url=inner_url, headers=host_headers).text
# 提取视频标题和contId
title = etree.HTML(inner_text).xpath('//h1[@class="video-tt"]/text()')[0] + '.mp4'
contId = etree.HTML(inner_text).xpath('//div[@id="poster"]/@data-cid')[0]
# 生成一个随机的mrd参数
mrd = random.random()
# 构造用于获取视频真实链接的ajax请求url
ajax_url = 'https://www.pearvideo.com/videoStatus.jsp?'
params = {
"contId": contId,
"mrd": mrd
}
# 6.设置ajax请求的头部,包括Referer
ajax_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
"Referer": inner_url
}
# 发送ajax请求,获取视频信息的JSON数据
response = requests.get(url=ajax_url, headers=ajax_headers, params=params)
# 提取JSON中的systemTime和srcUrl
systemTime = response.json()['systemTime']
srcUrl = response.json()['videoInfo']['videos']['srcUrl']
# print(srcUrl)
# 7.此时我们找到了json文件中包含的视频url,但是我们打开发现网页404报错,这就意味着这个链接其实并不是真正的视频链接;那么,我们开始对视频进行分析,
# 发现视频的真正链接是:https://video.pearvideo.com/mp4/short/20170915/cont-1156899-10890623-hd.mp4
# 通过对比分析,可以发现这两个链接只有一处不同的地方(systemTime),那么接下来我们就要把伪装的链接通过字符串替换,拿到真正的视频链接
# 8.替换伪装的视频链接中的systemTime参数,获取真正的视频链接
srcUrl = str(srcUrl).replace(str(systemTime), f'cont-{contId}')
# 发送请求获取视频内容
content = requests.get(url=srcUrl, headers=ajax_headers).content
# 9.将视频内容保存到指定目录下,以视频标题命名
with open(save_path + title, mode='wb') as fp:
fp.write(content)
fp.close()
# 打印提示信息
print(f'{title} 下载完毕!!!')
# 遍历用户选择的视频链接,将每个链接加入到并行下载任务
pool.map(download_video, selected_indices)
# 等待所有任务完成
pool.close()
pool.join()