《利用Python进行数据分析》豆瓣电影TOP250的爬取和作图分析【python】

简介: 又回到豆瓣了,这是多适合爬虫的网站 :)

1、这次的对象是top 250 , 网址: https://movie.douban.com/top250 目的是对这250部电影的分类做一个统计。

2、实际有其他人做了分析,正好看到,发现其代码比较漂亮,所以研读,顺便复习检阅自己学习成果。秉承一贯的作风,做一定的细节分析。先看下原文代码:

# -*- coding: utf-8 -*-
# !/usr/bin/env python

from lxml import etree
import requests
import pymysql
import matplotlib.pyplot as plt
from pylab import *
import numpy as np

# 连接mysql数据库
conn = pymysql.connect(host = 'localhost', user = 'root', passwd = '54545454', db = 'douban', charset = 'utf8')
cur = conn.cursor()
cur.execute('use douban')

def get_page(i):
    url = 'https://movie.douban.com/top250?start={}&filter='.format(i)

    html = requests.get(url).content.decode('utf-8')

    selector = etree.HTML(html)
    # //*[@id="content"]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]
    content = selector.xpath('//div[@class="info"]/div[@class="bd"]/p/text()')
    print(content)

    for i in content[1::2]:
        print(str(i).strip().replace('\n\r', ''))
        # print(str(i).split('/'))
        i = str(i).split('/')
        i = i[len(i) - 1]
        # print('zhe' +i)
        # print(i.strip())
        # print(i.strip().split(' '))
        key = i.strip().replace('\n', '').split(' ')
        print(key)
        for i in key:
            if i not in douban.keys():
                douban[i] = 1
            else:
                douban[i] += 1

def save_mysql():
    print(douban)
    for key in douban:
        print(key)
        print(douban[key])
        if key != '':
            try:
                sql = 'insert douban(类别, 数量) value(' + "\'" + key + "\'," + "\'" + str(douban[key]) + "\'" + ');'
                cur.execute(sql)
                conn.commit()
            except:
                print('插入失败')
                conn.rollback()


def pylot_show():
    sql = 'select * from douban;'
    cur.execute(sql)
    rows = cur.fetchall()
    count = []
    category = []

    for row in rows:
        count.append(int(row[2]))
        category.append(row[1])
    print(count)
    y_pos = np.arange(len(category))
    print(y_pos)
    print(category)
    colors = np.random.rand(len(count))
    plt.barh()
    plt.barh(y_pos, count, align='center', alpha=0.4)
    plt.yticks(y_pos, category)
    for count, y_pos in zip(count, y_pos):
        plt.text(count, y_pos, count,  horizontalalignment='center', verticalalignment='center', weight='bold')
    plt.ylim(+28.0, -1.0)
    plt.title(u'豆瓣电影250')
    plt.ylabel(u'电影分类')
    plt.subplots_adjust(bottom = 0.15)
    plt.xlabel(u'分类出现次数')
    plt.savefig('douban.png')


if __name__ == '__main__':
    douban = {}
    for i in range(0, 250, 25):
        get_page(i)
    save_mysql()
    pylot_show()
    cur.close()
    conn.close()

3、首先是他用了selector = etree.HTML(html) 而我用的比较多的是bs4,应该是殊途同归。他的xpath路径是:

content = selector.xpath('//div[@class="info"]/div[@class="bd"]/p/text()') 

而我用的偷懒模式:直接浏览器找到的:

selector.xpath('//*[@id="content"]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]/text()')

注意,默认到P[1]的路径,因为是需要读取里面的文本部分,所以加入/text()

4、这样就得到了第一页的影视信息的文本,不过如果用ipthon分步操作查看, 那简直是逆天的格式,如图:

5、必须进行文字提取处理了。

首先,这一大坨的,为了简单,我们用单元测试的思想,先调试第一页第一个,这里之前已经设置从:

url = 'https://movie.douban.com/top250?start={}&filter='.format(i)

修改为:

url = 'https://movie.douban.com/top250?start={1}&filter='

