欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python爬虫:AGE动漫下载之 requests 版

程序员文章站 2022-04-01 12:16:00
终于放暑假了,其实都还没有感觉!在家都待了大半年了,完全忘记了自己之前对放假期待的那种心情~~~...

Python爬虫:AGE动漫下载之 requests 版

导入:

终于放暑假了,其实都还没有感觉!在家都待了大半年了,完全忘记了自己之前对放假期待的那种心情~~~
不过无论怎样,都 挡不住我自学的脚步!!! 话不多说,马上开始这次的介绍:

此次,我采用了两种方式来完成这一次的爬虫实例:

  • 1、requests 版: 通过浏览器的开发者工具抓包,分析 发送的链接 以及 返回的数据 来爬取。
  • 2、selenium 版 : 通过驱动 驱动浏览器 来完成部分操作,并获取在完成 JS渲染后的网页源码 来爬取。直达:编写中…

分析与代码解释:

因为有些时候在爬取的列表中,或许没有我们想看的,所以我这次直接从搜索入手。
在网页端尝试后很容易的就找到 “搜索接口”

print('#' * 25 + '\tAGE动漫离线助手\t' + '#' * 25)
keyword = input('请输入搜索关键字:')
url = 'http://agefans.org/search?q=' + keyword

我们以 “约会” 为关键词来编写第一版的代码:
Python爬虫:AGE动漫下载之 requests 版
我们可以很清楚的看到,搜索到的视频信息就保存在 <li>…</li> 标签中,但是,对视频的描述信息全都在 class=“card-body p-2” 的 div 标签中,所以我们首先要拿到所有的 class=“card-body p-2” 的 div 标签 :

打印搜索到的信息

在打印信息时用 Serial 来顺序标注每个视频:由于含有视频所有的信息的标签全都被截取到了,所以在这里用 BeautifulSoup4 来解析所截取到的数据比之前的 Xpath简洁的多(至少在这里我时这么认为)。

def print_info(url):  # 输出搜索到的视频信息
    html = request(url).text
    search_info = parsel.Selector(html).xpath('//*[@id="search_list"]/ul//div[@class="card-body p-2"]').extract()
    Serial = 0
    for info in search_info:
        Serial += 1  #标注视频
        soup = BeautifulSoup(info, 'html.parser')
        video_url[soup.a.h5.string] = 'http://agefans.org' + soup.a.attrs['href']
        print("{:0>2d}:\t《 ".format(Serial) + soup.a.h5.string + ' 》')
        for div in soup.find_all('div', {'class': ''}):
            print('\t' + div.span.string + div.span.next_sibling.next_sibling.string)
        num = 0
        for li in soup.find_all('li'):
            num += 1
            if num % 2 != 0:  # 优化打印界面
                print('\t' + li.span.string + li.span.next_sibling.next_sibling.string, end='')
            else:
                print('\t' + li.span.string + li.span.next_sibling.next_sibling.string)
        intro = soup.find('div', {'class': 'ellipsis_summary catalog_summary small'})
        print('\t' + intro.span.string + intro.span.next_sibling.next_sibling.string.replace('\n', '') + '...')
        print('\n' + '=' * 30 + '\n')
BeautifelSoup4 库使用提醒

不过:这里有一点需要注意,这也是我当初自学 BeautifulSoup4 库所遗漏的地方了:
Python爬虫:AGE动漫下载之 requests 版
我们可以清楚的看到无论是保存在 <div>…</div> 还是在 <li>…</li> 标签中的信息,都由两个 <span>…</span> 标签包裹着。
所以在访问第一个 <span>…</span> 标签的文本:div.span.string
我们还知道访问下一个兄弟标签时使用: 当前标签.next_sibling
然而在这里,当我们想要访问第二个 <span>…</span> 标签的文本时,并不是使用 div.span.next_sibling.string ,而是**div.span.next_sibling.next_sibling.string**。
至于为什么用两次 .next_sibling 呢?
这个问题在 BeautifulSoup4 库的 官网文档 中就已经给了我们答案了:Python爬虫:AGE动漫下载之 requests 版

小优化

