天天看點

python爬蟲 TapTap

作業要求來自于https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3075

  • 對象 - TapTap

TapTap 是一個高品質手遊玩家社群,隻提供原版和官服遊戲下載下傳購買的平台。開發者無需接入SDK,即可上傳遊戲,海内外開發者都有機會在這裡售賣正版安卓遊戲。TapTap 提供真實排行榜單和玩家評價,堅持編輯獨立評測推薦。在TapTap 社群,使用者與開發者直接交流,推動遊戲改進。

  • 範圍 - “Android遊戲榜”各大榜單的TOP150遊戲

taptap安卓遊戲榜有五個榜單,分别為熱門榜(根據下載下傳量)、新品榜(根據近期發行遊戲下載下傳量)、預約榜(根據預約量)、熱賣榜(根據遊戲售賣量)和熱玩榜(根據玩家遊戲啟動量)。注:熱賣榜隻有TOP35。

python爬蟲 TapTap
  • 爬取限制 - ajax異步請求連結擷取資料

 在爬取過程中,如果隻是根據html元素來爬取網站資料,隻能爬到30條資料。由于排行榜的資料是分頁的,在點選“更多”的按鈕之後才會顯示下一頁的資料,而且網址并沒有發生變化。通過觀察發現網站是通過ajax異步請求了一條連結擷取資料,為了爬取整個榜單的資料資訊,是以要分析該頁面的請求。

首先打開浏覽器的開發者工具Network中的XHR(通過XMLHttpRequest方法發送的請求),點選“更多”後發現一條新請求,進去就會發現這是檢視更多資料的異步請求。

其次找到需要爬取的内容——爬取data裡的html所有内容。

最後把json格式的響應内容用BeautifulSoup(html, 'html.parser')方法解析,通過對标簽的篩選獲得需要的資訊。

為了防止在爬取過程中ip被限制,這裡設定了合理的爬取間隔和使用user-agent模拟真實的浏覽器提取内容(詳細見下面代碼)。user-agent可以在開發者工具→Network→Headers裡面找到。

python爬蟲 TapTap
  • 爬取内容 - 遊戲名、廠商、分類、标簽、評分、排名

 詳細代碼如下:

python爬蟲 TapTap
python爬蟲 TapTap
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')      

爬取taptap

 資料已存到資料庫,熱門榜如下圖所示:

python爬蟲 TapTap

新品榜如下圖所示:

python爬蟲 TapTap

 預約榜如下圖所示:

python爬蟲 TapTap

熱賣榜如下圖所示:

python爬蟲 TapTap

熱玩榜如下圖所示:

python爬蟲 TapTap
  • 資料分析及文本分析

 首先分别爬取了五個榜單TOP150的遊戲标簽,看看玩家都喜歡玩什麼類型的遊戲。詞雲生成結果如下圖所示。

python爬蟲 TapTap

其中發現玩家比較喜歡玩的類型集中于多人、聯機、中文、冒險和政策的遊戲(基于熱門榜和熱玩榜),而對新遊戲的期待類型則為角色扮演和政策這方面(基于新品榜和預約榜)。由于大部分的玩家為免費玩家,那麼付費玩家對于遊戲更熱衷于單機、益智、解密和獨立遊戲(基于熱賣榜)。

由此可見,現今對于玩家來說可玩性較高的遊戲大多為聯機的多人冒險遊戲,,但對于玩家所期待的角色扮演和政策遊戲在市場上受大衆歡迎的不多,另外付費玩家想在遊戲展現的更趨向于獨立完成和需要動腦的,是以各大遊戲廠商要想做出一個大賣、口碑又好的遊戲需要定期了解玩家的遊戲喜好。

接下來分析排行榜中高分遊戲TOP30的遊戲有哪些。由于評分是根據數以萬計的玩家打的分數來的,高分遊戲的類型更能展現玩家真實的喜好。詞雲生成結果如下圖所示。

python爬蟲 TapTap

其中高分遊戲排行與綜合榜單的遊戲類型有所不同,比較受歡迎的是單機、中文、獨立遊戲和UP主推薦的遊戲(基于熱門榜和熱玩榜),最受期待的是角色扮演和單機遊戲(基于新品榜和預約榜),但對于付費玩家來說遊戲類型并沒有過多的變化(熱賣榜隻有TOP35是以有影響)。

可以看出,口碑好的高分遊戲大多集中于單機和獨立遊戲,而多數付費遊戲基本上評分比免費遊戲要高。一般來說,單機和獨立遊戲更加注重玩家的體驗性,而多人聯機的遊戲更注重于商業化,要做出好口碑并不容易。是以,遊戲制作公司如果把大多心思放在遊戲劇本、提高玩家的遊戲體驗性和玩家與遊戲的融合度,将能得到更好的口碑,也會有更多的玩家願意為這個遊戲付費。

 之後來分析各大榜單上評分的數值分布。能上榜的遊戲與宣傳力度也有很大關系,但是遊戲是否與宣傳所說的那麼好玩還是需要參考一下評分。分析圖如下圖所示。

python爬蟲 TapTap

從中發現,預約榜和熱賣榜高分段的遊戲居多,熱門榜和熱玩榜分值偏中上,隻有新品榜的遊戲低分接近一般。由此可知,遊戲在未面世時廠商的宣傳度會大大影響玩家的期待值,是以許多玩家對新遊戲抱有很大的期待,可當開始内測、公測的時候,遊戲可能并沒有廠商說的那麼好,導緻大量玩家對該遊戲失望甚至“脫坑”,之後隻有經曆過玩家的一番篩選,好的遊戲才慢慢脫穎而出,最終受到玩家的追捧并大賣。

最後看一下各大榜單評分TOP10的遊戲。結果如下圖所示。

python爬蟲 TapTap

爬蟲測試到此結束。