爬取目標
-
從網頁中提取出top100電影的電影名稱、封面圖片、排名、評分、演員、上映國家/地區、評分等信息,並保存為csv文本文件。
-
根據爬取結果,進行簡單的可視化分析。
需要用到的庫
import requests
import re
import time
import csv
from requests.exceptions import RequestException
from bs4 import BeautifulSoup
from lxml import etree
如何爬取數據
利用requests來爬取網頁,實現代碼如下!
def get_one_page(url):
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
try:response = requests.get(url=url,headers=headers)
if response.status_code == 200:
return response.text
else:
return None
except RequestException:
return None
下面介紹四種解析方式!
正則表達式提取
正則表達式的用法參看https://www.runoob.com/python/python-reg-expressions.html
def parse_one_page(html):
pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)' '</p>.*?releasetime">(.*?)</p.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)
items = re.findall(pattern,html)
for item in items:
yield {'index': item[0],
'thumb': get_thumb(item[1]),
'name': item[2],
'star': item[3].strip()[3:],
# 'time': item[4].strip()[5:],
# 用函數分別提取time里的日期和地區
'time': get_release_time(item[4].strip()[5:]),
'area': get_release_area(item[4].strip()[5:]),
'score': item[5].strip() + item[6].strip()}
上面程序為了便於提取內容,又定義了3個方法:get_thumb()、get_release_time()和 get_release_area():
# 提取上映時間函數
def get_release_time(data):
pattern = re.compile(r'(.*?)(\(|$)')
items = re.search(pattern, data)
if items is None:
return '未知'
return items.group(1) # 返回匹配到的第一個括號(.*?)中結果即時間
# 提取國家/地區函數
def get_release_area(data):
pattern = re.compile(r'.*\((.*)\)')
# $表示匹配一行字符串的結尾,這里就是(.*?);\(|$,表示匹配字符串含有(,或者只有(.*?)
items = re.search(pattern, data)
if items is None:
return '未知'
return items.group(1)
# 獲取封面大圖
#http://p0.meituan.net/movie/5420be40e3b755ffe04779b9b199e935256906.jpg@160w_220h_1e1c
# 去掉@160w_220h_1e_1c就是大圖
def get_thumb(url):
pattern = re.compile(r'(.*?)@.*?')
thumb = re.search(pattern, url)
return thumb.group(1)
lxml+xpath提取
xpath的具體用法參考這篇博文https://cuiqingcai.com/5545.html
def parse_one_page2(html):
parse = etree.HTML(html)
items = parse.xpath('//*[@id="app"]//div//dd')
# 完整的是//*[@id="app"]/div/div/div[1]/dl/dd
# print(type(items))
# *代表匹配所有節點,@表示屬性
# 第一個電影是dd[1],要提取頁面所有電影則去掉[1]
# xpath://*[@id="app"]/div/div/div[1]/dl/dd[1]
for item in items:
yield{
'index': item.xpath('./i/text()')[0],
#./i/text()前面的點表示從items節點開始
#/text()提取文本
'thumb': get_thumb(str(item.xpath('./a/img[2]/@data-src')[0].strip())),
# 'thumb': 要在network中定位,在elements里會寫成@src而不是@data-src,從而會報list index out of range錯誤。
'name': item.xpath('./a/@title')[0],
'star': item.xpath('.//p[@class = "star"]/text()')[0].strip(),
'time': get_release_time(item.xpath(
'.//p[@class = "releasetime"]/text()')[0].strip()[5:]),
'area': get_release_area(item.xpath(
'.//p[@class = "releasetime"]/text()')[0].strip()[5:]),
'score' : item.xpath('.//p[@class = "score"]/i[1]/text()')[0] + \
item.xpath('.//p[@class = "score"]/i[2]/text()')[0]}
用beautifulsoup + css選擇器提取
def parse_one_page3(html):
soup = BeautifulSoup(html, 'lxml')
items = range(10)
for item in items:
yield{'index': soup.select('dd i.board-index')[item].string,
# iclass節點完整地為'board-index board-index-1',寫board-index即可
'thumb': get_thumb(soup.select('a > img.board-img')[item]["data-src"]),
# 表示a節點下面的class = board-img的img節點,注意瀏覽器eelement里面是src節點,而network里面是data-src節點,要用這個才能正確返回值
'name': soup.select('.name a')[item].string,
'star': soup.select('.star')[item].string.strip()[3:],
'time': get_release_time(soup.select('.releasetime')[item].string.strip()[5:]),
'area': get_release_area(soup.select('.releasetime')[item].string.strip()[5:]),
'score': soup.select('.integer')[item].string + soup.select('.fraction')[item].string}
Beautiful Soup + find_all函數提取
def parse_one_page4(html):
soup = BeautifulSoup(html,'lxml')
items = range(10)
for item in items:
yield{'index': soup.find_all(class_='board-index')[item].string,
'thumb': soup.find_all(class_ = 'board-img')[item].attrs['data-src'],
# 用.get('data-src')獲取圖片src鏈接,或者用attrs['data-src']
'name': soup.find_all(name = 'p',attrs = {'class' : 'name'})[item].string,
'star': soup.find_all(name = 'p',attrs = {'class':'star'})[item].string.strip()[3:],
'time': get_release_time(soup.find_all(class_ ='releasetime')[item].string.strip()[5:]),
'area': get_release_time(soup.find_all(class_ ='releasetime')[item].string.strip()[5:]),
'score':soup.find_all(name = 'i',attrs = {'class':'integer'})[item].string.strip() + soup.find_all(name = 'i',attrs = {'class':'fraction'})[item].string.strip()}
數據的存儲
def write_to_file(item):
with open('貓眼top100.csv', 'a', encoding='utf_8_sig',newline='') as f:
# 'a'為追加模式(添加)
# utf_8_sig格式導出csv不亂碼
fieldnames = ['index', 'thumb', 'name', 'star', 'time', 'area', 'score']
w = csv.DictWriter(f,fieldnames = fieldnames)
# w.writeheader()
w.writerow(item)
封面的下載
def download_thumb(name, url,num):
try:
response = requests.get(url)
with open('./' + name + '.jpg', 'wb') as f:
f.write(response.content)
print('第%s部電影封面下載完畢' %num)
print('------')
except RequestException as e:
print(e)
pass
# 存儲格式是wb,因為圖片是二進制數格式,不能用w,否則會報錯
主函數的實現
def main(offset):
url = 'http://maoyan.com/board/4?offset=' + str(offset)
html = get_one_page(url)
# print(html)
# parse_one_page2(html)
for item in parse_one_page(html): # 切換內容提取方法
print(item)
write_to_file(item)
# 下載封面圖
download_thumb(item['name'], item['thumb'],item['index'])
if __name__ == "__main__":
for i in range(10):
main(i * 10)
time.sleep(5)
到這里,我們就把爬蟲部分全部實現了。
下面我們對這些得到的數據進行簡單的分析。
電影評分最高top10
import pandas as pd
import matplotlib.pyplot as plt
import pylab as pl
plt.style.use('ggplot')
fig = plt.figure(figsize=(8,5))
colors = '#6D6D6D'
columns = ['index','thumb','name','star','time','area','score']
df = pd.read_csv('貓眼top100.csv',encoding="utf-8",header=None,names =columns,index_col = 'index')
df_core = df.sort_values('score',ascending=False)
name = df_core.name[:10]
score = df_core.score[:10]
plt.bar(range(10),score,tick_label = name,)
plt.ylim((9,9.8))
plt.title('電影評分最高top100',color = colors)
plt.xlabel('電影名稱')
plt.ylabel('評分')
for x,y in enumerate(list(score)):
plt.text(x,y+0.01,'%s' %round(y,1),ha = 'center',color = colors)
pl.xticks(rotation = 270)
plt.tight_layout()
plt.show()
各國家的電影數量比較
area_count = df.groupby(by = 'area').area.count().sort_values(ascending=False)
# 繪圖方法1
area_count.plot.bar(color = '#4652B1') #設置為藍紫色
pl.xticks(rotation=0) #x軸名稱太長重疊,旋轉為縱向
for x,y in enumerate(list(area_count.values)):
plt.text(x,y+0.5,'%s' %round(y,1),ha = 'center',color = colors)
plt.title('各國/地區電影數量排名')
plt.xlabel('國家/地區')
plt.ylabel('數量(部)')
plt.show()
電影作品數量集中的年份
# 從日期中提取年份
df['year'] = df['time'].map(lambda x:x.split('/')[0])
# 統計各年上映的電影數量
grouped_year = df.groupby('year')
grouped_year_amount = grouped_year.year.count()
top_year = grouped_year_amount.sort_values(ascending = False)
# 繪圖
top_year.plot(kind = 'bar',color = 'orangered') #顏色設置為橙紅色
for x,y in enumerate(list(top_year.values)):
plt.text(x,y+0.1,'%s' %round(y,1),ha = 'center',color = colors)
plt.title('電影數量年份排名')
plt.xlabel('年份(年)')
plt.ylabel('數量(部)')
plt.tight_layout()
plt.show()
擁有電影作品數量最多的演員
#表中的演員位於同一列,用逗號分割符隔開。需進行分割然后全部提取到list中
starlist = []
star_total = df.star
for i in df.star.str.replace(' ','').str.split(','):
starlist.extend(i)
# print(starlist)
# print(len(starlist))
# set去除重復的演員名
starall = set(starlist)
starall2 = {}
for i in starall:
if starlist.count(i)>1:
# 篩選出電影數量超過1部的演員
starall2[i] = starlist.count(i)
starall2 = sorted(starall2.items(),key = lambda starlist:starlist[1] ,reverse = True)
starall2 = dict(starall2[:10]) #將元組轉為字典格式
# 繪圖
x_star = list(starall2.keys()) #x軸坐標
y_star = list(starall2.values()) #y軸坐標
plt.bar(range(10),y_star,tick_label = x_star)
pl.xticks(rotation = 270)
for x,y in enumerate(y_star):
plt.text(x,y+0.1,'%s' %round(y,1),ha = 'center',color = colors)
plt.title('演員電影作品數量排名',color = colors)
plt.xlabel('演員')
plt.ylabel('數量(部)')
plt.tight_layout()
plt.show()
“”“778570108 群里有志同道合的小伙伴,互幫互助。群里有視頻學習教程和PDF,一起學習,共同進步!”“”