前两天偶然翻阅了一位blogger的文章,内容是记录了他个人油管的观看记录分析过程。然后我也心血来潮,效仿一番,在本篇文章也简单分析下我自己再youtube的观看历史,分析维度和那位blogger的基本一致(抄袭了别人的idea,惭愧~),这是原文链接。

  曾几何时,还不知道有fanqiang这回事的时候,在网络上观看视频资源都只能局限于几大视频平台。后来学会了用别人的飞机场,再后来学会了自己搭梯子。慢慢地才得以打开围墙外世界的一道道大门,而youtube(油管)就是其一。

  从两年前开始,我已经很少使用国内的几大视频平台了,其中包括抖音,腾讯视频,爱奇艺,优酷土豆等等主流平台,但还是保留观看bilibili和网易新闻客户端的视频习惯。当然,电影、电视剧这些类型的“长视频”并不在本文所指的视频资源范围内。

之所以开始放弃使用国内这些平台,主要是出于以下两个原因考虑:

  • 视频内容质量堪忧。伴随着近两年国内自媒体大火,大多平台的视频内容来源于这些水平参差不齐的自媒体人,所以可以想象视频内容的营养价值有多少水分。做自媒体门槛并不高,稍微有点拍摄设备和会点剪辑技能就能入门了。虽然我并未接触过自媒体相关的工作,但像拍摄视频这样丰富的内容承载媒介,要想输出足够有观看价值的内容,我觉得并不是一件简单的事情;

  • 内容同质化严重。这个不用多说了,互相抄袭idea、一窝蜂抢舆论热点、甚至于直接照搬他人视频等现象太多了;

  截至目前,油管算是我视频资源观看量最大的平台了,因为油管视频内容丰富,总能找到感兴趣的video或channel。以下篇幅大致记录了在分析过程中使用到的一些主要工具,以及有几个基于不同分析维度的svg图表。

准备工具

  • Python3。用于编写获取视频信息、过滤并保存待分析数据、以及生成可视化图表的脚本,当然完全可以使用其他语言来完成这部分工作,但whatever,毕竟人生苦短,我用Python;[狗头]~

  • sqlite3。用于保存过滤后的待分析数据,做数据的持久化是为了方便后面生成可视化图表;

  • chrome。用于渲染展示svg格式的图表,当然也可以使用其他可展示svg格式图表的工具;

准备观看历史数据

  • 在谷歌提供的此工具页面可以导出在油管的视频观看历史数据;链接:Google Tokeout

  • 前面获取的观看历史数据只是一些基础数据,还需要另外获取每个视频的其他属性,这样才可以有更多维度做分析,有个开源库youtube-dl 提供了获取油管视频更多属性信息的接口,在此库基础上我们就可以做更多感兴趣维度的分析了;

感兴趣的几个分析维度

  • 2019年每个月观看的视频量情况

  • 2019年每天24小时的视频观看量的分布情况

  • 观看量最多的前八个channel及其视频观看量;

  • 观看的视频时间长度的分布情况;

基于sqlite3数据库的数据表创建

CREATE TABLE `channel` ("channel_id" TEXT  NOT NULL PRIMARY KEY,"name" TEXT NOT NULL,"view_count" INTEGER
);CREATE TABLE `video` ("video_id" TEXT  NOT NULL PRIMARY KEY,"title" TEXT NOT NULL,"video_url" TEXT NOT NULL,"channel_id" TEXT  NOT NULL,"duration" INTEGER
);CREATE TABLE `history` ("history_id" INTEGER PRIMARY KEY AUTOINCREMENT,"video_id" TEXT  NOT NULL,"timestamp" TEXT NOT NULL
);

