[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                  &nbsp;<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)

原文发布于微信公众号 - 大数据挖掘DT数据分析(datadw)

原文发表时间:2016-09-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏斑斓

Spark 1.4为DataFrame新增的统计与数学函数

Spark一直都在快速地更新中,性能越来越快,功能越来越强大。我们既可以参与其中,也可以乐享其成。 目前,Spark 1.4版本在社区已经进入投票阶段,在Gi...

3457
来自专栏ACM算法日常

新手ACM算法学习建议

一般要做到50行以内的程序不用调试、100行以内的二分钟内调试成功。ACM主要是考算法的,主要时间是花在思考算法上,不是花在写程序与debug上。

783
来自专栏数据结构与算法

NOIP复习内容

https://www.luogu.org/problemnew/lists?name=GSS&orderitem=pid&tag=&content=0&typ...

962
来自专栏华章科技

烧脑:谷歌微软等巨头107道数据科学面试题,你能答出多少?

来自 Glassdoor 的最新数据可以告诉我们各大科技公司最近在招聘面试时最喜欢向候选人提什么问题。首先有一个令人惋惜的结论:根据统计,几乎所有的公司都有着自...

771
来自专栏数据结构与算法

Kruskal重构树入门

在运行Kruskal算法的过程中,对于两个可以合并的节点$(x, y)$,断开其中的连边,并新建一个节点$T$,把$T$向$(x, y)$连边作为他们的父亲,同...

1115
来自专栏AILearning

【机器学习实战】第14章 利用SVD简化数据

第14章 利用SVD简化数据 <script type="text/javascript" src="http://cdn.mathjax.org/mathj...

2327
来自专栏开心的学习之路

基于协同过滤的推荐引擎(实战部分)

基于协同过滤的推荐引擎(理论部分) 时隔十日,终于决心把它写出来。大多数实验都是3.29日做的,结合3.29日写的日记完成了这篇实战。 数据集准备 数据集使用上...

2605
来自专栏量化投资与机器学习

【Python量化投资】基于网格优化、遗传算法对CTA策略进行参数优化

投资策略 基于指数移动平均线的交易系统 多头开仓条件:短期均线上穿长期均线同时长期均线大于更长期均线的值 空头开仓条件:短期均线下穿长期均线同时长期均线小于更长...

2938
来自专栏大数据挖掘DT机器学习

时间序列预测全攻略(附带Python代码)

原文作者:AARSHAY JAIN 36大数据翻译,http://www.36dsj.com/archives/43811 时间序列(简称TS)被认为是分...

9517
来自专栏人工智能LeadAI

基于协同过滤的推荐引擎(实战部分)

时隔十日,终于决心把它写出来。大多数实验都是3.29日做的,结合3.29日写的日记完成了这篇实战。 01 数据集准备 数据集使用上篇提到的Movielens电影...

2777

扫码关注云+社区