爬取豆瓣最近热映电影、数据分析及可视化
爬取豆瓣最近热映电影、数据分析及可视化

爬取豆瓣最近热映电影、数据分析及可视化

1.介绍

爬豆瓣一直是练习爬虫的经典项目,由于其无太多反爬措施,所以上手难度较低,比较适合入门选手练手,我之前就爬过了,但是一直没时间整理,最近放假了正好来复盘顺便完善一波


2.网站分析

豆瓣最近热映电影:https://movie.douban.com/cinema/nowplaying

正在上映的区域即是我们要爬取的区域,这里还是推荐先登录,因为不登陆会自动跳转到登录页面

在爬取时可以不加cookies,只加入user-agent

我们的思路主要是爬取数据->数据处理->数据可视化

主要用到的库有:requests,BeautifulSoup, csv,pandas,matplotlib


3.数据爬取

直接用审查定位到信息的源代码位置,标题和评分

在定位的时候可以发现,其实标题评分这些信息,在上面的<li>标签的属性中就已经出现了,我们直接使用li标签即可一次获取所有信息

先看一下主函数,主函数主要是创建列表,然后调用各个函数

  1. def main():
  2.     print("------爬取豆瓣最近热映电影及评分------\n")
  3.     url = "https://movie.douban.com/cinema/nowplaying/tianjin"
  4.     indoList = [[ [] for i in range(2) ] for i in range(50) ]       #创建50*2的二维列表
  5.     html = getHTMLText(url)
  6.     parsePage(indoList, html)
  7.     SaveCSV(indoList)

然后是爬取部分,主要是使用soup.findAll方法获取某个div下所有的li标签,使用.attrs方法获取属性。然后用if判断长度是否大于1,因为有些li标签的属性只有一个,是无效内容,然后把某个属性提取出来存入在主函数创建的二维列表中即可

  1. def parsePage(ilt, html):
  2.     try:
  3.         cnt = 0
  4.         soup = BeautifulSoup(html, "html.parser")
  5.         for li in soup.find('div', class_='mod-bd').findAll('li'):
  6.             d = li.attrs    # 获取li标签的属性
  7.             if len(d) > 1:      # 筛选有效数据
  8.                 ilt[cnt][0] = d['data-title']   # 将标题存入列表
  9.                 ilt[cnt][1] = d['data-score']   # 将评分存入列表
  10.                 cnt = cnt + 1
  11.         del ilt[cnt:]      #删除空位置
  12.     except:
  13.         return ""

因为我们开始创建的是50*2的二维列表,长度是大于最近热映电影数量的,所以在循环完之后,直接用计数器得到的长度,把后面的空列表删除掉即可

爬取完数据之后以csv文件格式保存,便于后续的处理

然后是完整的数据爬取部分的函数

  1. import requests
  2. from bs4 import BeautifulSoup
  3. import csv
  4.  
  5.  
  6. def getHTMLText(url):   #获得页面函数
  7.     try:
  8.         header = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0'}
  9.         # 加入浏览器头部信息,模拟浏览器访问
  10.         r = requests.get(url, headers=header, timeout=30)
  11.         r.raise_for_status()    # 返回状态码
  12.         r.encoding = r.apparent_encoding    #文本解码
  13.         return r.text
  14.     except:
  15.         return ""
  16.  
  17.  
  18. def parsePage(ilt, html):
  19.     try:
  20.         cnt = 0
  21.         soup = BeautifulSoup(html, "html.parser")
  22.         for li in soup.find('div', class_='mod-bd').findAll('li'):
  23.             d = li.attrs    # 获取li标签的属性
  24.             if len(d) > 1:      # 筛选有效数据
  25.                 ilt[cnt][0] = d['data-title']   # 将标题存入列表
  26.                 ilt[cnt][1] = d['data-score']   # 将评分存入列表
  27.                 cnt = cnt + 1
  28.         del ilt[cnt:]      #删除空位置
  29.     except:
  30.         return ""
  31.  
  32.  
  33. def SaveCSV(ilt):
  34.     try:
  35.         with open('DouBan_RecentMovie.csv', 'w', newline='') as f:      #将数据以csv形式存储
  36.             writer = csv.writer(f)
  37.             writer.writerow(['片名', '评分'])
  38.             for i in ilt:
  39.                 writer.writerow(i)
  40.             f.close()
  41.         # 已存入的CSV文件预览
  42.         print("已存入的CSV文件如下:\n")
  43.         for i in csv.reader(open('DouBan_RecentMovie.csv','r')):
  44.             print(i)
  45.     except:
  46.         return ""
  47.  
  48.  
  49. def main():
  50.     print("------爬取豆瓣最近热映电影及评分------\n")
  51.     url = "https://movie.douban.com/cinema/nowplaying/tianjin"
  52.     indoList = [[ [] for i in range(2) ] for i in range(50) ]       #创建50*2的二维列表
  53.     html = getHTMLText(url)
  54.     parsePage(indoList, html)
  55.     SaveCSV(indoList)
  56.  
  57. main()