完成了信息的输出,一运行发现,居然有一个错误:
Python爬虫:AGE动漫下载之 requests 版
这是为什么呢?当我们打开浏览器实际操作时发现:
Python爬虫:AGE动漫下载之 requests 版
这个视频简介的第二个 <span>…</span> 标签的格式和之前的有所差别,我又找了几个类似的信息查看,觉得还是用简单粗暴的方法方便一点:
直接获取整个标签,将所有的换行元素标签 <br/> 给替换掉!

#
#之前的代码全都不变
#
intro = soup.find('div', {'class': 'ellipsis_summary catalog_summary small'})
try:
    print('\t' + intro.span.string + intro.span.next_sibling.next_sibling.string.replace('\n', '') + '...')
except:
    content = str(intro.span.next_sibling.next_sibling).replace('\n', '').replace('<span>', '').replace('<br/>', '').replace('</span>', '').replace('&lt;', '') + '...'
    print('\t' + intro.span.string + content)
print('\n' + '=' * 30 + '\n')

链接解析:

成功打印出了搜索到的所有视频信息后,现在开始分析发送的请求,用来提取视频的下载链接:

首先,该网站在视频打开后不会直接加载第几集,而是要我们选择,我们可以通过开发者工具可以获取到每一集的链接:
Python爬虫:AGE动漫下载之 requests 版
当我们点击某一集后,开发者工具会为我们捕获到如下的请求信息:
Python爬虫:AGE动漫下载之 requests 版
Python爬虫:AGE动漫下载之 requests 版
通过以上的两张图片,我们可以很清楚的发现这几条链接之间的关系,我试着将第二条请求返回的链接进行 URL解码 发现:这就是视频的加载链接:Python爬虫:AGE动漫下载之 requests 版

获取视频链接

这下就好办多了,下面开始编写获取视频链接的代码:
思路还是先通过 Xpath 获取所有的 <li>…</li> 标签,再用 BeautifulSoup4 来解析获取我们想要的东西:

def get_relurl(name):
    global referer
    referer = video_url[name]
    html = request(video_url[name]).text
    time.sleep(random.random())
    all_li = parsel.Selector(html).xpath('//div[@id="plays_list"]/ul/li').extract()
    for info in tqdm(all_li, desc='正在获取视频链接:'):
        li = BeautifulSoup(info, 'html.parser')
        url = 'http://agefans.org/myapp/_get_ep_plays?{}&anime_id={}'.format(li.a.attrs['href'].split('_', 1)[-1].replace('_', '='), video_url[name].split('/')[-1])
        referer = video_url[name] + li.a.attrs['href']
        ID=request(url).json()['result'][0]['id']
        new_url = 'http://agefans.org/myapp/_get_e_i?url={}&quote=1'.format(ID)
        episodes_url[li.a.string.replace(' ', '')] = unquote(request(new_url).json()['result'])
小优化:

虽然在当前的视频看来,这样就已经能够满足我们的要求,但是,凡事都有例外!当我后期发现以下的情况的时候:Python爬虫:AGE动漫下载之 requests 版
Python爬虫:AGE动漫下载之 requests 版
总结:

  • 1、每一集的第二次请求又有两种样式。
  • 2、同一个视频中不同集数的返回数据有些是列表,有些又是直接返回的链接 所以我再次做了一点小改进:
def get_relurl(name):
    global referer
    referer = video_url[name]
    html = request(video_url[name]).text
    time.sleep(random.random())
    all_li = parsel.Selector(html).xpath('//div[@id="plays_list"]/ul/li').extract()
    for info in tqdm(all_li, desc='正在获取视频链接:'):
        li = BeautifulSoup(info, 'html.parser')
        url = 'http://agefans.org/myapp/_get_ep_plays?{}&anime_id={}'.format(li.a.attrs['href'].split('_', 1)[-1].replace('_', '='), video_url[name].split('/')[-1])
        referer = video_url[name] + li.a.attrs['href']
        ID=request(url).json()['result'][0]['id']
        try:
            new_url = 'http://agefans.org/myapp/_get_e_i?url={}&quote=1'.format(ID)
            episodes_url[li.a.string.replace(' ', '')] = unquote(request(new_url).json()['result'])
        except:
            try:
                new_url = 'http://agefans.org/myapp/_get_mp4s?id={}'.format(ID)
                url_lists = request(new_url).json()
                if url_lists:
                    episodes_urls[li.a.string.replace(' ', '')] = url_lists
                else:
                    new_url = 'http://agefans.org/myapp/_get_raw?id={}'.format(ID)
                    episodes_urls.setdefault(li.a.string.replace(' ', ''), []).append(request(new_url).text)
                print(episodes_urls)
            except Exception as e:
                print(type(e), e)
