0 说明

例子原文来自Kaggle,本文会以原文例子为主线进行展开。

主要的点在于将非结构化数据(文本描述)进行向量化,然后通过图的结构关系做推荐。

1 数据

数据可以到这里下载(Kaggle),或者也可以在这里下载(CSDN), 文件不大,大概2.3M左右。
数据一共6234行,12列。

列名 含义
show_id 每部电影的唯一ID
type N,分类变量,电影Movie Or 电视剧TV
title 电影或电视剧的名称,可能不唯一
director 导演
cast 演员名单
country 原产国
date_added Netflix上架日期
release_year 影片发行年份
rating 电影的评分
duration 片长
listed_in 频道/分类
description 内容简介

2 数据分析

先读入数据看一看(DataManipulation 是自定义的函数包)

import pandas as pd
import numpy as np
import DataManipulation as dm # 1 读入数据
df = pd.read_csv('netflix_titles.csv')df.shape
# (6234, 12)# 2 观察元数据
df_meta = dm.view_df_varattr(df)


可以看到,因为变量主要都是非结构化的,例如description就是文本型的,因此基于表结构的处理方法可能没什么用。

2.1数据分析可视化

以下是用pyecharts 进行简单的一些可视化,先占个位,后续看看需要增加哪些。

2.1.1 时间轴变化函数(单数据源)

from pyecharts import options as opts
from pyecharts.charts import Bar, Timeline
from pyecharts.commons.utils import JsCode
# ---封装函数
# timerange: 2001,2002 ...
# xcols: colA, colB
# filename: xxx.html
# path = ./xxx/xxx/
def Draw1SourceCategoryViaTime(df, source_name,timerange,tcol,xcols, title,filename, path='./'):tl = Timeline()timerange = [int(x) for x in timerange]for i in timerange:# df1_summary是按照年份汇总的数据tem_dict = dict(df[df[tcol] == i])vals = []for x in xcols:vals.append(int(tem_dict[x]))bar = (Bar().add_xaxis(xcols).add_yaxis(source_name, vals)# .add_yaxis("商家B", vals2).set_global_opts(title_opts=opts.TitleOpts("{0}{1}".format(i,title)),graphic_opts=[opts.GraphicGroup(graphic_item=opts.GraphicItem(rotation=JsCode("Math.PI / 4"),bounding="raw",right=100,bottom=110,z=100,),children=[opts.GraphicRect(graphic_item=opts.GraphicItem(left="center", top="center", z=100),graphic_shape_opts=opts.GraphicShapeOpts(width=400, height=50),graphic_basicstyle_opts=opts.GraphicBasicStyleOpts(fill="rgba(0,0,0,0.3)"),),opts.GraphicText(graphic_item=opts.GraphicItem(left="center", top="center", z=100),graphic_textstyle_opts=opts.GraphicTextStyleOpts(text="{0}{1}".format(i,title),font="bold 26px Microsoft YaHei",graphic_basicstyle_opts=opts.GraphicBasicStyleOpts(fill="#fff"),),),],)],))tl.add(bar, "{}".format(i))tl.render(path+filename)print('File Saved To ', path+filename)return True# ------------------ 调用该函数的代码
# -> TimeLine 可视化
## 制作汇总数据
keep_cols = ['year','month','type']
df1 = df[keep_cols].dropna()
df1['YearMonth'] = df1['year'].apply(int).apply(str) + df1['month'].apply(int).apply(str).apply(lambda x: '0'+x if len(x) ==1 else x)df1_summary = df1.groupby(['YearMonth','type']).size().unstack()
df1_summary1 = df1.groupby(['year', 'type']).size().unstack().fillna(0).reset_index()from draw_func1 import Draw1SourceCategoryViaTime
## Time Pyecharts
source_name = 'Netflix'
timerange = range(2010,2020)
tcol = 'year'
xcols = ['Movie','TV Show']
title = '电影/电视剧数量'
filename= 'Netflix Movies.html'Draw1SourceCategoryViaTime(df1_summary1, source_name, timerange,tcol,xcols,title,filename)------------------------------
In [90]: df1_summary1.head()
Out[90]:
type    year  Movie  TV Show
0     2008.0    1.0      1.0
1     2009.0    2.0      0.0
2     2010.0    1.0      0.0
3     2011.0   13.0      0.0
4     2012.0    4.0      3.0