用于清洗、保存待分析数据的python脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-import youtube_dl
import json
import sqlite3
from datetime import datetime
import re
import os
import logging
import threading
import time
from dateutil import parser
from collections import defaultdictlogging.getLogger().setLevel(logging.INFO)db_conn = sqlite3.connect(database=os.path.join(os.path.dirname(__file__), "youtube.db"))
video_base_url = "http://www.youtube.com/watch?v={}"def add_channel(channel_id, channel_name):c = db_conn.cursor()c.execute("INSERT OR IGNORE INTO channel (channel_id, name) VALUES (?, ?)", (channel_id, channel_name))def update_channel_view_count(channel_id, view_count):c = db_conn.cursor()c.execute("UPDATE channel SET view_count=? WHERE channel_id=? ", (view_count, channel_id))def add_video_record(video_id, title, video_url, channel_id, duration):c = db_conn.cursor()c.execute("INSERT OR IGNORE INTO video (video_id, title, video_url, channel_id, duration) VALUES (?, ?, ?, ?, ?)", (video_id, title, video_url, channel_id, duration))def add_history(video_id, timestamp):c = db_conn.cursor()c.execute("INSERT OR IGNORE INTO history (video_id, timestamp) VALUES (?, ?)", (video_id, timestamp))def get_channel_id(url: str):if not url:return ""pattern = re.compile(r"^\S+/channel/(\S+)$")res = re.findall(pattern, url)if res:return res[0]return ""def get_video_id(url: str):if not url:return ""pattern = re.compile(r"^\S+/watch\?v=(\S+)$")res = re.findall(pattern, url)if res:return res[0]return ""def download_from_ydl(ydl: youtube_dl.YoutubeDL, video_url: str, cache_file_path: str):try:result = ydl.extract_info(video_url, download=False)except Exception as e:logging.exception(str(e))result = {}with open(cache_file_path, "w") as f:json.dump(result, f)def get_duration(ydl: youtube_dl.YoutubeDL, video_id: str):video_url = video_base_url.format(video_id)cache_file_path = os.path.join(os.path.dirname(__file__), "cache", video_id + ".json")if not os.path.exists(cache_file_path):logging.info("download video info: {}".format(cache_file_path))download_from_ydl(ydl, video_url, cache_file_path)duration = -1with open(cache_file_path, "r") as f:data = json.load(f)duration = data.get("duration") if data else -1return durationdef handle(history: dict, ydl: youtube_dl.YoutubeDL, channel_view_counts: dict):if "subtitles" not in history or "titleUrl" not in history:returnvideo_id = get_video_id(history.get("titleUrl"))title = history.get("title", "").replace("Watched ", "")logging.info("current item title: {}".format(title))channel_id = ""for subtitle in history["subtitles"]:channel_id = get_channel_id(subtitle.get("url"))                add_channel(channel_id, subtitle.get("name", ""))channel_view_counts[channel_id] += 1duration = get_duration(ydl, video_id)add_video_record(video_id, title, history.get("titleUrl"), channel_id, duration)timestamp = int(parser.parse(history["time"]).timestamp())add_history(video_id, timestamp)returndef batch_add_channel_view_count(channel_count: dict):if not channel_count:returnfor channel_id, view_count in channel_count.items():logging.info("current channel id:{}, view count:{}".format(channel_id, str(view_count)))update_channel_view_count(channel_id, view_count)db_conn.commit()returndef batch_download_video_info(ydl: youtube_dl.YoutubeDL, video_id: str):video_url = video_base_url.format(video_id)cache_file_path = os.path.join(os.path.dirname(__file__), "cache", video_id + ".json")if not os.path.exists(cache_file_path):logging.info("batch download video info: {}".format(cache_file_path))download_from_ydl(ydl, video_url, cache_file_path)time.sleep(1)logging.info("finish batch download")def main():logging.info("========= start work =========")ydl = youtube_dl.YoutubeDL({"outtmpl": "%(id)s%(ext)s"})threads = []history_file_path = os.path.join(os.path.dirname(__file__), "history.json")with open(history_file_path, "r", encoding="utf-8") as f:historys = json.load(f)for history in historys:video_id = get_video_id(history.get("titleUrl"))t = threading.Thread(target=batch_download_video_info, args=(ydl, video_id))t.start()threads.append(t)for t in threads:t.join()channel_view_counts = defaultdict(int)for history in historys:handle(history, ydl, channel_view_counts)db_conn.commit()batch_add_channel_view_count(channel_view_counts)db_conn.close()logging.info("========= finish work =========")if __name__ == "__main__":main()

