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

背景

由于之前做视频搬运,需要批量保存某不存在的网站视频(保险起见就不透露具体网址了,胆小嘿嘿~~),这是一个长期而固定的事情,几乎每天都要去关注有没有新的视频然后下载保存,既然如此重复,作为一个开发者,当然要想着有没有什么更省力的方法,虽然是做前端的,还是觉得这个场景用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("Chinesehomemadevideo")
    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的应用或许不是那么精通,如有纰漏,欢迎指出~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容