前期准备:python、Anaconda(用到的是安装完成后的Spyder)、Jupyter Notebook(主要用于数据可视化,因为画图比较方便)
需要用到的库:pandas、numpy、matplotlib、requests、BeautifulSoup、re、time、collections
爬取网站:https://movie.douban.com/top250
OK,Let's go.
首先分析目标网站的URL
第二页的URL是这样的:
https://movie.douban.com/top250?start=25&filter=
第三页的URL是这样的:
https://movie.douban.com/top250?start=50&filter=
可以发现每一页的URL除了''start=''后面的数字之外,都是一样的,数字的变化规律是每一页增加25,并且从0开始到225结束,一共10页。
接下来就可以解析URL得到HTML页面了,用到的库是requests和BeautifulSoup,并设置爬取的间隔时间。
for i in range(0,250,25):
print(i)
headers={
'Accept':'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding':'gzip, deflate, br',
'Accept-Language':'zh-CN,zh;q=0.8',
'Connection':'keep-alive',
'Referer':'http://www.baidu.com/link?url=_andhfsjjjKRgEWkj7i9cFmYYGsisrnm2A-TN3XZDQXxvGsM9k9ZZSnikW2Yds4s&wd=&eqid=c3435a7d00006bd600000003582bfd1f'
}
url='https://movie.douban.com/top250?start='+str(i)+'&filter='
html=requests.get(url=url,headers=headers).text
time.sleep(0.5)
douban=BeautifulSoup(html,'html.parser')
可以看到我在获取HTML之前构造了一个头部文件headers,为的是模拟浏览器访问网页,降低封禁风险。
接下来就要从获取的页面信息中提取出想要的信息,并保存在列表中。这一段的心路历程可以写一本书了......
首先获取电影的排名和评分。这两项的获取比较容易,因为它们的节点中杂乱的数据较少。只需要使用find_all()函数找到对应的节点和子节点,直接获取就好了,并将获取到的数据存入对应的列表中。
rank=douban.find_all('em',attrs={'class':''})
for i in rank:
r=i.string
ranknum.append(r)
score=douban.find_all('span',attrs={'class':'rating_num'})
for i in score:
s=i.string
filmscore.append(s)
接下来获取电影名,别名,评价人数和一句话短评。这些数据的节点大致相同,子节点不同,数据较整齐。选择定位大的节点,再一步步往下定位小节点的方法,并配合正则表达式筛选出需要的正确字段。
movie_list=douban.find('ol',attrs={'class':'grid_view'})
for movie in movie_list.find_all('li'):
上述代码找到所有的
标签,以下所有的数据都可以从该标签中获取。
#电影名
name=movie.find('span',attrs={'class':'title'}).get_text()
filmname.append(name)
#别名
other=movie.find('span',attrs={'class':'other'}).get_text()
o=re.findall(r'\xa0/\xa0(.*?)\s+\/',other)
'''获取的数据为列表类型,需要转化为字符串才能判断如果为空字符串'''
if ''.join(o).strip() == '' :
othername.append('无')
else:
othername.append(''.join(o))
#评价人数
comment=movie.find('div',attrs={'class':'star'})
num=re.findall(r'\d+',str(comment))[-1]#匹配一个或多个数字
commentnum.append(num)
#短评
quote=movie.find('span',attrs={'class':'inq'})
if quote is None:#如果为空
quoteword.append('无短评')
else:
q=quote.get_text()
quoteword.append(q)
上述代码需要注意的就是当正则表达式匹配不到字段的时候,如果不给其赋值,会导致最后的汇总时出现各个列表长度不一样的错误,所以需要判断是否为空。
下面获取导演、演员、上映时间、上映地区和影片类型,这里是难点所在。因为所有的信息都保存在一个标签当中,我们需要用正则表达式将标签进行合理的拆分再得到对应的信息。并且最好将得到的网页源代码转化为文本文件,因为源码中的空格表示符 会给正则表达式的匹配带来麻烦。可以选择先转化一个页面,看看其空格的处理方式,会给正则表达式的书写带来方便。
info=movie.find('div',attrs={'class':'bd'})#获取所有信息
#导演
director=re.findall(r'\n\n\s+(.*?)\xa0\xa0\xa0',info.text)
if director is None:
director_list.append('无')
else:
director_list.append(''.join(director))
#转化为字符串保存,避免出现多维数组
#上映时间
show_time=re.findall(r'\n\s+(\d+)\xa0/\xa0',info.text)
if show_time is None:
showtime.append('无')
else:
showtime.append(''.join(show_time))
#演员
actors=re.findall(r'\xa0\xa0\xa0(.*?)\n\s+',info.text)
if actors is None:
actors_list.append('无')
else:
actors_list.append(''.join(actors))
#上映地区
place=re.findall('\xa0/\xa0(.*?)\xa0/\xa0',info.text)
if place is None:
showplace.append('无')
else:
showplace.append(''.join(place))
#影片类型
kind=re.findall(r'\xa0/\xa0.*?\xa0/\xa0(.*?)\n\s+',info.text)
if kind is None:
filmkind.append('无')
else:
filmkind.append(''.join(kind))
接下来就是整合数据。将所有得到的数组用DataFrame类型存储到一个变量中,再保存在CSV文件中。这里更倾向于将文件保存为CSV文件而非Excel文件,虽然都可以用excel打开。具体原因可以自行百度。
data=pd.DataFrame({'排名':ranknum,'电影名':filmname,'别名':othername,'评分':filmscore,
'评价人数':commentnum,'概述':quoteword,'导演':director_list,
'演员':actors_list,'上映时间':showtime,'上映地区':showplace,
'类型':filmkind},columns=['排名','电影名','评分','上映时间','上映地区',
'别名','概述','导演','演员','评价人数','类型'])
data.to_csv('豆瓣电影TOP250.csv')
有了数据,就可以对数据做一些可视化的处理。接下来的所有步骤都是用Jupyter Notebook完成的。因为相对于Spyder来说,Jupyter Notebook在数据可视化方面更加方便。
初始化。获取数据,删去无用的列,设置图像能够正常显示中文和正负号。
data=pd.read_csv(r'D:\pycode\豆瓣电影TOP250.csv',engine='python')
plt.rcParams['font.sans-serif'] = ['STXihei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
del data['Unnamed: 0']
data.head()
不同评分的电影数量
a=data.groupby('评分')['评分'].count()
x1=np.arange(len(a.index))
y1=a.values
a.plot(kind='bar',x=a.index,y=a.values,colormap='summer',figsize=(12,4),alpha=0.6)
plt.xlabel('评分')
plt.ylabel('数量')
plt.title('不同评分电影数量')
plt.grid(True,linestyle='--',alpha=0.3)
for i,j in zip(x1,y1):
plt.text(i-0.1,j+0.2,'%.0f'%j,color='grey',alpha=0.7)
电影上映时间的分布。X轴:时间,Y轴:数量,散点的大小代表了该年上映电影的平均评分。
可以从图中看到1980年前的电影占据了少数部分,但评分明显高于1980年后的平均评分。电影的高产和高质量时期在1990-2000年之间。
b=data.groupby('上映时间')['上映时间'].count()
c=data.groupby('上映时间')['评分'].mean()
plt.scatter(x=b.index,y=b.values,c=c.values,cmap='Reds')
plt.xlabel('上映时间')
plt.ylabel('数量')
plt.title('电影上映时间分布')
plt.xticks(range(1930,2020,10))
plt.grid(True,linestyle='--',alpha=0.4)
各地区的TOP250上映数量。直接通过groupby函数分类计算各地区上映电影数。我们先来看一下绘图的效果。
可以发现,Y轴的数量特别多,使得整张图表看起来非常不整洁,原因是每一部电影的上映地区可能有很多个,但groupby将每一个多元素的数组当成一个整体进行了计算。依旧附上代码。
d=data.groupby('上映地区')['上映地区'].count()
d.plot(kind='barh',x=d.values,y=d.index,figsize=(12,12),cmap='summer',alpha=0.5)
plt.xlabel('数量')
plt.ylabel('上映地区')
plt.title('各地区上映')
plt.grid(True,linestyle='--',alpha=0.3)
对上图进行改进。方法是将原本的单个数组中的多个元素分割,使其一个数组中只包含一个元素。再通过计数得到各个地区上映TOP250电影的数量。
可以看到,美国占据了250部中的143部,超过了半数。英国法国德国日本和中国香港都有不错的表现。这张图可以从侧面反映出各个地区电影制片水平的高低,数量越多,水平越高。
place=data['上映地区']
place_list=[]
for i in place:
a=i.split(' ')
place_list.extend(a)
#place_list=array(place_list)
#place_list.flatten()
#place_list
from collections import *
d=defaultdict(int)
for i in place_list:
d[i]+=1
counts=pd.Series(d)
counts.plot(kind='barh',x=counts.values,y=counts.index,figsize=(12,12),cmap='summer',alpha=0.5)
plt.xlabel('数量')
plt.ylabel('上映地区')
plt.title('各地区上映TOP250电影数量')
plt.grid(True,linestyle='--',alpha=0.3)
a=counts.values
b=range(len(counts.index))
for i,j in zip(a,b):
plt.text(i+0.2,j-0.2,'%.0f'%i,color='grey')
以上就是全部。(提取密码:lr11)
领取专属 10元无门槛券
私享最新 技术干货