效果:

2.1.2 水平轴滑动条

如果只是单一产品的时间轴展示,用这个比时间轴变化函数要好(后者更适合与多数据源,多产品的时间比较)

from pyecharts import options as opts
from pyecharts.charts import Bardef SliderBar(source_name,x_str_list, y_val_list, title, filename, path='./'):x_str_list = [str(x) for x in x_str_list]y_val_list = [int(x) for x in y_val_list]c = (Bar().add_xaxis(x_str_list).add_yaxis(source_name, y_val_list).set_global_opts(title_opts=opts.TitleOpts(title=title),datazoom_opts=opts.DataZoomOpts(),).render(path+filename))print('File Saved To ', path+filename)return True
# -------- 调用代码
from draw_func2 import SliderBar
source_name = 'Movies&TVs'
x_str_list = df1_summary1['year'].apply(int)
y_val_list = df1_summary1['Movie'] + df1_summary1['TV Show']
title ='Netflix Moview&TVs By Year'
filename = 'Netflix Movies1.html'SliderBar(source_name,x_str_list,y_val_list,title, filename)

效果:

2.1.3 堆叠柱状图

这个可能会更加常用。

from pyecharts import options as opts
from pyecharts.charts import Bardef Stacked2SouceBar(source_name1, source_name2, x_str_list, y_val_list1, y_val_list2, title, filename, path='./'):x_str_list = [str(x) for x in x_str_list]y_val_list1 = [int(x) for x in y_val_list1]y_val_list2 = [int(x) for x in y_val_list2]c = (Bar().add_xaxis(x_str_list).add_yaxis(source_name1, y_val_list1, stack="stack1").add_yaxis(source_name2, y_val_list2, stack="stack1").set_series_opts(label_opts=opts.LabelOpts(is_show=False)).set_global_opts(title_opts=opts.TitleOpts(title=title)).render(path+filename))print('File Saved To ', path+filename)return True
# ---------------------- 调用
from draw_func3 import Stacked2SouceBar
source_name1 = 'Movies'
source_name2 = 'TV Shows'
x_str_list = df1_summary1['year'].apply(int)
y_val_list1 = df1_summary1['Movie']
y_val_list2 = df1_summary1['TV Show']
title = 'Netflix Moview&TVs By Year'
filename = 'Netflix Movies2.html'Stacked2SouceBar(source_name1,source_name2,x_str_list,y_val_list1,y_val_list2, title, filename)

效果:

2.1.4 柱状打分图

这里暂时用来看缺失率(用来给变量的重要性打分应该也不错)。

# DataSet 数据集 可筛选Bar
from pyecharts import options as opts
from pyecharts.charts import Bar# columns
# matlist |-> insert columns to head
# score
# min, max (of score)
# path ,filname
# InfOrFloat
# titledef try_to_trans(x, IntOrFloat='int'):try:if IntOrFloat.lower() == 'int':res = int(x)else:res = float(x)except:res = str(x)return resdef ScoreBar(x_str_list, matlist, scorename, valname, catename,minscore, maxscore,filename, path='./',title='', IntOrFloat='int'):headers = [str(x) for x in x_str_list]rows = []for i in range(len(matlist)):tem_row = [try_to_trans(x, IntOrFloat=IntOrFloat) for x in matlist[i]]rows.append(tem_row)rows.insert(0,headers)c = (Bar().add_dataset(source=rows).add_yaxis(series_name="",yaxis_data=[],encode={"x": valname, "y": catename},label_opts=opts.LabelOpts(is_show=False),).set_global_opts(title_opts=opts.TitleOpts(title=title),xaxis_opts=opts.AxisOpts(name=valname),yaxis_opts=opts.AxisOpts(type_="category"),visualmap_opts=opts.VisualMapOpts(orient="horizontal",pos_left="center",min_=minscore,max_=maxscore,range_text=["High Score", "Low Score"],dimension=0,range_color=["#FF0000", "#00FF7F"],),).render(path+filename))print('File Saved To ', path+filename)return True# ----------------------调用
from draw_func6 import ScoreBar
score_bar_data = df_meta[['missing_num', 'missing_ratio', 'varname']]
score_bar_data['missing_score'] = (1-score_bar_data['missing_ratio'])*100
score_bar_data = score_bar_data.sort_values(['missing_num'], ascending=False)x_str_list = ['missing_score','missing_num','varname']
matlist = score_bar_data[x_str_list].values.tolist()
scorename = 'missing_score'
valname = 'missing_num'
catename = 'varname'
minscore = 0
maxscore = 100
filename = 'Netflix Movies3.html'
title = '变量缺失情况'
ScoreBar(x_str_list, matlist, scorename, valname, catename,minscore, maxscore,filename, path='./',title=title, IntOrFloat='int')