同时,观察整个循环输出的格式是一个列表,每个元素是一个字符串,那么第一个电影的电影信息对应的字符串是:

'\n                            导演: 弗兰克·德拉邦特 Frank Darabont\xa0\xa0\xa0主演: 蒂姆·罗宾斯 Tim Robbins /...', '\n                            1994\xa0/\xa0美国\xa0/\xa0犯罪 剧情\n                        '

和原始网页的信息对照,实际就是第一部分包含导演主演信息,第二部分是年份国家和剧种, 就本文,我们需要的是第二部分的内容,也就是“1994 / 美国 / 犯罪 剧情”这部分内容。然后呢,因为整个content 包含了25部电影的文本, 这些都保存在列表里,并且我们需要进一步处理的,都在对应坐标1、3、5、、这样的奇数里。因为列表第一个是从0开始的。这就是第一个for循环的意义所在,用到了python的切片知识第二个冒号,表示从下表1的元素开始检索,并且隔开2个元素为下一次检索:

for i in content[1::2]:

查看了循环内的第一句为,注意这只是print,没有运行具体的代码影响原来的字符串:

print(str(i).strip().replace('\n\r', ''))

这句的意思就是得到整个content列表中,下标为1、3、5。。。。的元素,并通过strip()来进行去掉首尾的多余的空格,,随后使用replace函数,把’\n\r’替换为空字符。我们可以在ipython中验证下:

记录:aa = conntent[1]:

aa =  '\n                            1994\xa0/\xa0美国\xa0/\xa0犯罪 剧情\n                        '

执行:aa.strip(),得到:

In [19]: aa.strip()
Out[19]: '1994\xa0/\xa0美国\xa0/\xa0犯罪 剧情'

In [20]: aa.strip().replace('\n\r', '')
Out[20]: '1994\xa0/\xa0美国\xa0/\xa0犯罪 剧情'

发现上述的replace函数似乎没起作用,那是这里正好没有’\n\r’,同时你可以发现,这里怎么有4个地方有\xa0 ? 这是什么呢? 原来是:转义字符,”\x”后接数字(两位)代表16进制数,这玩意牵涉编码的问题,打印的时候是一个空格。

然后循环里面的开始执行的命令都是为了去除\n和空格这些没有的东西,i = str(i).split(‘/‘)就是把去除空格和\n的字符串通过‘/’分隔在列表里。

In [35]: aa =  '\n                            1994\xa0/\xa0美国\xa0/\xa0犯罪 剧情\n                        '

In [36]: aa = aa.split('/')

In [37]: aa = aa(len(aa)-1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-37-06f75198160a> in <module>()
----> 1 aa = aa(len(aa)-1)

TypeError: 'list' object is not callable

In [38]: aa = aa[len(aa)-1]

In [39]: aa
Out[39]: '\xa0犯罪 剧情\n

随后,通过

In [40]: key = aa.strip().replace('\n', '').split(' ')

In [41]: key
Out[41]: ['犯罪', '剧情']

发现,已经把\xa0当作左侧的空格给替换掉了,实际他显示出来就是一个空格的。然后通过中间的空格再劈开,得到了2个元素的字符串的列表。
这样就清洗出了所需要的数据。

下面的代码紧跟了一个针对key的for循环,是用来构建一个名为douban的字典dict,后续会把他的值再导入到数据库中。

for i in key:
    if i not in douban.keys():
        douban[i] = 1
    else:
        douban[i] += 1

里面的含义是:如果一个电影的分类关键词,在douban字典中(此时数据还没导入到数据库)不存在,那么新建一个对应名字的键值(keys),数值为1.否则如果是已经存在的,那么累加1.

5、 第二个函数是def save_mysql():作用是把提取的数据加载到数据库中去。
其中insert的那一行命令写的有点个性,但因为用到了过多的引号,所以我不是很推荐,一般的写法是: %s + 变量名

6、最后一个函数是用来画图的, 这块后续要配合nunpy,pandas等一直在加强下学习。

统计结果显示,绝大部分的好电影都是通过剧情抓住人心的:

7、 参考: 爬取豆瓣电影top250提取电影分类进行数据分析