个人youtube视频观看历史分析
前两天偶然翻阅了一位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视频观看历史分析相关推荐
- 【Hadoop】YouTube 视频数据集分析实验 (原理+过程+代码)
一.实验背景 随着近年来视频拍摄设备与视频处理技术的高速发展,对网络上海量视频的分析越来越受到关注与重视.本实验希望通过使用 Hadoop 实验数据集 -- Dataset for "Sta ...
- 在Firefox中以电影院风格观看YouTube视频
Do you like watching lots of videos at YouTube but are tired of the small video size and abundant ba ...
- Hadoop实战系列之MapReduce 分析 Youtube视频数据
Hadoop实战系列之MapReduce 分析 Youtube视频数据 一.实战介绍 MapReduce 是 Hadoop 的计算框架. 在运行一个 MR 程序时,任务过程被分为两个阶段:Map 阶段 ...
- 关于B站(bilibili)对未登录用户视频观看进行暂停和弹窗的分析与简单解决方案
关于B站(bilibili)对未登录用户视频观看进行暂停和弹窗的分析与简单解决方案 情况介绍 简要分析 初步的解决方案 总结(太长不看点这里) 情况介绍 于近日的某次更新后,B站(bilibili)网 ...
- 如何在Plex上观看YouTube视频
You've added all your media to a Plex server and it's great, but now you want to watch your favorite ...
- 在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 ...
- 爱奇艺视频与腾讯视频竞品分析
随着视频直播业的火爆,市场上视频直播的APP也层出不穷,这些APP主拼的内容和资源,更需进一步推动用户付费习惯的养成.从用户关注因素出发,以用户体验的多方面的校对市场上热门视频直播类APP进行对比分析 ...
- hive尚硅谷实战案例统计youtube视频热度
hive视频热度统计案例 文章目录 hive视频热度统计案例 背景及需求描述 项目的完成 1. 数据清洗 (1) maven依赖 (2)ETLUtils-处理具体的数据清洗逻辑 (3)ETLMappe ...
- Hive项目之谷粒影音:ETL清洗原数据、Hive统计视频观看数top10、视频类别top、视频观看数top其所属类别、类别流量top、类别热度top、上传视频用户数量top、类别视频观看top
Hive实战之谷粒影音 项目数据下载地址: guiliVideo.zip谷粒影音项目视频表.用户表 包含内容: 两个文件夹 User表中的74702条数据 video表中5张表,每张表中都有多条数据 ...
- 爱奇艺、优酷、腾讯视频竞品分析报告2016(二)
接上一篇<爱奇艺.优酷.腾讯视频竞品分析报告2016(一)> http://milkyqueen520.blog.51cto.com/11233158/1760192 2.4 产品设计与交 ...
最新文章
- Alphabet wars - nuclear strike--5 kyu--Python解法
- MySQL中的联合索引学习教程
- 如何对局域网内的无线设备进行管理和流控?
- android bootloader阶段GPIO的控制
- 这个程序员如何以一己之力阻止了 Bug 代码的提交并改变整个 DevOps 世界?
- ASP.NET Compilation and Deployment
- StegaStamp:加州大学伯克利分校开源神奇的照片隐写术,打印的照片能当二维码用...
- 大牛用emacs还是vim_Emacs,Vim还是其他?
- 12306 辟谣用户信息被卖;比特大陆两 CEO 均卸任?苹果又被起诉 | 极客头条
- eclipse java开发实例_eclipse+webservice开发实例
- Clojure 学习入门(3)- 数字类型
- Mac电脑卡在启动模式了怎么办?
- WebStorm破解激活
- 被欧美公司垄断的工业软件,中国还有机会吗?
- itextpdf 更换字体
- pycharm显示中间变量
- android 测量距离 app,手机测距软件哪个好?6款手机测距APP推荐
- mysql执行计划详解
- 转载文章-【工具】10分钟快速搭建属于自己的文档网站-来自掘金
- javaScript面试高频技术点(多为原生基础+框架集合)