likes
comments
collection
share

使用python爬虫实现网页视频自动下载并保存

作者站长头像
站长
· 阅读数 59

背景

由于之前做视频搬运,需要批量保存某不存在的网站视频(保险起见就不透露具体网址了,胆小嘿嘿~~),这是一个长期而固定的事情,几乎每天都要去关注有没有新的视频然后下载保存,既然如此重复,作为一个开发者,当然要想着有没有什么更省力的方法,虽然是做前端的,还是觉得这个场景用python来搞定最合适,在此记录下解决的过程。

我的日常场景是浏览视频标题和视频封面,来大致判断这个视频是不是值得下载(每个视频都预览一遍太慢了),但是时间久了,发现过程中有以下痛点:

痛点

  • 网站自带的保存按钮访问路径比较长,点击去保存又返回继续挑选效率很低。
  • 最烦恼的是命名问题,网站保存视频文件名是编号,要是标题就好了,保存久了我每次都回忆不起来视频是什么内容。
  • 由于需要代理访问,下载比较慢,同时下载又边浏览找视频就会很卡,想把查找和下载分离。

根据上面痛点想出的解决方案:

  • 使用python爬取页面,然后自动下载视频,但是这样会有一个问题,我并不是需要所有视频,有些是垃圾视频不需要下载,python似乎还没法做到识别我对哪些视频是有价值的,所以这一步人工绕不开,我选择直接把目标视频的详情页直接右键另存为本地html文件,随后再用python批量处理这些本地的html文件。
  • 解析html文件取出视频的标题和下载url,按格式存为本地json
  • 读取json,循环下载整个list的视频,并按配置给视频命名

目标功能点

  • 递归读取html列表
  • 研究html内格式、解密url
  • 保存解析产物为json
  • 读取json下载保存视频

功能点代码

递归读取html目录

这一步可以将每一个html文件的路径从Downloads文件夹取出来。

def get_all_path():
    global train_path, all_path, labels
    train_path, all_path, labels = "./Downloads", [], []

    # 递归获取文件夹内文件列表
    def get_label_and_wav(path, file):
        dirs = os.listdir(path)
        for a in dirs:
            # print(a)
            # # 是否为文件夹
            # print(os.path.isfile(path + "/" + a))
            if os.path.isfile(path + "/" + a):
                all_path.append(dirs)
                if file != "":
                    labels.append(file)
            else:
                get_label_and_wav(str(path) + "/" + str(a), a)
            # 循环遍历这个文件夹

        return all_path, labels

    # 第一步、加载文件,获取文件路径以及标签
    [all_path, labels] = get_label_and_wav(train_path, "")
    loop_parse_html(paths=all_path[0])

循环html文件列表

有一个小坑是python生成json会自动使用单引号,不符合json标准,需要做下ensure_ascii配置,见注释

# 循环html路径列表,解析每一个链接和标题存入json
def loop_parse_html(paths):
    global res_json
    res_json = []
    for index in range(len(paths)):
        v = paths[index]
        v_len = len(v)
        # 当前后缀四位
        v_end = v[v_len - 4:len(v)]
        # 跳过非html结尾文件
        if v_end == "html":
            parse_html(position=index, path=f"{train_path}/{paths[index]}")
            sleep(0.1)
    # 这里将ensure_ascii配置为false,不然会强制存为单引号格式的json,不符合标准,也会导致中文是编码显示
    res_json = json.dumps(res_json, ensure_ascii=False)
    # 创建一个本地json文件存入结果
    file = open('urls.json', 'w')
    file.write(str(res_json))
    file.close()

重点,解析单个html文件

实际上这里也可以直接用request库从网络爬取,区别不大,只是一个取本地一个取请求,本质结果都是返回一个html文件传入xpath返回结构化的html对象而已,也就是下面的html_tree,由于我的场景特殊需要筛选,才用这种本地html的形式。

另外通过分析html内容,发现下载链接是一个js生成的a标签的前部分,也就是类似于下方,关键的href链接是被编码过的,也需要处理下。