效果:
可以看到,导演缺失的比较多,不过总体缺失情况还好。

2.1.5 4X4饼图

用来显示交叉表的结果,暂时实现4X4饼图的效果。

# 数据
df_crosstab = pd.crosstab(df['rating'], df['release_year']).reset_index()
# ---
In [210]: df_crosstab
Out[210]:
release_year    rating  1925  1942  1943  ...   2017  2018  2019  2020
0                    G     0     0     0  ...      2     1     0     0
1                NC-17     0     0     0  ...      0     1     0     0
2                   NR     0     0     1  ...      4     5     2     0
3                   PG     0     0     0  ...     11    30     7     0
4                PG-13     0     0     0  ...     22    19    10     0
5                    R     0     0     0  ...     47    24    24     0
6                TV-14     0     0     1  ...    279   291   211     5
7                 TV-G     0     1     0  ...     25    15    25     1
8                TV-MA     0     0     0  ...    400   491   412    17
9                TV-PG     1     1     1  ...    105   103    83     0
10                TV-Y     0     0     0  ...     19    30    29     1
11               TV-Y7     0     0     0  ...     36    26    27     0
12            TV-Y7-FV     0     0     0  ...      7    27    13     1
13                  UR     0     0     0  ...      0     0     0     0[14 rows x 73 columns]# --- 4X4饼图函数
def Pie4CrossTab(x_str_list,y_str_list, matlist, x_4dim_list, y_4dim_list,filename, path='./',title='', IntOrFloat='int'):x_str_list = [str(x) for x in x_str_list]y_str_list = [str(x) for x in y_str_list]col_index = [x_str_list.index(x) for x in y_4dim_list]row_index = [y_str_list.index(y) for y in x_4dim_list]rows = []for i in range(len(matlist)):if i in row_index:tem_row = []for j in range(len(x_str_list)):if j == 0:tem_row.append(str(matlist[i][j]))elif j in col_index:tem_row.append(try_to_trans(matlist[i][j], IntOrFloat=IntOrFloat))rows.append(tem_row)headers = [x_str_list[x] for x in col_index]headers.insert(0, x_str_list[0])rows.insert(0, headers)c = (Pie().add_dataset(source=rows).add(series_name= x_4dim_list[0],data_pair=[],radius=60,# 左上center=["25%", "30%"],encode={"itemName": headers[0], "value": y_4dim_list[0]},).add(series_name=x_4dim_list[1],data_pair=[],radius=60,# 右上center=["75%", "30%"],encode={"itemName": headers[0], "value": y_4dim_list[1]},).add(series_name=x_4dim_list[2],data_pair=[],radius=60,# 左下center=["25%", "75%"],encode={"itemName": headers[0], "value": y_4dim_list[2]},).add(series_name=x_4dim_list[3],data_pair=[],radius=60,# 右上center=["75%", "75%"],encode={"itemName": headers[0], "value": y_4dim_list[3]},).set_global_opts(title_opts=opts.TitleOpts(title=title),legend_opts=opts.LegendOpts(pos_left="30%", pos_top="2%"),).render(path+filename))print('File Saved To ', path+filename)return True
# ---- 调用
x_str_list = list(df_crosstab.columns)
y_str_list = list(df_crosstab['rating'])
matlist = df_crosstab.values.tolist()
x_4dim_list = ['TV-14', 'TV-MA', 'TV-PG', 'TV-Y7']
y_4dim_list = ['2015','2016','2017','2018']
filename = 'Netflix Movies4.html'
title = '评级与年份情况'Pie4CrossTab(x_str_list,y_str_list, matlist, x_4dim_list, y_4dim_list,filename, path='./',title=title, IntOrFloat='int')

效果:

3 正式开始

以下的部分会顺着例子的主线进行介绍,Kaggle原版例子在这里

3.1 要做什么?

基于使用Adamic Adar measure建立的图推荐电影。评分越高,匹配度越高。

Adamic Adar measure= Frequency-Weighted Common Neighbors
【共同邻居的频数加权距离】
当我们计算两个相同邻居的数量的时候,其实每个邻居的“重要程度”都是不一样的,我们认为这个邻居的邻居数量越少,就越凸显它作为“中间人”的重要性,毕竟一共只认识那么少人,却恰好是x,y的好朋友。

3.2 对于描述(Description)变量的处理思考

  • 1 第一个想法

    • 为了把描述变量纳入模型,使用KMeans对TF-IDF特征化后的向量进行聚类。如果两部电影的描述一致,那么这两部电影视为一个节点。
    • 如果一个组中的电影数量越少,那么它们的联系越强

结论:不work, 因为cluster非常不平衡

  • 2 第二个想法

    • 为每部电影描述计算TF-IDF矩阵,选择前5个相似描述,并建立一个相似节点。 参考
  • 3 第三个想法Adamic Adar measure(AAM)

    • 通过两个节点共同的邻居来描述两个点的接近程度 -> 间接度量指标
    • AAM(x,y) = sum (1/ N(u)) for u be the neighbor of both x and y
      • 如果x,y的邻居u有许多邻居(度比较高),那么这个u不太被计入x和y的AAM
      • 反之,u没什么邻居,那么这个u对AMM的贡献比较大。

4 文本字段的向量化及聚类

4.1 文本列表的简单拆分

将 director(导演), listed_in(频道/分类), cast(演员名单) and country(原产国)这些原本是字符串的变量进行转换,变为列表。

# 将 director(导演), listed_in(频道/分类), cast(演员名单) and country(原产国)
# 这些字符串变量使用apply转换为列表,如果缺失值那么就是空列表
df['directors'] = df['director'].apply(lambda l: [] if pd.isna(l) else [i.strip() for i in l.split(",")])
df['categories'] = df['listed_in'].apply(lambda l: [] if pd.isna(l) else [i.strip() for i in l.split(",")])
df['actors'] = df['cast'].apply(lambda l: [] if pd.isna(l) else [i.strip() for i in l.split(",")])
df['countries'] = df['country'].apply(lambda l: [] if pd.isna(l) else [i.strip() for i in l.split(",")])# 数据表 -> 太长,还是不如DataTables
# from draw_func5 import FlatTable
# x_str_list = df.columns
# matlist = df.values.tolist()
# filename = 'tem.html'
# title = '对导演、频道/分类、演员名单、原产国等变量处理后的数据'# FlatTable(x_str_list, matlist, filename, title=title)

4.2 对Description进行TF-IDF

这里因为是英文,所以可以使用Sklearn进行处理。对于中文,一般使用Jieba来计算,这部分可以单独开一个专题(TF-IDF虽然简单,但是如果不能做成离线计算就没什么意义)。
这块参考原文,内容不是本次重点。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel
from sklearn.cluster import MiniBatchKMeans
# Build the tfidf matrix with the descriptions
start_time = time.time()
text_content = df['description']
vector = TfidfVectorizer(max_df=0.4,    # 去掉普遍的词     # drop words that occur in more than X percent of documentsmin_df=1,      # 入选词的门槛 only use words that appear at least X timesstop_words='english',  # remove stop wordslowercase=True,  # Convert everything to lower caseuse_idf=True,   # Use idfnorm=u'l2',     # Normalizationsmooth_idf=True # Prevents divide-by-zero errors)
tfidf = vector.fit_transform(text_content)
# Clustering  Kmeans
k = 200
kmeans = MiniBatchKMeans(n_clusters=k)
kmeans.fit(tfidf)
centers = kmeans.cluster_centers_.argsort()[:, ::-1]
terms = vector.get_feature_names()# print the centers of the clusters
# for i in range(0,k):
#     word_list=[]
#     print("cluster%d:"% i)
#     for j in centers[i,:10]:
#         word_list.append(terms[j])
#     print(word_list)request_transform = vector.transform(df['description'])
# new column cluster based on the description
df['cluster'] = kmeans.predict(request_transform)df['cluster'].value_counts().head()# 聚类结果非常不均匀
Out[13]:
44     5950
199       9
85        7
159       6
125       6
Name: cluster, dtype: int64# 另一个相似性函数
# Find similar : get the top_n movies with description similar to the target description
def find_similar(tfidf_matrix, index, top_n = 5):cosine_similarities = linear_kernel(tfidf_matrix[index:index+1], tfidf_matrix).flatten()related_docs_indices = [i for i in cosine_similarities.argsort()[::-1] if i != index]return [index for index in related_docs_indices][0:top_n]  

5 构建无向图

原文中对图进行的抽象如下
图的节点和边:

  • 1 点(的类别):

    • 1 电影
    • 2 人(导演或演员)
    • 3 分类
    • 4 原产国
    • 5 聚类
    • 6 5个最相似的描述
  • 2 边
    • 1 扮演(演员和电影之间)
    • 2 分类(电影和分类之间)
    • 3 导演(导演和电影之间)
    • 4 国家(国家和电影之间)
    • 5 描述(描述和电影之间)
    • 6 相似性(由find_similar定义)
      备注:两个电影间没有直接连接,都是通过演员,导演,描述之类的连接。

以下是我的观点:
某种程度上说,电影、人、原产国甚至聚类等的都可以视为「点」,如果把「点」理解成为认知里某个概念(Concept)的话。但是我觉得如果把谋改革概念视为点的时候最好满足两点:
1.概念之间是天然独立的,例如说人和飞机分别视为两类节点应该没有人反对。但是最好不要出现虚虚实实的混杂,例如这里的人(该概念指代一个具体的独立的实体),而聚类和描述是什么?该把他们作为相提并论的点吗。
2.模型是为了通过描述、预测来帮助人类更好的认知以及处理问题,那么把某个概念作为点是否对问题有帮助?把原产国作为电影的一个属性是不是更好呢?

关于边,一种边代表一种关系,有时候把所有边放在一起可能对存储可取(例如把图存入类似Neo4j这样的图数据库,需要时可以按照节点、边的类型和属性方便的提出来),但是对于算法不太可取。扮演、导演和投资可以把人和电影联系起来,但他们能一样吗?获取在计算连通性的时候有用,但个人感觉,如果没有特别合适的度量(定义边的计算方法),不要贸然把所有的边揉在一起。

综合起来,我的思路如下:

  1. 关于图,数据分为节点和关系两种。存储方法有很多种(关系型数据库 Mysql, 非关系型数据库 Mongo以及专门的图数据库Neo4j)。从实验方便性来说,可以使用Mysql这种最习惯的表结构来看,许多图属性还是使用Neo4j计算比较快,Networkx太慢了。
  2. 关于如何积累数据。这里有一个假设:我们能观察到的数据始终是不完整的(有些数据本来有,但是没拿到)以及包含错误。所以,不能“全信”拿到的数据,有部分需要通过推断来补全,当然推断本身又需要通过推断来管理…

一张完整的节点表:

NodeId Attr Val CreateTS UpdateTS InferId NodeIDInfer AttrInfer ValInfer CreateTS UpdateTS
节点ID 属性 创建时间 更新时间 推断ID 推断节点 推断属性 推断值 推断创建时间 推断更新时间

在使用算法时,通过计算和筛选,我们只需要这样一张表

NodeId Attr Val
节点ID 属性

关系表在推断方面和节点表类似,但是在具体应用在某个问题时也会是简单的一张表,形式上类似一笔电商的交易:

NodeA NodeB Val
节点ID 属性

值得注意的是,这样一张表其实只是某种关系(网),某种属性下的,例如A给B5万元。而最终的结果可能是要叠加多张网生效的。

未完待续…

后续内容:
3. 将数据构建为无向图
4. 子图分析
5. 基于图的推荐函数
6. 结果测试

Python 图算法系列2 -电影推荐相关推荐

  1. 基于python+django的个性化电影推荐系统设计与实现

    目录 第 1 章 绪论 1 1.1 研究背景及意义 1 1.2 国内外研究现状 1 1.3 本文研究目标和研究内容 4 1.4 论文结构安排 4 第 2 章 推荐算法的研究 7 2.1 推荐算法简介 ...

  2. 基于Python和Tensorflow的电影推荐算法

    第一步:收集和清洗数据 数据链接:https://grouplens.org/datasets/movielens/ 下载文件:ml-latest-small import pandas as pd ...

  3. Python 图算法系列13-cypher 查询以及模糊查询

    说明 整理一些常用的查询备用,可以参考这篇文章. 查询之前要先建立索引,如果是动态的写入数据(小批量方式),那么在建库的时候就先声明索引:如果是静态的一次性删库导入,只能等数据完全导入后建立索引(2亿 ...

  4. Python基于用户协同过滤算法电影推荐的一个小改进

    之前曾经推送过这个问题的一个实现,详见:Python基于用户协同过滤算法的电影推荐代码demo 在当时的代码中没有考虑一种情况,如果选出来的最相似用户和待测用户完全一样,就没法推荐电影了.所以,在实际 ...

  5. python爬取图片教程-推荐|Python 爬虫系列教程一爬取批量百度图片

    Python 爬虫系列教程一爬取批量百度图片https://blog.csdn.net/qq_40774175/article/details/81273198# -*- coding: utf-8 ...

  6. 免费python全套视频教学-有哪些优质的Python全系列视频教程推荐,免费的收费的都可以?...

    为大家推荐两本适合小白的python书籍,希望能对你有所帮助. <python编程从入门到实践> /> 本书是一本针对所有层次的Python 读者而作的Python 入门书.全书分两 ...

  7. 基于Python + Django + mysql的协同推荐算法的电影推荐系统

    基于Python + Django + mysql的协同推荐算法的电影推荐系统 本系统一共分为前台系统功能和后台系统功能两个模块,两个模块之间虽然在表面上是相互独立的,但是在对数据库的访问上是紧密相连 ...

  8. Python基于修正余弦相似度的电影推荐引擎

    //2022.7.15更新,经评论区提醒,更正cosine函数相关描述. 数据集下载地址:MovieLens 最新数据集 数据集包含600 名用户对 9,000 部电影应用了 100,000 个评级和 ...

  9. python电影推荐算法_基于Python的电影推荐算法

    原标题:基于Python的电影推荐算法 第一步:收集和清洗数据 数据链接:https://grouplens.org/datasets/movielens/ 下载文件:ml-latest-small ...

  10. 在线电影推荐网 Python+Django+Mysql 协同过滤推荐算法在电影网站中的运用 基于用户、物品的协同过滤推荐算法 开发在线电影推荐系统 电影网站推荐系统 人工智能、大数据、机器学习开发

    在线电影推荐网 Python+Django+Mysql 协同过滤推荐算法在电影网站中的运用 基于用户.物品的协同过滤推荐算法 开发在线电影推荐系统 电影网站推荐系统 人工智能.大数据.机器学习开发 M ...

最新文章

  1. php中对于json_decode()和json_encode()的使用方法笔记
  2. 网站发布问题及使用Web Deployment Projects
  3. 利用Eclipse/MyEclipse 实体类生成.hbm.xml文件
  4. java + httpclient +post请求(记录下)
  5. 选择排序稳定吗_最常见的四种数据结构排序算法你不知道?年末怎么跳槽涨薪...
  6. 程序员的.NET时代
  7. yum -y install与yum install有什么不同
  8. Android之mvp和mvc对比分析以及实际应用
  9. c 中=和==的区别有哪些?
  10. 计算机应用综合实践实验心得,综合实践活动培训心得体会范文(精选5篇)
  11. kylin启动netstat: n: unknown or uninstrumented protocol
  12. android中的多渠道打包,Android 多渠道打包简析
  13. (一) Qt Model/View 的简单说明
  14. Codeforces Round #460 (Div. 2)
  15. AJAX ControlToolkit学习日志-AnimationExtender控件(3)
  16. ai自动生成字幕软件有哪些?自动生成字幕软件推荐!
  17. Linux下基于UDP协议实现的聊天室项目(附源码)
  18. 使用editor编辑器遇到的小问题:editor.md工具栏置顶
  19. 盈高入网规范管理平台linux,入网引导测试和修复测试
  20. HTML5不支持createtouch,新手写createjs时容易遇到的坑(持续更新)

热门文章

  1. java选择,智力,数量,推理
  2. 哈利波特3 阿兹卡班的囚徒
  3. PHP 3D大富翁,大富翁3D版 Monopoly Classic HD
  4. Mac键盘符号说明(全)
  5. 使用Nginx实现多重流量复制
  6. 键盘锁定了,无法输入是什么原因?
  7. 嵌入式Linux —— usb鼠标驱动
  8. mysql -b -w_MySQL系列(三)
  9. 微型计算机的最少配是,只有SD卡大小的微型电脑 配Atom处理器
  10. 微信小程序防抖功能的实现