4.数据分析

在数据分析时,我们先从csv文件读入爬取的数据,存入列表中,然后处理列表,之后用pandas处理数据,再写入新的csv文件

这里要注意在写入csv文件时,要加入 newline=” ,不然每行之后会有空一行,可能是因为在写入时编译器会自动在每一行数据之后加上换行

在读入csv文件时要注意读取出的reader是个迭代类型,需要遍历一下才能把数据拿出来

然后先来说一下处理列表

  1. def Deal_li(ilt):       # 处理列表
  2.     del ilt[0]       # 删除表头
  3.     for i in range(len(ilt)):       #数据类型转化
  4.         t = float(ilt[i][1])
  5.         ilt[i][1] = t
  6.  
  7.     dlt = [[] for i in range(len(ilt))]
  8.     for i in range(len(ilt)):        #创建没有0值的数组用于统计
  9.         if ilt[i][1] != 0:
  10.             dlt[i].append(ilt[i][0])
  11.             dlt[i].append(ilt[i][1])
  12.     print(dlt)
  13.     return dlt

因为在写入时候为了看起来方便,所以我们加了一个表头,在统计数据的时候我们用不到,所以先把表头从列表中删除,注意:从csv文件中拿出来的数据时str类型,所以我们要先把评分转化为float类型

同时,数据中有一些0值,因为上映时间太短,所以还没有评分,统计的时候需要去除这部分数据,否则会增加样本数量,导致统计的平均值下降。所以我们要创建一个去掉这些0值的列表用于统计,而具有完整数据的列表可以用于评分排序。

而原本列表中带有0值的,我们可以放到后面利用pandas对其进行排序,根据分数把对应的电影名称排序做出来

然后利用pandas来处理数据

  1. def Deal_pd(ilt, dlt):       # 利用Pandas处理数据
  2.     array = pd.DataFrame(ilt)
  3.     countArray = pd.DataFrame(dlt)
  4.     newArray = array.sort_values(1, ascending=True)       # 按第二列评分进行升序排序
  5.     print(newArray)
  6.     mean = countArray.describe().loc['mean']
  7.     std = countArray.describe().loc['std']
  8.     return mean, std, newArray

先利用传入的两个列表得到两个dataframe类型,把带有0值的类型用array.sort_values方法排序,然后把不含0值的类型用descibe方法获得统计数据,再用.loc方法拿出平均值和方差

最后再把排序后的数组直接写入为csv,在末尾追加一组统计数据即可

完整代码如下:

  1. import pandas as pd
  2. import csv
  3.  
  4.  
  5. def in_stream(ilt):         #读入CSV文件
  6.     with open('DouBan_RecentMovie.csv', 'r', ) as f_out:
  7.         reader = csv.reader(f_out)
  8.         for i in reader:
  9.             ilt.append(i)
  10.         f_out.close()
  11.  
  12.  
  13. def Deal_li(ilt):       # 处理列表
  14.     del ilt[0]       # 删除表头
  15.     for i in range(len(ilt)):       #数据类型转化
  16.         t = float(ilt[i][1])
  17.         ilt[i][1] = t
  18.  
  19.     dlt = [[] for i in range(len(ilt))]
  20.     for i in range(len(ilt)):        #创建没有0值的数组用于统计
  21.         if ilt[i][1] != 0:
  22.             dlt[i].append(ilt[i][0])
  23.             dlt[i].append(ilt[i][1])
  24.     print(dlt)
  25.     return dlt
  26.  
  27.  
  28. def Deal_pd(ilt, dlt):       # 利用Pandas处理数据
  29.     array = pd.DataFrame(ilt)
  30.     countArray = pd.DataFrame(dlt)
  31.     newArray = array.sort_values(1, ascending=True)       # 按第二列评分进行升序排序
  32.     print(newArray)
  33.     mean = countArray.describe().loc['mean']
  34.     std = countArray.describe().loc['std']
  35.     return mean, std, newArray
  36.  
  37.  
  38. def out_stream(mean, std, newArray):    # 处理数据写入新的CSV文件
  39.     newArray.to_csv('DouBan_RecentMovie_Deal.csv', header=0, index=0,  encoding='utf_8_sig')
  40.     with open('DouBan_RecentMovie_Deal.csv', 'a+', newline='') as f_in:         # 追加统计数据放在表尾
  41.         writer = csv.writer(f_in)
  42.         writer.writerow(['mean', mean.values[0]])
  43.         writer.writerow(['std', std.values[0]])
  44.         f_in.close()
  45.  
  46.  
  47. def main():     # 主函数
  48.     print("------豆瓣数据处理------\n")
  49.     li = []
  50.     in_stream(li)
  51.     lt = Deal_li(li)
  52.     mean, std, new_li = Deal_pd(li, lt)
  53.     out_stream(mean, std, new_li)
  54.  
  55.  
  56. main()