<a href="http://21312jhg312@#$@#$@#$">

编码就是前端的enencodeURIComponent,查了下python里可以使用unquote库来解码

from urllib.parse import unquote  # url解码库,类似于js里的decodeURIComponent

注:xpath是一个流行的html解析库,不过在此处也需要特殊处理,留意代码注释的标签闭合问题,通过自己创建一个html解析器来解决xpath对未闭合标签不识别的问题。

# 解析单个html
def parse_html(position, path):
    position = position + 1
    print(f'开始第{position}次解析:{path}')
    # 因为现在html存在未闭合的标签,如title标签,xpath解析会报错,因为闭合不完整
    # 所以这里自己创建一个html解析器parser
    parser = etree.HTMLParser(encoding="utf-8")
    # 使用etree解析返回一个html结构对象
    html_tree = etree.parse(path, parser=parser)
    # 文本形式可直接打印的html,此处暂时不用,用xpath结构化解析
    # result = etree.tostring(html_tree, pretty_print=True, encoding="utf-8").decode("utf-8")
    # print(result)
    print(f'解析下载链接')
    # 通过解析获取目标视频链接a标签,实际是一个a标签被加密了
    aim_video_url = html_tree.xpath('// *[ @ class = "demo-class"] / script / text()')[0]
    # 再次处理链接格式
    # 找到a标签首位,截取出来
    a_start = aim_video_url.find('("')
    a_end = aim_video_url.find('")')
    aim_video_url = aim_video_url[a_start + 2:a_end]
    # 解码a标签
    aim_video_url = unquote(aim_video_url)
    # 取出a标签的href属性就是下载链接了,这里是字符串就用截取了,不用xpath解析
    href_start = aim_video_url.find('https')
    href_end = aim_video_url.find('>')
    aim_video_url = aim_video_url[href_start:href_end]
    print(f"链接解析完成:{aim_video_url}")
    print(f'解析标题')
    # 目标标题
    aim_title = html_tree.xpath('/html//title/text()')[0]
    # 处理下标题格式,去掉无用的字符、空格、回车换行等
    aim_title = aim_title.replace(" ", "")
    aim_title = aim_title.replace("\n", "")
    useless_str_start = aim_title.find("powerbyxxx")
    aim_title = aim_title[0:useless_str_start]
    print(f"标题解析完成:{aim_title}\n")
    write_json_file(title=aim_title, link=aim_video_url)

存入全局的json变量,在最开始的循环html列表结束后,json变量会被写入本地json文件

# 将标题和下载链接按键值对存入json文件,供下载脚本读取
def write_json_file(title, link):
    temp = {
        "title": f"{title}",
        "link": f"{link}"
    }
    res_json.append(temp)

进入下载保存流程

这一块其实比较简单

读取json本地配置

def get_video(index):
    if index > len(json_res) - 1:
        print(f'下载完毕,一共下载了{index}次')
        quit(200)
    else:
        print(f'开始第{index + 1}次下载')
        save_video(index)


def main():
    with open('urls.json', encoding='utf-8') as res:
        print('开始读取json配置')
        # 把json配置读出来保存到全局变量
        global json_res
        json_res = json.load(res)
        get_video(index=0)

循环下载,组合文件名将视频存入指定目录

这里做了一个简单的请求时间随机发起

def save_video(index):
    url = json_res[index].get("link")
    title = json_res[index].get("title")
    video_content = requests.get(url).content
    with open('./output/' + title + '.mp4', 'wb') as f:
        f.write(video_content)
    print(f"第{index + 1}次下载完成\n")
    ran = random.randint(0, 10)+random.randint(0, 10)/10+random.randint(0, 10)/100
    # 随机休息1-10秒两位小数的时间
    print(f'休息{ran}秒~')
    # sleep(ran)
    get_video(index + 1)

到这一步就全部完成

结语

我作为前端对python的应用或许不是那么精通,如有纰漏,欢迎指出~

转载自:https://juejin.cn/post/7160579675050213390
评论
请登录