提示:

episodes_urls.setdefault(li.a.string.replace(’ ', ‘’), []).append(request(new_url).text) 的解释:
1、episodes_urls :一个空字典。
2、li.a.string.replace(’ ', ‘’) :获取到的集数名。
3、dict.setdefault(key, default=None)

  • 参数:
    key – 查找的键值。
    default – 键不存在时,设置的默认键值。
  • 返回值:
    如果字典中包含有给定键,则返回该键 对应的值,否则返回为该键 设置的值。

4、episodes_urls.setdefault(key, []).append(value) :向 episodes_urls 字典中 key 对应的值(已设置为列表类型)中追加值 value

视频下载:

到目前为止,已经成功获取了所有的视频链接,思路为判断获取到的视频链接中是否有列表,若没有列表则直接遍历下载。 若有列表则需要判断是否存在当前集数的视频,若存在则在集数后加上一个 “1”

1、episodes_url :保存着没有分段的视频(无列表)。
2、episodes_urls :保存着有分段的视频(有列表)。

def video_download():
    global rel_path
    proxies = {'HTTP': random.choice(proxy)}
    if episodes_url:
        for name in tqdm(episodes_url, desc='正在下载: '):
            r = requests.get(episodes_url[name], proxies=proxies)
            with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                f.write(r.content)
    else:
        for name in tqdm(episodes_urls, desc='正在下载: '):
            for epi_url in episodes_urls[name]:
                if os.path.exists(rel_path + '/' + name + '.mp4'):
                    r = requests.get(epi_url, proxies=proxies)
                    with open(rel_path + '/' + name + '1.mp4', 'wb') as f:
                        f.write(r.content)
                else:
                    r = requests.get(epi_url, proxies=proxies)
                    with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                        f.write(r.content)

求大佬赐教

问题描述:在有列表的情况下,遍历下载时,起初我想用我之前写的一个方法(详情请见:Python 爬虫用最普通的方法爬取ts文件并合成为mp4格式),起初我的代码如下:改动的部分我用 “#” 隔出来了。

def video_download():
    global rel_path
    proxies = {'HTTP': random.choice(proxy)}
    if episodes_url:
        for name in tqdm(episodes_url, desc='正在下载: '):
            r = requests.get(episodes_url[name], proxies=proxies)
            with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                f.write(r.content)
    else:
        for name in tqdm(episodes_urls, desc='正在下载: '):
            for epi_url in episodes_urls[name]:
                if os.path.exists(rel_path + '/' + name + '.mp4'):
                    r = requests.get(epi_url, proxies=proxies)
                    ##########################################################
                    with open(rel_path + '/' + name + '.mp4', 'ab') as f:
                    ##########################################################
                        f.write(r.content)
                else:
                    r = requests.get(epi_url, proxies=proxies)
                    with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                        f.write(r.content)

但是这样到的结果是:视频的 大小改变了,但是播放时还是 只有第一段视频的时间长度! 到这里我就突然迷茫了~~~
Python爬虫:AGE动漫下载之 requests 版


实例源码及结果

import os
import time
import parsel
import random
import requests
from tqdm import tqdm
from bs4 import BeautifulSoup
from urllib.parse import unquote

referer = 'http://agefans.org/'
header = {
    'Cookie': '__cfduid=d9216f203c70cad8785fd8f10dcf2fb5d1594952490; csrftoken=OBJWjqkshWbI1IGirAjQqa9IKy9KOBp9T1a16N6GrA5qTcTdc4azooNitiumjxYA',
    'Host': 'agefans.org',
    'Referer': referer,
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 Edg/84.0.522.40'
}
proxy = ['HTTP://113.195.225.104:9999', 'HTTP://110.243.6.125:9999', 'HTTP://58.215.201.98:56566',
         'HTTP://115.221.245.165:9999', 'HTTP://110.243.15.112:9999', 'HTTP://115.221.242.162:9999',
         'HTTP://218.59.193.14:38640', 'HTTP://120.198.76.45:41443', 'HTTP://113.128.9.88:9999',
         'HTTP://110.243.31.141:9999', 'HTTP://125.108.84.103:9000', 'HTTP://120.83.101.89:9999',
         'HTTP://113.194.135.166:9999', 'HTTP://118.126.107.41:8118']

path = './Spider'
video_url = {}  # 搜索到的视频信息
episodes_url = {}  # 视频下载地址
episodes_urls = {}  #分段视频链接

def request(url):
    proxies = {'HTTP': random.choice(proxy)}
    try:
        r = requests.get(url, headers=header,proxies=proxies)
        r.raise_for_status()
        r.encoding = 'utf-8'
        time.sleep(2)
        return r
    except Exception as e:
        print('request(url) : Error :' + str(type(e)) + ' : ' + str(e))

def print_info(url):  # 输出搜索到的视频信息
    html = request(url).text
    search_info = parsel.Selector(html).xpath('//*[@id="search_list"]/ul//div[@class="card-body p-2"]').extract()
    Serial = 0
    for info in search_info:
        Serial += 1
        soup = BeautifulSoup(info, 'html.parser')
        video_url[soup.a.h5.string] = 'http://agefans.org' + soup.a.attrs['href']
        print("{:0>2d}:\t《 ".format(Serial) + soup.a.h5.string + ' 》')
        for div in soup.find_all('div', {'class': ''}):
            print('\t' + div.span.string + div.span.next_sibling.next_sibling.string)
        num = 0
        for li in soup.find_all('li'):
            num += 1
            if num % 2 != 0:
                print('\t' + li.span.string + li.span.next_sibling.next_sibling.string, end='')
            else:
                print('\t' + li.span.string + li.span.next_sibling.next_sibling.string)
        intro = soup.find('div', {'class': 'ellipsis_summary catalog_summary small'})
        try:
            print('\t' + intro.span.string + intro.span.next_sibling.next_sibling.string.replace('\n', '') + '...')
        except:
            content = str(intro.span.next_sibling.next_sibling).replace('\n', '').replace('<span>', '').replace(
                '<br/>', '').replace('</span>', '').replace('&lt;', '') + '...'
            print('\t' + intro.span.string + content)
        print('\n' + '=' * 30 + '\n')

def get_relurl(name):
    global referer, rel_path
    rel_path = path + '/' + name
    if os.path.exists(rel_path):
        pass
    else:
        os.makedirs(rel_path)
    referer = video_url[name]
    html = request(video_url[name]).text
    time.sleep(random.random())
    all_li = parsel.Selector(html).xpath('//div[@id="plays_list"]/ul/li').extract()
    for info in tqdm(all_li, desc='正在获取视频链接:'):
        li = BeautifulSoup(info, 'html.parser')
        url = 'http://agefans.org/myapp/_get_ep_plays?{}&anime_id={}'.format(li.a.attrs['href'].split('_', 1)[-1].replace('_', '='), video_url[name].split('/')[-1])
        referer = video_url[name] + li.a.attrs['href']
        ID=request(url).json()['result'][0]['id']
        try:
            new_url = 'http://agefans.org/myapp/_get_e_i?url={}&quote=1'.format(ID)
            episodes_url[li.a.string.replace(' ', '')] = unquote(request(new_url).json()['result'])
        except:
            try:
                new_url = 'http://agefans.org/myapp/_get_mp4s?id={}'.format(ID)
                url_lists = request(new_url).json()
                if url_lists:
                    episodes_urls[li.a.string.replace(' ', '')] = url_lists
                else:
                    new_url = 'http://agefans.org/myapp/_get_raw?id={}'.format(ID)
                    episodes_urls.setdefault(li.a.string.replace(' ', ''), []).append(request(new_url).text)
                print(episodes_urls)
            except Exception as e:
                print(type(e), e)

def video_download():
    global rel_path
    proxies = {'HTTP': random.choice(proxy)}
    if episodes_url:
        for name in tqdm(episodes_url, desc='正在下载: '):
            r = requests.get(episodes_url[name], proxies=proxies)
            with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                f.write(r.content)
    else:
        for name in tqdm(episodes_urls, desc='正在下载: '):
            for epi_url in episodes_urls[name]:
                if os.path.exists(rel_path + '/' + name + '.mp4'):
                    r = requests.get(epi_url, proxies=proxies)
                    with open(rel_path + '/' + name + '1.mp4', 'wb') as f:
                        f.write(r.content)
                else:
                    r = requests.get(epi_url, proxies=proxies)
                    with open(rel_path + '/' + name + '.mp4', 'wb') as f:
                        f.write(r.content)

def user_ui():
    print('#' * 25 + '\tAGE动漫离线助手\t' + '#' * 25)
    keyword = input('请输入搜索关键字:')
    url = 'http://agefans.org/search?q=' + keyword
    print_info(url)
    choice = int(input('请输入序号选择:'))
    name = list(video_url.keys())[choice - 1]
    start = time.time()
    get_relurl(name)
    video_download()
    end = time.time()
    print("下载完成!共耗时:{}".format(end - start))

if __name__ == '__main__':
    user_ui()

结果及下载情况:

#########################	AGE动漫离线助手	#########################
请输入搜索关键字:约会
01:	《 约会大作战 》
	原版名称: デート·ア·ライブ 
	其他名称: DATE A LIVE 
	动画种类: TV 	首播时间: 2013-04-05 
	播放状态: 完结 	原作: 橘公司 
	制作公司: AIC PLUS+ 	剧情类型: 搞笑,后宫,校园,奇幻 
	简介: 电视动画《约会大作战》改编自日本轻小说家橘公司原作、Tsunako负责插画的同名系列轻小说。故事是讲述一名普通的高中二年级生五河士道,突然在某一天遇上了一场大爆炸,而在这场大爆炸之中竟然出现一名身穿盔甲手持大剑的神秘美少女。原来这名少女的真正身份是“精灵”,是会给世界带来毁灭性灾难的存在,她的再次出现,将会给地球带来毁灭性的未来!然而主人公士道却有方法阻止世界毁灭,这个唯一能够阻止世界毁灭的方法 ...

==============================

02:	《 约会大作战 第二季 》
	原版名称: デート・ア・ライブⅡ 
	其他名称: DATE A LIVE 2 
	动画种类: TV 	首播时间: 2014-04-11 
	播放状态: 完结 	原作: 橘公司 
	制作公司: Production IMS 	剧情类型: 搞笑,后宫,校园,奇幻 
	简介: 电视动画《约会大作战Ⅱ》是根据日本小说家橘公司著作、つなこ(Tsunako)负责插画的轻小说《约会大作战》改编的同名电视动画的第二期作品。2013622日,由AIC PLUS+制作的第一季第12话末尾宣布了该作二期制作决定的消息。动画公司由AIC PLUS+更换成Production IMS制作。原作小说作者橘公司也在第一时间发了动画二期决定的推文。伴随着空间震荡、拥有毁灭世界力量的精灵。然 ...

==============================
#
#  为节省篇幅,省略一部分
#
==============================

请输入序号选择:1
正在获取视频链接:: 100%|██████████| 13/13 [01:57<00:00,  9.06s/it]
正在下载: 100%|██████████| 13/13 [02:39<00:00, 12.26s/it]
下载完成!共耗时:285.04985427856445

进程已结束,退出代码 0

下载结果拼接示例:
Python爬虫:AGE动漫下载之 requests 版


不知道是不是福利~

由于此次的示例没有任何第三方依赖工具,所以可以把这个示例发送到手机上,随身下载,随时看片:详情请见:Pydroid 3第三方库安装失败的解决方案
Python爬虫:AGE动漫下载之 requests 版

本文地址:https://blog.csdn.net/qq_44700693/article/details/107510787