作業要求來自於https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3075
- 對象 - TapTap
TapTap 是一個高品質手游玩家社區,只提供原版和官服游戲下載購買的平台。開發者無需接入SDK,即可上傳游戲,海內外開發者都有機會在這里售賣正版安卓游戲。TapTap 提供真實排行榜單和玩家評價,堅持編輯獨立評測推薦。在TapTap 社區,用戶與開發者直接交流,推動游戲改進。
- 范圍 - “Android游戲榜”各大榜單的TOP150游戲
taptap安卓游戲榜有五個榜單,分別為熱門榜(根據下載量)、新品榜(根據近期發行游戲下載量)、預約榜(根據預約量)、熱賣榜(根據游戲售賣量)和熱玩榜(根據玩家游戲啟動量)。注:熱賣榜只有TOP35。
- 爬取限制 - ajax異步請求鏈接獲取數據
在爬取過程中,如果只是根據html元素來爬取網站數據,只能爬到30條數據。由於排行榜的數據是分頁的,在點擊“更多”的按鈕之后才會顯示下一頁的數據,而且網址並沒有發生變化。通過觀察發現網站是通過ajax異步請求了一條鏈接獲取數據,為了爬取整個榜單的數據信息,因此要分析該頁面的請求。
首先打開瀏覽器的開發者工具Network中的XHR(通過XMLHttpRequest方法發送的請求),點擊“更多”后發現一條新請求,進去就會發現這是查看更多數據的異步請求。
其次找到需要爬取的內容——爬取data里的html所有內容。
最后把json格式的響應內容用BeautifulSoup(html, 'html.parser')方法解析,通過對標簽的篩選獲得需要的信息。
為了防止在爬取過程中ip被限制,這里設置了合理的爬取間隔和使用user-agent模擬真實的瀏覽器提取內容(詳細見下面代碼)。user-agent可以在開發者工具→Network→Headers里面找到。
- 爬取內容 - 游戲名、廠商、分類、標簽、評分、排名
詳細代碼如下:

