20172304 實驗四python綜合實踐報告
- 姓名:段志軒
- 學號:20172304
- 指導教師:王志強
- 課程:Python程序設計
- 實驗時間:2020年5月13日至2020年6月14日
實驗分析
本次是使用python來進行軟件開發,python是一個有很多庫的軟件,提供了很多集成式的方法,可以很方便地實現很多操作。在本次實驗的伊始,不自量力地想嘗試游戲開發,在網上找到了一個植物大戰僵屍的python源碼,不過其內容較為繁瑣,於是最后沒有進行學習。后來又想嘗試進行象棋游戲的開發。找到了一份象棋的c++源碼,想要進行學習,最后敗在了c++的格式上。最后還是回歸了python應用最廣泛的方面————爬蟲。我將目光投向了b站。bilibili目前擁有動畫、番劇、國創、音樂、舞蹈、游戲、科技、生活、娛樂、鬼畜、時尚等分區,並開設直播、游戲中心、周邊等業務板塊,是目前國內最大的二次元綜合娛樂社區平台。於是就決定八一八b站的數據。
實驗設計
本次實驗扒取的數據主要是b站的視頻排行榜上的100個視頻的相關數據。主要數據有視頻AV號,視頻BV號,彈幕id,作者id,作者名稱,投幣數,視頻時長,播放量,綜合評分,視頻標題,重放次數等。還有就是將視頻對應的彈幕扒取下來並進行簡單的分析(彈幕隨秒數分布圖),以及彈幕組成的詞雲圖等等。
實驗實現
1.首先當然是是找到數據的來源,打開嗶哩嗶哩網址,點擊搜索榜。
2.按下Fn+F12,進入瀏覽器的控制台。
3.當然,此時其內部只是一片空白,此時還需要進行刷新來重新加載數據,按下F5,你會發現新世界。
4.再點擊全站標簽,在Filter中輸入json,你會發現名為ranking?rid=0&day=3&type=1&arc_type=0&jsonp=jsonp&callback=__jp0的一個東西,點擊它,並在新生成的窗口中選擇preview標簽,將所有隱藏的子標簽打開。你會發現所有視頻的信息。
5.初步的目的就達成了,獲得了可以用來爬取b站全站排行榜的網址:https://api.bilibili.com/x/web-interface/ranking?rid=0&day=3&type=1&arc_type=0
6.初步進行功能實現。
這段代碼比較簡單主要就是通過給定的網址獲取json字符串,headers的作用主要是為了欺騙瀏覽器以達到偽裝自己是從瀏覽器發出的請求。方法最后返回的是對應的json。
def get_json():
"""
從指定的url中通過requests請求攜帶請求頭和請求體獲取網頁中的信息,
:return:
"""
url = 'https://api.bilibili.com/x/web-interface/ranking?rid=0&day=3&type=1&arc_type=0'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
}
res = requests.get(url,headers=headers,timeout=3)
res.raise_for_status()
res.encoding = 'zh'
page_data = res.json()
print('請求響應結果:', page_data, '\n\n')
return page_data
7.接下來要介紹一下json中內容的獲取。首先在瀏覽器控制台中查看json的數據格式。最后了解到需要的數據在json中的data.list下。
於是便有了如下的代碼,此時data就是一個存放着各個視頻信息的字典的列表了。
page_data = get_json() # 獲取響應json
bilibili_list = page_data['data']['list']
data = get_data(bilibili_list)
8.然后就是獲取列表中的值了。
通過如下代碼,將字典中的值提取出來,存到一個列表中。
def get_data(bilibilis_list):
page_info_list = []
for i in bilibilis_list:
bilibili_info = []
bilibili_info.append(i['aid'])#從名稱來看應該是作者的id
bilibili_info.append(i['bvid'])#從名稱來看應該是作品的id
bilibili_info.append(i['cid']) # 這個是彈幕的id
bilibili_info.append(i['mid']) #這個是作者的id
bilibili_info.append(i['author'])#作者名稱
bilibili_info.append(i['coins'])#z作品投幣數
bilibili_info.append(i['duration'])#視頻時長
bilibili_info.append(i['play'])#播放量A
bilibili_info.append(i['pts'])#這個東西在網絡后台進行了初步的對比,初步判定為是b站進行綜合排名的數據,不過算法應該是b站的不傳之密。
bilibili_info.append(i['title'])#毫無疑問這就是視頻的標題了
bilibili_info.append(i['video_review'])#這個標簽我覺得是b站進行數據分析的一個選項,應該是重播次數,不過這只是猜測,畢竟也不知道當初人家是怎么設計的
page_info_list.append(bilibili_info)
return page_info_list
9.最后將由存儲着各個視頻信息的列表的列表通過pandas存放到pandas的dataframe數據結構中,這個就很人性化我覺得,這個dataframe是pandas包中的一個數據結構,是一個二維的數據結構,類似於表格,最后通過pandas自帶的命令存入csv文件中,scv文件是一個二維的文件,其通用性比較強,可以比較容易地導入到excel中。
df = pd.DataFrame(data=data,
columns=['視頻AV號', '視頻BV號','彈幕id' ,'作者id','作者名稱', '投幣數', '視頻時長', '播放量', '綜合評分', '視頻標題', '重放次數'])
df.to_csv('Bilibili_Data_engineer.csv', index=False)
print('bilibili排行榜信息已保存')
10.拿到數據之后,第一步算是初步完成了。接下來利用爬到的數據來獲取彈幕數據。
從網絡取得數據的部分就不在進行講解了,接下來講解利用已經獲取的數據來獲取彈幕,在之前獲取的數據中,有一列數據是用來標明彈幕的,就是在之前的代碼中的彈幕id,這個爬取彈幕我也借鑒過網上的帖子,不過在經歷過多次嘗試之后,還是沒能找到各個教程中提及的xml文件。最后還是直接引用了教程中提供的網址。https://comment.bilibili.com/+彈幕id+.xml的方式獲取到了彈幕資源
def main():
df = pd.read_csv('Bilibili_Data_engineer.csv',encoding='utf-8')
result_dic = df['彈幕id']
result_dic1 = df['視頻AV號']
result_dic = list(result_dic)
result_dic1 = list(result_dic1)
bullet_screen = []
像這段代碼,其作用就是將之前爬取的視頻信息加以利用,df[x]這個方法可以直接通過存儲的列名來調用列,最后返回的應該是series類型,series類型是pandas包定義的一個一維數據結構。可以通過list(series)方法直接將其轉化為python中的list結構,這里我就進行了轉化並獲得了兩個列表,其中分別存儲着彈幕id以及與之對應的視頻av號。提到av號,這是之前bilibili創建時為了迎合主流而采取的視頻標識碼,而隨着b站(b友們親切的稱呼其為小破站)的不斷強大,其自己制定了符合自己規則的視頻碼bv號。用兩個識別碼都可以唯一識別視頻(親測有效)。
給大家展示一下b站存儲彈幕的xml文件的層級結構。
接下來就是要將數據從xml中提取出來了。對於python提取xml文件中的內容其實我本人也不是很了解,於是就援引了官方的例子。
for i,i1 in zip(result_dic,result_dic1):
page_data_2 = get_bullet_screen(i)
xml = page_data_2.decode()
root = ET.fromstring(xml)
for neighbor in root.iter('d'):
d = neighbor.text
if "SimHei" in d:
d=d.replace("[","")
d=d.replace("]","")
m = d.split(",")
d = m[4]
p = neighbor.attrib
str = p.get('p')
其實例大致如上,大致給大家講解一下,ET是引用的包的化用,root就是xml文件的根節點,而root.iter則是一個通過名稱查找來返回子標簽的列表的一個方法。在這里我就返回了存有彈幕內容的標簽d。這里在給大家講解一下,這個if "SimHei" in root.iter的作用。因為就是之前在博文中提到的彈幕類型導致的。
如圖,在前幾個標簽的p屬性中的第二個數值都是7,這就對應了之前提到的網上給出的數據類型,其中第二個字段對應的就是彈幕類型,而當這個數值等於7的時候,表示特殊彈幕,此時d標簽里的內容不再是文本了,而是一個列表,至於這個列表怎么使用無從得知。所以需要一個結構來清洗這部分數據,使其只剩下彈幕內容部分。也就是列表中對應的第五個元素,這樣在之后生成詞雲圖的時候就不會產生干擾了。接下來的內容就比較簡單了將獲得的數據存入dataframe結構中。使用pandas包中的.to_csv方法來將其寫入到文件中。
list12 = str.split(',')
list12.append(d)
list12.append(i1)
bullet_screen.append(list12)
print(result_dic)
df = pd.DataFrame(data = bullet_screen,columns=['出現時間','彈幕模式','字號','字體顏色','時間戳','彈幕池','發送者id','歷史彈幕','彈幕內容','作品id'])
#p 字段內容的含義,內容來自於網絡
#第一個參數是彈幕出現的時間以秒數為單位。第二個參數是彈幕的模式1..3滾動彈幕4 5頂端彈幕6.逆向彈幕7精准定位8高級彈幕第三個參數是字號, 12
# 非常小, 16
# 特小, 18
# 小, 25
# 中, 36
# 大, 45
# 很大, 64
# 特別大
# 第四個參數是字體的顏色以HTML顏色的十進制為准第五個參數是Unix格式的時間戳。基准時間為1970 - 1 - 1 08: 00:00第六個參數是彈幕池
# 0普通池
# 1字幕池
# 2特殊池【目前特殊池為高級彈幕專用】
# 第七個參數是發送者的ID,用於“屏蔽此彈幕的發送者”功能
# 第八個參數是彈幕在彈幕數據庫中rowID
# 用於“歷史彈幕”功能。
df.to_csv('bilibili_bullet_screen.csv',index=False)
展示一下生成的數據
11.接下來的程序就是將得到的數據繼續拿給你可視化了。這部分我主要采取的是pyechart包,因為這個包是國人進行開發的,在api的閱讀上有着極強的友好度。我總共生成了兩個圖,一個是柱狀圖,是用來描述時間和彈幕數量的關系。時間采用的是整數非連續型。
讓我來逐一講解代碼
這些進行畫圖所用的方法都在draw_graph.py文件中.
def get_data(aid):
df = pd.read_csv('bilibili_bullet_screen.csv')
print(df.columns)
grouped = df.groupby('作品id')
list = grouped.get_group(aid)
list1 = list['出現時間'].tolist()
list2 = []
上面這部分代碼就是通過視頻的aid來進行分組,就是將視頻id相同的放在一個組里,這樣是為了獲取一個視頻的所有彈幕。groupby這種通用的就不說了。使用groupby方法之后實際返回的類型應該是dataframegroup和dataframe類似。之后通過get_group獲取aid為輸入進來的視頻的分組。
print(list1)
for i in list1:
new = math.floor(i)
list1.remove(i)
list2.append(new)
print(list2)
result = Counter(list2)
這一步做了一個處理,主要是獲得的視頻出現的時間不是整秒數,需要通過向下取整轉化為正數,然后通過Counter來統計彈幕出現描述重復次數並返回Counter類型,這個Counter類型和字典類型比較相似。
list3 = []
for key, value in result.items():
list3.append((key, value))
list3.sort(key=lambda tup: tup[0])
list4 = []
list5 = []
for x, y in list3:
list4.append(x)
list5.append(y)
list6 = (list4, list5)
return list6
統計出彈幕在同一秒出現的次數之后,在使用for循環將其組成字典,然后按照出現時間從小到大排序,最后將字典的key和value分別存入列表中。最后返回一個含有key和value列表的列表。
12.接下來其實就可以考慮畫柱形圖了。
下面這個方法就是畫柱形圖的方法了。 .add_xaxis()方法是添加橫軸數據的方法,將之前生成的時間和彈幕數量的兩個列表進行傳入,再傳入標題。 .add_yaxis()方法就是添加y軸數據的方法,同樣是傳入一個列表。 set_global_opts()中的第一行代碼主要功能就是將x周上的數據旋轉15度。第二行就是設置標題。第三行就是在柱形圖的下面添加一個調節條。
def draw_graph(list1,list2,title):
c = (
Bar()
.add_xaxis(
list1
)
.add_yaxis("每秒彈幕條數", list2)
.set_global_opts(
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
title_opts=opts.TitleOpts(title=title+"柱狀圖"),
datazoom_opts=opts.DataZoomOpts(),
)
.render(title+"_bar"+".html")
)
return c
生成的柱形圖如下:
13.接下來進行的步驟就是通過視頻號來獲取彈幕內容,通過jieba進行分詞,然后使用Counter來統計詞頻,最后返回一個列表
def get_words(aid):
df = pd.read_csv('bilibili_bullet_screen.csv')
print(df.columns)
grouped = df.groupby('作品id')
list8 = grouped.get_group(aid)
list1 = list8['彈幕內容'].tolist()
list2 = [str(i) for i in list1]
txt = "".join(list2)
seg_list = jieba.cut(txt)
c = Counter()
for x in seg_list:
if len(x) > 1 and x != '\r\n':
c[x] += 1
c1 = dict(c)
return c1
14.下面是生成詞雲圖的方法
將剛才生成的詞頻字典進行傳入,在下列代碼中,WordCloud()類是生成詞雲圖的類,add方法中第一個是傳入的名稱,第二個是存儲着詞頻信息的字典,第三個參數是字體大小的范圍。title_opts一項中是對詞雲圖的標題進行設置,在這里我選擇了使用視頻的標題,並將其字體大小設置為23。
def draw_cloud_picture(text,title):
wc=(
WordCloud()
.add(series_name="熱點分析", data_pair=text, word_size_range=[6, 66])
.set_global_opts(
title_opts=opts.TitleOpts(
title=title+"詞雲圖", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
),
to、oltip_opts=opts.TooltipOpts(is_show=True),
)
.render(title+"_cloud"+".html")
)
return wc
生成的詞雲圖如下:
15.然后我考慮了將兩張圖放在一下,於是使用了pyechart中的多圖展示類,tab。
這個就比較簡單了,就是先聲明兩個圖類,然后使用add進行添加。
tab = Tab()
tab.add(c,"bar-example")
tab.add(wc,"line-example")
tab.render(title+".html")
最后生成的圖如下
實驗結果展示及源碼鏈接
生成彈幕文件
生成排行榜視頻信息文件
[碼雲鏈接](https://gitee.com/python_programming/a20172304_duanzhixuan/tree/master/game)
其中bili.py文件是用於爬取近三天排行榜前一百的視頻的數據的
bilibilibullet_screen.py文件時用於爬取彈幕視頻的。
Bilibili_data_engineer.csv是用來存儲生成的視頻的數據的。
bilibili_bullet_screen.csv是用來存儲生成的彈幕數據的。
實驗感想及建議
又上了王老師的課有時熟悉的味道,嚴肅而又活潑,一板一眼的講解中不時穿插着令人輕松的話語。不過給我的感覺比學java的時候要輕松了不少。感覺還是緊張一些的好,熟能生巧,書上的一千行代碼不如自己手打的一百行代碼,老師的講解比較到位,答疑也很及時,就是希望老師能夠多布置一些任務。這樣才能讓我們掌握更多的東西。