5.数据可视化

数据可视化主要是用到了matplotlib库,绘制横向条形图,箭头注释,平均值分界线

因为第一次用matplotlib,我把数据分成了三个一维列表作为绘图数据,我也不知道有没有什么便捷的方法,因为在网上看了很久也没找到

然后先修改字体,绘制横向条形图,由于竖向条形图会把电影名称重叠在一起,所以用横向的,写主标题,写横竖轴标题,然后再在条形图上加上数据标签,绘制平均值分界线,进行箭头注释

这里需要注意就是,很多操作都需要知道坐标位置,只要运行之后,在图形右下角就能看到横纵坐标

完整代码如下:

  1. import matplotlib.pyplot as plt
  2. import matplotlib
  3. import csv
  4.  
  5.  
  6. def in_stream(ilt):         #读入CSV文件
  7.     with open('DouBan_RecentMovie_Deal.csv', 'r', encoding='utf_8_sig') as f_out:
  8.         reader = csv.reader(f_out)
  9.         for i in reader:
  10.             ilt.append(i)
  11.         f_out.close()
  12.  
  13.  
  14. def Deal_li(ilt):       # 处理列表
  15.     del ilt[0]       # 删除表头
  16.     for i in range(len(ilt)):
  17.         t = float(ilt[i][1])
  18.         ilt[i][1] = t
  19.  
  20.  
  21. def Show(ilt):
  22.     li1 = []
  23.     li2 = []
  24.     li3 = []
  25.     Len = len(ilt)
  26.     # 创建三个一维列表进行数据存储
  27.     for i in range(Len-2):
  28.         t = ilt[i][0]
  29.         li1.append(t)
  30.     for i in range(Len-2):
  31.         t = ilt[i][1]
  32.         li2.append(t)
  33.     for i in range(Len-2, Len):
  34.         t = ilt[i][1]
  35.         li3.append(t)
  36.  
  37.     matplotlib.rcParams['font.family'] = 'SimHei'           #有中文,修改全局字体
  38.     y = [i for i in range(Len-2)]           #给出在y轴上的位置,绘图时从下到上
  39.     plt.title('Recent Hot Movie in DouBan', fontsize=20, color='green')
  40.     plt.xlabel('横轴:评分', fontsize=13, color='orange')
  41.     plt.ylabel('纵轴:片名', fontsize=13, color='orange')
  42.     rects_Mov = plt.barh(y, li2, facecolor='orange', tick_label=li1)
  43.     for rect in rects_Mov:          #添加数据标签
  44.         width = rect.get_width()
  45.         plt.text(width+0.05, rect.get_y()+0.3, width, ha='left', va='center')
  46.         rect.set_edgecolor('white')
  47.     plt.vlines(li3[0], 0, Len-2-0.5, color="#c72e29", linestyle='--')        #平均值直线
  48.     plt.text(10, 0, "标准差:{:.2f}\n平均值:{:.2f}".format(li3[1], li3[0]), fontsize=13, color='green')    #角落数据标注
  49.     plt.annotate("平均值:{:.2f}".format(li3[0]), xy=(li3[0], 3), xytext=(li3[0]+0.5, 0),       #使用箭头进行注释
  50.                  arrowprops=dict(facecolor='black', shrink=0.1, width=2))
  51.     plt.annotate("暂无数据", xy=(0.2, 3), xytext=(0.8, 0),
  52.                  arrowprops=dict(facecolor='black', shrink=0.1, width=2))
  53.     plt.show()
  54.  
  55.  
  56. def main():
  57.     print("------豆瓣数据展示------\n")
  58.     li = []
  59.     in_stream(li)
  60.     Deal_li(li)
  61.     Show(li)
  62.  
  63. main()