好久没有发表爬虫的博文了,其实爬虫一直都有写,只不过没有记录罢了。本次详细分析一个海外视频网站,抓取其中最高画质的视频,撰写本篇博文记录完整分析过程。
一.目标
1.主站
这个网站的视频内容丰富,直接就能访问,里边的内容包括并不限于电影、电视剧、动漫。
2.目标
本次以电视剧为入口,一步步分析每一层、每个接口的获取,最终拿到视频的最高画质地址,下载视频。
二.分析
1.完整流程
打开一集电视剧,点击播放按钮,视频开始播放。
https://watchmovie.ac/series/roswell-new-mexico-season-4-episode-11
点击视频下方的下载按钮
来到了下载中间页,在这个页面可以选择不同的源,我这里直接选择了第三个(因为相对其他三个简单)。
点击第三个按钮,来到了视频下载页面(这个封面有点哲学)。
点击左侧下载按钮,等待10秒钟,出现了下面的页面
其实在这个过程中向服务器发送了一个POST请求,地址为:
其实这两个地址是从接口返回的数据中拿到的
直接选择一个清晰度,点击按钮,就跳到了MP4播放页面
这个MP4地址是能够粘贴到迅雷里直接下载的,清晰度也可以。
国内下载确实不怎么快。
2.代码流程
先放一个我自己的分析草图
放一张完整分析流程图
三.重点
在分析过程中,发现此网站很多网页都存在JS监控开发者工具的代码,即当你打开了开发者工具,网页会给予提示甚至直接隐藏或者跳转。
或者
所以在分析过程中,还是要借助一下抓包工具,比如在分析最后一个页面的接口时,我用到了Fiddler。
在JS中发现了POST中的API地址。
直接获取所有的下载地址存储到数组,然后按索引取-1拿到最高画质!
四.源代码
# coding:utf-8
import requests
import json
from lxml import etree
import re
class WatchMovieSpider(object):
def __init__(self, url):
super(WatchMovieSpider, self).__init__()
self.normal_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
self.base_url = url
self.watchMovie_domain = "https://watchmovie.ac"
def do_request(self, url, headers, json_flag=False):
"""
携带headers发送请求到url
返回值类型可以是html也可以是json
"""
try:
r = requests.get(url, headers=headers)
if r.status_code == 200:
html = r.text
if json_flag:
_json_data = json.loads(html)
return _json_data
else:
return html
return False
except:
return False
def get_all_eps_link_and_name(self, ):
"""
根据用户输入的电视剧、动漫
获取其下所有剧集的地址
"""
if not self.base_url.endswith("/"):
all_eps_page_link = self.base_url + "/season"
html = self.do_request(all_eps_page_link, self.normal_headers)
if html:
res = etree.HTML(html)
selector = res.xpath(
'//div[@class="main-inner"]//ul[@class="video_module carousel"]/li//div[@class="vid_info"]/span[1]')
data_list = []
for index, each in enumerate(selector):
item = {
}
title = each.xpath('./a[@class="videoHname"]/@title')
if title:
video_ep_title = title[0]
else:
video_ep_title = "eps{}".format(index)
link = each.xpath('./a[@class="videoHname"]/@href')
if link:
video_ep_link = self.watchMovie_domain + link[0]
else:
video_ep_link = ""
item['video_ep_title'] = video_ep_title
item['video_ep_link'] = video_ep_link
data_list.append(item)
data_list.reverse() # 反转列表 使得电视剧集从小到大排列
return data_list
return False
def get_downloadPage_link(self, ep_link):
"""
根据主站获取iframe地址
:return:
"""
html = self.do_request(ep_link, headers=self.normal_headers)
if html:
iframe_link = re.findall('<iframe src="(.*?)".*?></iframe>', html)
if iframe_link:
iframe_real_link = "https:" + iframe_link[0]
donwloadPage_link = iframe_real_link.replace("streaming.php", "download")
return donwloadPage_link
return False
def get_postApi_addr(self, donwloadPage_link):
"""
访问根据下载页面,解析出【第三个按钮】的地址
使用字符串拼接,拼接出视频下载接口地址
:return:
"""
html = self.do_request(donwloadPage_link, headers=self.normal_headers)
if html:
res = etree.HTML(html)
aim_link = res.xpath(
'//div[@class="mirror_link"]/div[@class="dowload"]/a[text()="Download Xstreamcdn"]/@href')
if aim_link:
apiString = aim_link[0].split("#")[0].split("/")[-1]
postApi = "https://embedsito.com/api/source/" + apiString
return postApi
else:
# 设置一个提取Btn3的备选方案
print("使用plan2")
# 先提取出所有的地址,再使用字符串匹配
all_btn_links = res.xpath('//div[@class="mirror_link"]/div[@class="dowload"]/a/@href')
if all_btn_links:
aim_link = [l for l in all_btn_links if l.startswith("https://embedsito.com")]
if aim_link:
apiString = aim_link[0].split("#")[0].split("/")[-1]
postApi = "https://embedsito.com/api/source/" + apiString
return postApi
return False
def get_movie_downloadAddr(self, postApi):
"""
根据postApi获取真正的视频下载地址
:param postApi:
:return:
"""
try:
r = requests.post(postApi, headers=self.normal_headers)
if r.status_code == 200:
_json_data = r.json()
movie_high_addr = self.parse_movie_high(_json_data)
real_mp4_link = self.get_redirect_after_addr(movie_high_addr)
return real_mp4_link
return False
except:
return False
def parse_movie_high(self, _json_data):
"""
根据_json_data解析出画质最高的电影
:param _json_data:
:return:
"""
data = _json_data.get("data")
print("-" * 50)
movie_high_addr = data[-1].get("file")
return movie_high_addr
def get_redirect_after_addr(self, link):
"""
获取重定向之后真实的mp4播放地址
"""
r = requests.get(link, headers=self.normal_headers, allow_redirects=False) # 禁止重定向,拿到真实的mp4地址
if r.status_code == 302: # 获取重定向之后的真实mp4视频地址
return r.headers.get('Location')
elif r.status_code == 200:
return r.url
else:
return False
def video_name_format(self, name_str, ep):
"""
将电视剧名称转化为指定格式的名称
"""
# 正则提取一下剧名
ep_name = re.findall("Episode (.*?)$", name_str)
if ep_name:
eps_name_sp = ep_name[0].strip().split(" ")
eps_name_sp.pop(0)
eps_name = ' '.join(eps_name_sp)
else:
if "-" in name_str:
eps_name = name_str.split("-")[-1].strip()
else:
eps_name = ""
tv_eps = re.findall("Season \d+ Episode(\d) -", name_str)
if tv_eps:
eps_num = tv_eps[0]
else:
eps_num = ep
eps_name = eps_name.replace("-", "").strip()
full_ep_name = "Eps {}: {}".format(eps_num, eps_name)
if full_ep_name.strip().endswith(":"):
final_tv_name = full_ep_name.split(":")[0]
else:
final_tv_name = full_ep_name
return final_tv_name
def main():
all_eps_link_name = spider.get_all_eps_link_and_name()
print("ep_sum=", len(all_eps_link_name))
for index, video_item in enumerate(all_eps_link_name):
result = {
}
video_each_name = video_item['video_ep_title']
result['name'] = spider.video_name_format(video_each_name, index + 1)
video_each_link = video_item['video_ep_link']
downloadPage_link = spider.get_downloadPage_link(video_each_link) # downloadPage_link:下载页面的地址,包含四个按钮
postApi = spider.get_postApi_addr(downloadPage_link)
if postApi:
real_mp4_link = spider.get_movie_downloadAddr(postApi)
if real_mp4_link:
result['link'] = real_mp4_link
else:
result['link'] = ""
else:
result['link'] = ""
print("没有解析出视频下载接口!")
print(result)
if __name__ == '__main__':
lnik = "https://watchmovie.ac/series/the-amazing-race-canada-season-8"
if lnik.startswith("http") or lnik.startswith("https"):
spider = WatchMovieSpider(lnik)
main()
else:
print("输入地址不合法!")
五.总结
本次分析了一个海外视频网站,着重在于爬虫的分析思路,因为这会直接影响代码的撰写。发表本篇记录一下分析过程,也为其他的道友踩踩坑。思路、代码方面有什么不足欢迎各位大佬指正、批评!