用于输出svg格式图表的python脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-import sqlite3
import matplotlib.pyplot as pplt
import osdb_conn = sqlite3.connect(database=os.path.join(os.path.dirname(__file__), "youtube.db"))def plot_yearly_data():x, y = [], []c = db_conn.cursor()c.execute("""SELECTSTRFTIME('%Y', DATE(timestamp, 'unixepoch')) AS year,COUNT(1)FROMhistoryGROUP BY 1;""")result = c.fetchall()for item in result:x.append(item[0])y.append(item[1])pplt.bar(x, y)pplt.xlabel("Year")pplt.ylabel("Video")pplt.title("Yearly Watched Videos")pplt.legend()def plot_watch_hour():x, y = [], []c = db_conn.cursor()c.execute("""SELECTSTRFTIME('%Y', DATETIME(timestamp, 'unixepoch', 'localtime')) AS year,STRFTIME('%H', DATETIME(timestamp, 'unixepoch', 'localtime')) AS hour,COUNT(1)FROM history GROUP BY 1, 2 HAVING year = '2019';""")result = c.fetchall()for item in result:x.append(int(item[1]))y.append(int(item[2]))pplt.bar(x, y)pplt.xlabel("Hour")pplt.ylabel("Video")pplt.title("Watched In Hour (2019) ")pplt.legend()def plot_top_five():x, y = [], []c = db_conn.cursor()c.execute("""SELECTname,view_countFROM channel ORDER BY view_count DESC LIMIT 8;""")result = c.fetchall()for item in result:x.append(str(item[0]).encode(encoding="utf-8"))y.append(int(item[1]))pplt.bar(x, y)pplt.ylim(0, 120)pplt.xlabel("Channel Name")pplt.ylabel("View Count")pplt.title("Channel View Count (2019) ")pplt.legend()def plot_video_length():x, y = [], []c = db_conn.cursor()c.execute("""SELECT duration / 60, COUNT(1) FROM video WHERE duration > 0GROUP BY 1;""")result = c.fetchall()for item in result:x.append(int(item[0]))y.append(int(item[1])) pplt.plot(x, y)pplt.xlim(0, 120)pplt.xlabel("Video Length (Unit: Minute)")pplt.ylabel("Video Count")pplt.title("Video Length Distribution")pplt.legend()def main():pplt.figure(figsize=(12, 6))pplt.rcParams['font.sans-serif']=['SimHei']pplt.subplot(2, 1, 1)plot_yearly_data()pplt.subplot(2, 1, 2)plot_watch_hour()pplt.subplots_adjust(wspace=0.2, hspace=0.8)filepath = os.path.join(os.path.dirname(__file__), "plot", "graghs_one.svg")pplt.savefig(filepath, format="svg")pplt.clf()pplt.subplot(1, 1, 1)plot_top_five()filepath = os.path.join(os.path.dirname(__file__), "plot", "graghs_two.svg")pplt.savefig(filepath, format="svg")pplt.clf()pplt.subplot(1, 1, 1)plot_video_length()filepath = os.path.join(os.path.dirname(__file__), "plot", "graghs_three.svg")pplt.savefig(filepath, format="svg")db_conn.commit()db_conn.close()if __name__ == "__main__":main()

可视化图表展示及分析

  图二数据可以看出,每天的观看时间主要集中分布在三个时间段。中午13点左右是因为上班的午休时间,吃完饭准备午睡前总会喜欢看上几部视频。另外一个比较突出的是晚上12点时间段,侧面可以看出自己的作息时间还算是比较晚的,希望在未来的一年在作息习惯上自己能有所改善。

  图三为观看油管视频以来观看数量排前八的channel,大致涵盖了科普、潮流、游戏、电影这几个内容领域,这个名单大致能体现最近一段时间我比较感兴趣的不同内容领域的channel。

  图四可以看出观看过的视频大部分分布在20分钟以下,其中10分钟长度左右的视频数量占比最大。

总结

  • 相比国内那些发展还未成熟的视频平台,油管能让我汲取到更多有价值的知识,获取到更多有用的资讯,所以未来我还会一直保持使用油管的习惯;

  • 这次搜集到的数据其实还能用来做更多其他有趣的维度分析,例如看过的视频标签分布情况、视频热度分布情况、点赞最多的视频名单、dislike最多的视频名单等等。这次先点到为止,留些TODO下次来完成;

