[Python]从豆瓣批量获取看过电影的用户列表,并应用kNN算法预测用户性别
首先从豆瓣电影的“看过这部电影 的豆瓣成员”页面上来获取较为活跃的豆瓣电影用户。
链接分析
这是看过"模仿游戏"的豆瓣成员的网页链接:http://movie.douban.com/subject/10463953/collections。
一页上显示了20名看过这部电影的豆瓣用户。当点击下一页时,当前连接变为:http://movie.douban.com/subject/10463953/collections?start=20。
由此可知,当请求下一页内容时,实际上就是将"start"后的索引增加20。
因此,我们可以设定base_url='http://movie.douban.com/subject/10463953/collections?start=',i=range(0,200,20),在循环中url=base_url+str(i)。
之所以要把i的最大值设为180,是因为后来经过测试,豆瓣只给出看过一部电影的最近200个用户。
读取网页
在访问时我设置了一个HTTP代理,并且为了防止访问频率过快而被豆瓣封ip,每读取一个网页后都会调用time.sleep(5)等待5秒。 在程序运行的时候干别的事情好了。
网页解析
本次使用BeautifulSoup库解析html。 每一个用户信息在html中是这样的:
<table width="100%" class="">
<tr>
<td width="80" valign="top">
<a href="http://movie.douban.com/people/46770381/">
<img class="" src="http://img4.douban.com/icon/u46770381-16.jpg" alt="七月" />
</a>
</td>
<td valign="top">
<div class="pl2">
<a href="http://movie.douban.com/people/46770381/" class="">七月 <span style="font-size:12px;">(银川)</span>
</a>
</div>
<p class="pl">2015-08-23 <span class="allstar40" title="推荐"></span>
</p>
</td>
</tr>
</table>
首先用读取到的html初始化soup=BeautifulSoup(html)
。本次需要的信息仅仅是用户id和用户的电影主页,因此真正有用的信息在这段代码中:
<td width="80" valign="top">
<a href="http://movie.douban.com/people/46770381/">
<img class="" src="http://img4.douban.com/icon/u46770381-16.jpg" alt="七月" />
</a>
</td>
因此在Python代码中通过td_tags=soup.findAll('td',width='80',valign='top')找到所有<td width="80" valign="top">的块。
td=td_tags[0],a=td.a就可以得到
<a href="http://movie.douban.com/people/46770381/">
<img class="" src="http://img4.douban.com/icon/u46770381-16.jpg" alt="七月" />
</a>
通过link=a.get('href')可以得到href属性,也就用户的电影主页链接。然后通过字符串查找也就可以得到用户ID了。
完整代码
1 #coding=utf-8
2 ##从豆瓣网页中得到用户id
3
4 ##网页地址类型:http://movie.douban.com/subject/26289144/collections?start=0
5 ##http://movie.douban.com/subject/26289144/collections?start=20
6
7 from BeautifulSoup import BeautifulSoup
8 import codecs
9 import time10 import urllib2
11
12 baseUrl='http://movie.douban.com/subject/25895276/collections?start='
13
14 proxyInfo='127.0.0.1:8087'
15 proxySupport=urllib2.ProxyHandler({'http':proxyInfo})
16 opener=urllib2.build_opener(proxySupport)
17 urllib2.install_opener(opener)
18
19
20 #将用户信息(id,主页链接)保存至文件
21 def saveUserInfo(idList,linkList):
22 if len(idList)!=len(linkList):
23 print 'Error: len(idList)!=len(linkList) !'
24 return
25 writeFile=codecs.open('UserIdList3.txt','a','utf-8')
26 size=len(idList)
27 for i in range(size):
28 writeFile.write(idList[i]+'t'+linkList[i]+'n')
29 writeFile.close()
30
31 #从给定html文本中解析用户id和连接
32 def parseHtmlUserId(html):
33 idList=[] #返回的id列表
34 linkList=[] #返回的link列表
35
36 soup=BeautifulSoup(html)
37 ##<td width="80" valign="top">
38 ##<a href="http://movie.douban.com/people/liaaaar/">
39 ##<img class="" src="/u3893139-33.jpg" alt="Liar." />
40 ##</a>
41 ##</td>
42 td_tags=soup.findAll('td',width='80',valign='top')
43 i=0
44 for td in td_tags:
45 #前20名用户是看过这部电影的,
46 #而后面的只是想看这部电影的用户,因此舍弃
47 if i==20:
48 break
49 a=td.a
50 link=a.get('href')
51 i_start=link.find('people/')
52 id=link[i_start+7:-1]
53 idList.append(id)
54 linkList.append(link)
55 i+=1
56 return (idList,linkList)
57
58 #返回指定编号的网页内容
59 def getHtml(num):
60 url=baseUrl+str(num)
61 page=urllib2.urlopen(url)
62 html=page.read()
63 return html
64
65 def launch():
66 #指定起始编号:20的倍数
67 ques=raw_input('Start from number?(Multiples of 20) ')
68 startNum=int(ques)
69 if startNum%20 != 0:
70 print 'Input number error!'
71 return
72 for i in range(startNum,200,20):
73 print 'Loading page %d/200 ...' %(i+1)
74 html=getHtml(i)
75 (curIdList,curLinkList)=parseHtmlUserId(html)
76 saveUserInfo(curIdList,curLinkList)
77 print 'Sleeping.'
78 time.sleep(5)
应用kNN算法预测豆瓣电影用户的性别
本文认为不同性别的人偏好的电影类型会有所不同,因此进行了此实验。利用较为活跃的274位豆瓣用户最近观看的100部电影,对其类型进行统计,以得到的37种电影类型作为属性特征,以用户性别作为标签构建样本集。使用kNN算法构建豆瓣电影用户性别分类器,使用样本中的90%作为训练样本,10%作为测试样本,准确率可以达到81.48%。
实验数据
本次实验所用数据为豆瓣用户标记的看过的电影,选取了274位豆瓣用户最近看过的100部电影。对每个用户的电影类型进行统计。本次实验所用数据中共有37个电影类型,因此将这37个类型作为用户的属性特征,各特征的值即为用户100部电影中该类型电影的数量。用户的标签为其性别,由于豆瓣没有用户性别信息,因此均为人工标注。
数据格式如下所示: X1,1,X1,2,X1,3,X1,4……X1,36,X1,37,Y1 X2,1,X2,2,X2,3,X2,4……X2,36,X2,37,Y2 ………… X274,1,X274,2,X274,3,X274,4……X274,36,X274,37,Y274
示例: 0,0,0,3,1,34,5,0,0,0,11,31,0,0,38,40,0,0,15,8,3,9,14,2,3,0,4,1,1,15,0,0,1,13,0,0,1,1 0,1,0,2,2,24,8,0,0,0,10,37,0,0,44,34,0,0,3,0,4,10,15,5,3,0,0,7,2,13,0,0,2,12,0,0,0,0
像这样的数据一共有274行,表示274个样本。每一个的前37个数据是该样本的37个特征值,最后一个数据为标签,即性别:0表示男性,1表示女性。
kNN算法
k-近邻算法(KNN),是最基本的分类算法,其基本思想是采用测量不同特征值之间的距离方法进行分类。
算法原理:存在一个样本数据集合(训练集),并且样本集中每个数据都存在标签(即每一数据与所属分类的关系已知)。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较(计算欧氏距离),然后提取样本集中特征最相似数据(最近邻)的分类标签。一般会取前k个最相似的数据,然后取k个最相似数据中出现次数最多的标签(分类)最后新数据的分类。
在此次试验中取样本的前10%作为测试样本,其余作为训练样本。
首先对所有数据归一化。对矩阵中的每一列求取最大值(max_j)、最小值(min_j),对矩阵中的数据X_j, X_j=(X_j-min_j)/(max_j-min_j) 。
然后对于每一条测试样本,计算其与所有训练样本的欧氏距离。测试样本i与训练样本j之间的距离为:
distance_i_j=sqrt((Xi,1-Xj,1)^2+(Xi,2-Xj,2)^2+……+(Xi,37-Xj,37)^2) , 对样本i的所有距离从小到大排序,在前k个中选择出现次数最多的标签,即为样本i的预测值。
实验结果
首先选择一个合适的k值。 对于k=1,3,5,7,均使用同一个测试样本和训练样本,测试其正确率,结果如下表所示。
表1 选取不同k值的正确率表
k |
1 |
3 |
5 |
7 |
---|---|---|---|---|
测试集1 |
62.96% |
81.48% |
70.37% |
77.78% |
测试集2 |
66.67% |
66.67% |
59.26% |
62.96% |
测试集3 |
62.96% |
74.07% |
70.37% |
74.07% |
平均值 |
64.20% |
74.07% |
66.67% |
71.60% |
由上述结果可知,在k=3时,测试的平均正确率最高,为74.07%,最高可以达到81.48%。
上述不同的测试集均来自同一样本集中,为随机选取所得。
Python代码
自己重新实现了一下kNN的代码,对上次的算法一小处(从k个近邻中选择频率最高的一项)做了简化。
from numpy import *
#打开数据文件,导出为矩阵,其中最后一列为类别
def fileToMatrix(filename, sep=','):
f = open(filename)
content = f.readlines()
f.close()
first_line_list = content[0].strip().split(sep)
data_matrix = zeros( (len(content), len(first_line_list)-1) )
label_vector = []
index = 0
for line in content:
list_from_line = line.strip().split(sep)
data_matrix[index,:] = list_from_line[0:-1]
label_vector.append(int(list_from_line[-1]))
index += 1
return (data_matrix,label_vector)
def classify(inX, data_matrix, label_vector, k):
diff_matrix = inX - data_matrix
square_diff_matrix = diff_matrix ** 2
square_distances = square_diff_matrix.sum(axis=1)
sorted_indicies = square_distances.argsort()
label_count = {}
for i in range(k):
cur_label = label_vector[ sorted_indicies[i] ]
label_count[cur_label] = label_count.get(cur_label, 0) + 1
max_count = 0
nearest_label = None
for label in label_count:
count = label_count[label]
if count > max_count:
max_count = count
nearest_label = label
return nearest_label
def test(filename,k=3,sep=',',hold_ratio=0.3):
data_matrix, label_vector = fileToMatrix(filename,sep=sep)
data_num = data_matrix.shape[0]
test_num = int(hold_ratio * data_num)
train_num = data_num - test_num
train_matrix = data_matrix[0:train_num,:]
test_matrix = data_matrix[train_num:,:]
train_label_vector = label_vector[0:train_num]
test_label_vector = label_vector[train_num:]
right_count = 0
for i in range(test_num):
inX = test_matrix[i,:]
classify_result = classify(inX, train_matrix, train_label_vector, k)
if classify_result == test_label_vector[i]:
right_count += 1
print(" The classifier came back with: %d, the real answer is: %d" % (classify_result, test_label_vector[i]))
accuracy = float(right_count)/float(test_num)
print('The total accuracy is %f' % accuracy)
- Vuejs和其他前端框架的对比
- 详述 IntelliJ IDEA 中恢复代码的方法
- C# 通过IEnumberable接口和IEnumerator接口实现自定义集合类型foreach功能
- 微信小程序之picker组件
- 详述 IntelliJ IDEA 中恢复代码的方法「进阶篇」
- mac环境下mongodb的安装和使用
- C# 终极基类Object介绍
- EF基础知识小记五(一对多、多对多处理)
- 字符串的方法汇总
- Kotlin和anko融合进行Android开发
- EF基础知识小记六(使用Code First建模自引用关系,常用于系统菜单、文件目录等有层级之分的实体)
- Vue.js简介
- Kotlin之Elvis 操作符
- C# 文件操作系列一
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- mapboxGL中popup遮挡的优化
- SQL注入的基本步骤
- JS 变量作用域导致的一个坑
- 池化技术到达有多牛?看了线程和线程池的对比吓我一跳!
- Nginx 跨域 add_header 403状态下无效
- Cannot set property 'branchdata' of undefined
- 【每日一题】27. Remove Element
- 【CPP】《程序员面试金典》习题(1)——数组与字符串
- 【CPP】《程序员面试金典》习题(2)——链表
- 【CPP】《程序员面试金典》习题(3)——栈和队列
- PPYOLO:2020不容错过的目标检测调参Tricks
- 【笔记】《C++Primer》—— 第11章:关联容器
- 【笔记】《C++Primer》—— 第12章:动态内存
- 【笔记】《C++Primer》—— 第13章:拷贝控制
- 【笔记】《C++Primer》—— 第16章:模板与泛型编程