前两天偶然翻阅了一位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 defaultdict
logging.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 ""
p