个人youtube视频观看历史分析相关推荐

  1. 【Hadoop】YouTube 视频数据集分析实验 (原理+过程+代码)

    一.实验背景 随着近年来视频拍摄设备与视频处理技术的高速发展,对网络上海量视频的分析越来越受到关注与重视.本实验希望通过使用 Hadoop 实验数据集 -- Dataset for "Sta ...

  2. 在Firefox中以电影院风格观看YouTube视频

    Do you like watching lots of videos at YouTube but are tired of the small video size and abundant ba ...

  3. Hadoop实战系列之MapReduce 分析 Youtube视频数据

    Hadoop实战系列之MapReduce 分析 Youtube视频数据 一.实战介绍 MapReduce 是 Hadoop 的计算框架. 在运行一个 MR 程序时,任务过程被分为两个阶段:Map 阶段 ...

  4. 关于B站(bilibili)对未登录用户视频观看进行暂停和弹窗的分析与简单解决方案

    关于B站(bilibili)对未登录用户视频观看进行暂停和弹窗的分析与简单解决方案 情况介绍 简要分析 初步的解决方案 总结(太长不看点这里) 情况介绍 于近日的某次更新后,B站(bilibili)网 ...

  5. 如何在Plex上观看YouTube视频

    You've added all your media to a Plex server and it's great, but now you want to watch your favorite ...

  6. 在Python中使用Seaborn和WordCloud可视化YouTube视频

    I am an avid Youtube user and love watching videos on it in my free time. I decided to do some explo ...

  7. 爱奇艺视频与腾讯视频竞品分析

    随着视频直播业的火爆,市场上视频直播的APP也层出不穷,这些APP主拼的内容和资源,更需进一步推动用户付费习惯的养成.从用户关注因素出发,以用户体验的多方面的校对市场上热门视频直播类APP进行对比分析 ...

  8. hive尚硅谷实战案例统计youtube视频热度

    hive视频热度统计案例 文章目录 hive视频热度统计案例 背景及需求描述 项目的完成 1. 数据清洗 (1) maven依赖 (2)ETLUtils-处理具体的数据清洗逻辑 (3)ETLMappe ...

  9. Hive项目之谷粒影音:ETL清洗原数据、Hive统计视频观看数top10、视频类别top、视频观看数top其所属类别、类别流量top、类别热度top、上传视频用户数量top、类别视频观看top

    Hive实战之谷粒影音 项目数据下载地址: guiliVideo.zip谷粒影音项目视频表.用户表 包含内容: 两个文件夹 User表中的74702条数据 video表中5张表,每张表中都有多条数据 ...

  10. 爱奇艺、优酷、腾讯视频竞品分析报告2016(二)

    接上一篇<爱奇艺.优酷.腾讯视频竞品分析报告2016(一)> http://milkyqueen520.blog.51cto.com/11233158/1760192 2.4 产品设计与交 ...

最新文章

  1. Alphabet wars - nuclear strike--5 kyu--Python解法
  2. MySQL中的联合索引学习教程
  3. 如何对局域网内的无线设备进行管理和流控?
  4. android bootloader阶段GPIO的控制
  5. 这个程序员如何以一己之力阻止了 Bug 代码的提交并改变整个 DevOps 世界?
  6. ASP.NET Compilation and Deployment
  7. StegaStamp:加州大学伯克利分校开源神奇的照片隐写术,打印的照片能当二维码用...
  8. 大牛用emacs还是vim_Emacs,Vim还是其他?
  9. 12306 辟谣用户信息被卖;比特大陆两 CEO 均卸任?苹果又被起诉 | 极客头条
  10. eclipse java开发实例_eclipse+webservice开发实例
  11. Clojure 学习入门(3)- 数字类型
  12. Mac电脑卡在启动模式了怎么办?
  13. WebStorm破解激活
  14. 被欧美公司垄断的工业软件,中国还有机会吗?
  15. itextpdf 更换字体
  16. pycharm显示中间变量
  17. android 测量距离 app,手机测距软件哪个好?6款手机测距APP推荐
  18. mysql执行计划详解
  19. 转载文章-【工具】10分钟快速搭建属于自己的文档网站-来自掘金
  20. javaScript面试高频技术点(多为原生基础+框架集合)

热门文章

  1. mapreduce详细工作流程
  2. SM9学习笔记与图解(合集)
  3. word 引文 角标_如何自动向Microsoft Word添加引文和书目
  4. 苹果4s变php服务器,苹果4S改装无线充电
  5. python生成word文档有哪些库_python实现生成word文档并转为pdf
  6. 三星推出体积更小的1亿像素图像传感器产品线
  7. 基于Matlab的随机森林算法实现(附算法介绍及代码详解)
  8. Python—Django中的视图(views.py)
  9. C++通信录管理系统
  10. 北京二手房价10月微涨 业内:坚持限购就不会大涨