import pymysql from sqlalchemy import create_engine import pandas as pd import requests from bs4 import BeautifulSoup import time import random from urllib.parse import urlencode import jieba from wordcloud import WordCloud import matplotlib.pyplot as plt from os import path from PIL import Image import numpy as np #爬取一條游戲的信息 def agame(url): gamesDetail = {} res = requests.get(url) res.encoding = 'utf-8' soup = BeautifulSoup(res.text,'html.parser') gamesDetail['游戲名'] = soup.select('h1')[0].text.rstrip(' CN')#截取掉游戲名后面的空格和CN標簽 gamesDetail['廠商'] = soup.select('.header-text-author')[0].select('span')[1].text if len(soup.select('.app-rating-score')) >0:#如果游戲存在評分 gamesDetail['評分'] = soup.select('.app-rating-score')[0].text else:#如果游戲不存在評分 gamesDetail['評分'] = soup.select('.app-rating-no-score')[0].text gamesDetail['分類'] = soup.select('li')[12].text.lstrip().rstrip() gamesDetail['標簽'] =' '.join(soup.select('#appTag')[0].text.lstrip().rstrip().split())#獲取標簽並轉為字符串 return gamesDetail #將一頁游戲編碼為utf-8 def toalist(url): res = requests.get(url) res.encoding = 'utf-8' soup = my_get_soup(url)#模擬真實瀏覽器訪問 return alist(soup) #獲取一頁游戲信息 def alist(soup): sleep()#設置合理的爬取間隔 gamesList = [] for games in soup.select('.taptap-top-card'): if len(games.select('div'))>0:#如果存在游戲信息 gamesUrl = games.select('a')[0]['href']#獲取每個游戲詳情頁面的網址 gamesRank = games.select('span')[1].text#獲取游戲排名 gamesDict = agame(gamesUrl) gamesDict['排名'] = gamesRank gamesList.append(gamesDict)#把每個游戲的信息放進字典擴展到列表里 return gamesList #設置合理的爬取間隔 def sleep(): for i in range(5): time.sleep(random.random()*3)#沉睡隨機數的3倍秒數 #隨機選擇user-agent def get_ua(): au = random.choice(uas) return au #模擬真實瀏覽器訪問 def my_get_soup(url): headers = {'user-agent':get_ua()} res = requests.get(url,headers = headers) res.encoding = 'utf-8' soup = BeautifulSoup(res.text,'html.parser') return soup #獲取ajax異步請求的網址 def get_page(i,page): params = { 'page':page+1, 'total': 30 * page, } url = 'https://www.taptap.com/ajax/top/{}?'.format(i)+ urlencode(params) #拼接URL try: r = requests.get(url) if r.status_code == 200: return r.json() # 返回json格式的響應內容 except: return None #在異步請求里找到需要的信息 def get_html(jsondata): if jsondata.get('data'): data = jsondata.get('data') yield { data.get('html'), } #解析json返回的內容 def get_soup(i,page): jsondata = get_page(i, page) for item in get_html(jsondata): html = ''.join(item) soup = BeautifulSoup(html, 'html.parser') return soup #保存每個游戲的標簽 def save_tags(wtxt,games): wclist = [] for j in range(len(games)): wclist.append(games[j]['標簽']) for x in wclist: wtxt.write(x) wtxt.write('\n') wtxt.close() #保存評分最高的前30的游戲標簽 def save_score(wtxt,games): wclist = [] sorted_x = sorted(games, key=lambda x : x['評分'], reverse=True)#游戲以評分降序排列 # 輸出詞頻最大TOP30 for j in range(len(sorted_x[:30])): wclist.append(sorted_x[j]['標簽']) for x in wclist: wtxt.write(x) wtxt.write('\n') wtxt.close() #生成詞雲 def wordCloud(txt): # 分詞 wordsls = jieba.lcut(txt) wcdict = {} for word in wordsls: if word != ' ': wcdict[word] = wcdict.get(word, 0) + 1 # 排序 wcls = list(wcdict.items()) wcls.sort(key=lambda x: x[1], reverse=True) # 去掉文件名,返回目錄 d = path.dirname(__file__) # 打開蒙版圖片 alice_mask = np.array(Image.open(path.join(d, "mask.jpg"))) # 設置詞雲的一些屬性 wc = WordCloud(background_color="white", max_words=2000, mask=alice_mask) # 生成詞雲 wc.generate(txt) # 保存到本地 wc.to_file(path.join(d, "image.png")) # 展示 plt.imshow(wc, interpolation='bilinear') plt.axis("off") plt.show() downloadGames = []#熱門榜 newGames = []#新品榜 reserveGames = []#預約榜 sellGames = []#熱賣榜 playedGames = []#熱門榜 rankList={"download","new","reserve","sell","played"} #不同瀏覽器訪問 uas = ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",\ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134",\ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0"] #連接mysql的賬戶 conInfo = "mysql+pymysql://root:123456@localhost:3306/taptap?charset=utf8" engine = create_engine(conInfo, encoding='utf-8')#初始化引擎 for i in rankList: rankUrl = 'https://www.taptap.com/top/{}'.format(i)#獲取不同榜單的鏈接 if(i == 'download'): for page in range(5): if(page == 0):#第一頁,沒有異步請求 downloadGames.extend(toalist(rankUrl))#把一頁的游戲信息添加到列表里 if (page > 0):#二到五頁,有異步請求 soup = get_soup(i,page) downloadGames.extend(alist(soup)) twtxt = open('tagsDownload.txt', 'w', encoding='utf-8')#將每個游戲標簽寫到文本里 save_tags(twtxt,downloadGames)#保存 tagstxt = open('tagsDownload.txt', 'r', encoding='utf-8').read()#打開標簽文本 wordCloud(tagstxt)#生成標簽詞雲 swtxt = open('scoreDownload.txt', 'w', encoding='utf-8')#將每個游戲評分高的標簽寫到文本里 save_score(swtxt,downloadGames)#保存 scoretxt = open('scoreDownload.txt', 'r', encoding='utf-8').read()#打開評分文本 wordCloud(scoretxt)#生成評分高的標簽詞雲 gamesdf = pd.DataFrame(downloadGames)#形成表格 gamesdf.to_sql(name='download', con=engine, if_exists='append', index=False)#存儲表 conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='123456', db='taptap', charset='utf8')#連接數據庫 if(i == 'new'): for page in range(5): if (page == 0): newGames.extend(toalist(rankUrl)) if (page > 0): soup = get_soup(i,page) newGames.extend(alist(soup)) twtxt = open('tagsNew.txt', 'w', encoding='utf-8') save_tags(twtxt,newGames) tagstxt = open('tagsNew.txt', 'r', encoding='utf-8').read() wordCloud(tagstxt) swtxt = open('scoreNew.txt', 'w', encoding='utf-8') save_score(swtxt,newGames) scoretxt = open('scoreNew.txt', 'r', encoding='utf-8').read() wordCloud(scoretxt) gamesdf = pd.DataFrame(newGames) gamesdf.to_sql(name='new', con=engine, if_exists='append', index=False) conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='123456', db='taptap', charset='utf8') if (i == 'reserve'): for page in range(5): if (page == 0): reserveGames.extend(toalist(rankUrl)) if (page > 0): soup = get_soup(i,page) reserveGames.extend(alist(soup)) twtxt = open('tagsReserve.txt', 'w', encoding='utf-8') save_tags(twtxt,reserveGames) tagstxt = open('tagsReserve.txt', 'r', encoding='utf-8').read() wordCloud(tagstxt) swtxt = open('scoreReserve.txt', 'w', encoding='utf-8') save_score(swtxt,reserveGames) scoretxt = open('scoreReserve.txt', 'r', encoding='utf-8').read() wordCloud(scoretxt) gamesdf = pd.DataFrame(reserveGames) gamesdf.to_sql(name='reserve', con=engine, if_exists='append', index=False) conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='123456', db='taptap', charset='utf8') if (i == 'sell'): for page in range(5): if (page == 0): sellGames.extend(toalist(rankUrl)) if (page > 0): soup = get_soup(i,page) sellGames.extend(alist(soup)) twtxt = open('tagsSell.txt', 'w', encoding='utf-8') save_tags(twtxt,sellGames) tagstxt = open('tagsSell.txt', 'r', encoding='utf-8').read() wordCloud(tagstxt) swtxt = open('scoreSell.txt', 'w', encoding='utf-8') save_score(swtxt,sellGames) scoretxt = open('scoreSell.txt', 'r', encoding='utf-8').read() wordCloud(scoretxt) gamesdf = pd.DataFrame(sellGames) gamesdf.to_sql(name='sell', con=engine, if_exists='append', index=False) conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='123456', db='taptap', charset='utf8') if (i == 'played'): for page in range(5): if (page == 0): playedGames.extend(toalist(rankUrl)) if (page > 0): soup = get_soup(i,page) playedGames.extend(alist(soup)) twtxt = open('tagsPlayed.txt', 'w', encoding='utf-8') save_tags(twtxt,playedGames) tagstxt = open('tagsPlayed.txt', 'r', encoding='utf-8').read() wordCloud(tagstxt) swtxt = open('scorePlayed.txt', 'w', encoding='utf-8') save_score(swtxt,playedGames) scoretxt = open('scorePlayed.txt', 'r', encoding='utf-8').read() wordCloud(scoretxt) gamesdf = pd.DataFrame(playedGames) gamesdf.to_sql(name='played', con=engine, if_exists='append', index=False) conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='123456', db='taptap', charset='utf8')
數據已存到數據庫,熱門榜如下圖所示:
新品榜如下圖所示:
預約榜如下圖所示:
熱賣榜如下圖所示:
熱玩榜如下圖所示:
- 數據分析及文本分析
首先分別爬取了五個榜單TOP150的游戲標簽,看看玩家都喜歡玩什么類型的游戲。詞雲生成結果如下圖所示。
其中發現玩家比較喜歡玩的類型集中於多人、聯機、中文、冒險和策略的游戲(基於熱門榜和熱玩榜),而對新游戲的期待類型則為角色扮演和策略這方面(基於新品榜和預約榜)。由於大部分的玩家為免費玩家,那么付費玩家對於游戲更熱衷於單機、益智、解密和獨立游戲(基於熱賣榜)。
由此可見,現今對於玩家來說可玩性較高的游戲大多為聯機的多人冒險游戲,,但對於玩家所期待的角色扮演和策略游戲在市場上受大眾歡迎的不多,另外付費玩家想在游戲體現的更趨向於獨立完成和需要動腦的,因此各大游戲廠商要想做出一個大賣、口碑又好的游戲需要定期了解玩家的游戲喜好。
接下來分析排行榜中高分游戲TOP30的游戲有哪些。由於評分是根據數以萬計的玩家打的分數來的,高分游戲的類型更能體現玩家真實的喜好。詞雲生成結果如下圖所示。
其中高分游戲排行與綜合榜單的游戲類型有所不同,比較受歡迎的是單機、中文、獨立游戲和UP主推薦的游戲(基於熱門榜和熱玩榜),最受期待的是角色扮演和單機游戲(基於新品榜和預約榜),但對於付費玩家來說游戲類型並沒有過多的變化(熱賣榜只有TOP35所以有影響)。
可以看出,口碑好的高分游戲大多集中於單機和獨立游戲,而多數付費游戲基本上評分比免費游戲要高。一般來說,單機和獨立游戲更加注重玩家的體驗性,而多人聯機的游戲更注重於商業化,要做出好口碑並不容易。因此,游戲制作公司如果把大多心思放在游戲劇本、提高玩家的游戲體驗性和玩家與游戲的融合度,將能得到更好的口碑,也會有更多的玩家願意為這個游戲付費。
之后來分析各大榜單上評分的數值分布。能上榜的游戲與宣傳力度也有很大關系,但是游戲是否與宣傳所說的那么好玩還是需要參考一下評分。分析圖如下圖所示。
從中發現,預約榜和熱賣榜高分段的游戲居多,熱門榜和熱玩榜分值偏中上,只有新品榜的游戲低分接近一般。由此可知,游戲在未面世時廠商的宣傳度會大大影響玩家的期待值,因此許多玩家對新游戲抱有很大的期待,可當開始內測、公測的時候,游戲可能並沒有廠商說的那么好,導致大量玩家對該游戲失望甚至“脫坑”,之后只有經歷過玩家的一番篩選,好的游戲才慢慢脫穎而出,最終受到玩家的追捧並大賣。
最后看一下各大榜單評分TOP10的游戲。結果如下圖所示。
爬蟲測試到此結束。