1. 程式人生 > >go語言爬蟲 - TapTap用戶都喜歡些什麽遊戲

go語言爬蟲 - TapTap用戶都喜歡些什麽遊戲

col 詞典 抓取 url 易雲 ejs index 生成 erro

前面的廢話

說到爬蟲,首先想到的當然是python~ 它在機器學習、爬蟲數據分析領域可謂是如日中天,十分熱門。但我最近在學習go語言,所以就用go寫了

TapTap社區

這是一個高品質的遊戲分享社區,可以說是手機上的steam。上面的用戶質量非常高,核心玩家多,看到他們那麽用心的寫那麽多長評論,讓我驚嘆,所以這次打算拿它來爬取數據練練手,下面先看看成果

先看效果,這裏的玩家,都喜歡玩啥類型遊戲呀?

根據 下載榜 裏遊戲標簽的詞頻統計出:
技術分享圖片
發現單機、二次元、MOBA、策略等標簽比較突出

讓我們加入玩家評分的權重,評分是根據數以萬計的玩家打的分數來的,多個遊戲相同標簽會求平均值。
看看有什麽變化?
技術分享圖片

詞雲完全不一樣了呢,視覺錯位、腦洞、哲理等標簽的評分較高,這些才是玩家真實的喜好,為啥加入評分權重變化這麽大呢,讓我們看一下究竟是哪些遊戲評分這麽高!
技術分享圖片

原來是紀念碑谷、猿騎、艾希等遊戲。而紀念碑谷(tag:視覺錯位)的評分竟然達到了10分!!(7951條評價)

不過這款遊戲也確實讓我服氣,連我媽媽、老婆她們不太玩遊戲的,都很喜歡這款遊戲呢~

技術分享圖片
技術分享圖片

那麽下面就都把評分權重加進去,看看玩家心裏的真實需求

接著分析新品榜

技術分享圖片

遊戲名稱(根據排名權重+評分權重)
技術分享圖片

看看我們分析出來的跟榜單上的有什麽不一樣?
技術分享圖片

可以看到,加入評分權重後,像《我叫MT4》、《王牌戰爭:代號英雄》這種雖然排名靠前,但是口碑很差的遊戲

,幾乎在我們的分析圖上就看不見啦。(所以在taptap上,就算你花錢刷榜上去了,也並沒有太多用,玩家的眼睛是雪亮的,哈哈哈)

預約榜

技術分享圖片
遊戲名稱(根據排名權重+評分權重)
技術分享圖片

這裏可以看出未來市場的玩家需求,《全職覺醒》、《堡壘之夜》等都是期待比較高的

熱玩榜

遊戲名稱(根據排名權重+評分權重)
技術分享圖片
《絕地求生、刺激戰場》也是突出遊戲之一,看來taptap的玩家,也是很喜歡吃雞的

實現方式

goquery解析html
iconv-go進行編碼轉換
sego用來中文分詞
wordart實現詞雲效果

現在先做了個簡單的版本,完整版是還想實現抓取某個遊戲的玩家評論,進行分詞,情感分析的。

先分析html結構,找到一個遊戲信息裏包含哪些html元素,然後用goquery解析
技術分享圖片

使用谷歌瀏覽器,按F12可以很方便的找到元素哦

然後定義一個結構體,用來存放數據

type GameInfo struct {
    Rank     int      //排名
    TapTapID string   //遊戲ID
    Name     string   //遊戲名
    Company  string   //公司名
    Score    float64  //遊戲評分
    IconUrl  string   //圖標地址
    Type     string   //遊戲類型
    tags     []string //標簽
}

分析單個遊戲信息

//解析一個遊戲信息
func ParseGameInfoCell(selection *goquery.Selection) {
    gameInfo := GameInfo{}
    nameA := selection.Find(".card-middle-title ")
    gameInfo.TapTapID = nameA.AttrOr("href", "")
    gameInfo.TapTapID = gameInfo.TapTapID[strings.LastIndex(gameInfo.TapTapID, "/")+1:]
    gameInfo.Name = nameA.Find("h4").Text()
    gameInfo.Company = selection.Find(".card-middle-author").Find("a").Text()
    score, _ := strconv.ParseFloat(selection.Find(".middle-footer-rating").Find("span").Text(), 64)
    gameInfo.Score = score
    gameInfo.IconUrl = selection.Find(".card-left-image").Find("img").AttrOr("src", "")
    tempRank, _ := strconv.ParseInt(selection.Find(".top-card-order-text").Text(), 10, 32)
    gameInfo.Rank = int(tempRank)

    gameInfo.Type = selection.Find(".card-middle-footer").Find("a").Text()

    tagsAList := selection.Find(".card-tags").Find("a")

    tagsAList.Each(func(i int, selectionA *goquery.Selection) {
        gameInfo.tags = append(gameInfo.tags, selectionA.Text())
    })

    GameInfoList = append(GameInfoList, gameInfo)
    //fmt.Printf("%v\n", gameInfo)
}

但是很快就遇到了問題,因為排行榜的數據是分頁的,我們請求一次只能得到30條數據,於是我們找到了“更多”按鈕,發現裏面通過ajax異步的請求了一條鏈接獲取數據。

https://www.taptap.com/ajax/top/played?page=2&total=30

page就代表的頁數,根據排行榜總數量150,每頁30條可以得出一共有5頁。這樣我們就可以循環5次去請求所有的數據了

func ReqRankPage(page int) {
    res, err := http.Get("https://www.taptap.com/ajax/top/" + rankTypeName + "?page=" + strconv.Itoa(page))
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    if res.StatusCode != 200 {
        log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
    }

    jsonBs, err := ioutil.ReadAll(res.Body)
    tPageJson := TPageJson{}
    err = json.Unmarshal(jsonBs, &tPageJson)
    if err != nil {
        fmt.Println("解析json錯誤", err)
    }

    var htmlRead io.Reader = strings.NewReader(tPageJson.Data.Html)
    doc, err := goquery.NewDocumentFromReader(htmlRead)
    if err != nil {
        log.Fatal(err)
    }

    doc.Find(".taptap-top-card").Each(func(i int, selection *goquery.Selection) {
        ParseGameInfoCell(selection)
    })
}

全部代碼

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "github.com/PuerkitoBio/goquery"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "strconv"
    "strings"
    "math"
)

type TPageJson struct {
    Success bool          `json:"success"`
    Data    TPageDataJson `json:"data"`
}

type TPageDataJson struct {
    Html string `json:"html"`
    Next string `json:"next"`
}

type GameInfo struct {
    Rank     int      //排名
    TapTapID string   //遊戲ID
    Name     string   //遊戲名
    Company  string   //公司名
    Score    float64  //遊戲評分
    IconUrl  string   //圖標地址
    Type     string   //遊戲類型
    tags     []string //標簽
}

var GameInfoList []GameInfo
var rankTypeName = "reserve"

var rankTypes = []string{"download", "new", "reserve", "sell", "played"}

func main() {

    for _, typeName := range rankTypes {
        GameInfoList = []GameInfo{}

        rankTypeName = typeName
        //每個排行榜有5頁數據(根據總數150條,每頁30條得出)
        for i := 1; i <= 5; i++ {
            ReqRankPage(i)
        }
        //生成標簽詞典
        GenerateTags()
        GenerateGameNames()
        fmt.Println("生成排行榜:", rankTypeName, "完畢")
    }
}

func GenerateGameNames() {
    var tagsBuffer bytes.Buffer
    tagsBuffer.WriteString("word;weight\n")

    for _, gameInfo := range GameInfoList {
        //weightSize := 150 - gameInfo.Rank //把排名的權值加上
        //weightSize := int(math.Ceil(float64(150-gameInfo.Rank) * gameInfo.Score)) //把排名的權值加上
        weightSize := int(math.Ceil(gameInfo.Score*100)) //把排名的權值加上

        tagsBuffer.WriteString(gameInfo.Name)
        tagsBuffer.WriteString(";")
        tagsBuffer.WriteString(strconv.Itoa(weightSize))
        tagsBuffer.WriteString("\n")
    }

    WriteFile(rankTypeName+"_names_score.csv", tagsBuffer.String())
}

func GenerateTags() {
    tagsCountDic := make(map[string]int)
    tagsScoreDic := make(map[string]float64)

    var tagsBuffer bytes.Buffer
    tagsBuffer.WriteString("word;weight;")

    for _, gameInfo := range GameInfoList {
        for _, tag := range gameInfo.tags {
            tagsCountDic[tag]++
            tagsScoreDic[tag] += gameInfo.Score*100
        }
    }

    for key, value := range tagsCountDic {
        tagsBuffer.WriteString(key)
        tagsBuffer.WriteString(";")
        //tagsBuffer.WriteString(strconv.Itoa( value))
        tagsBuffer.WriteString(strconv.Itoa( int(tagsScoreDic[key]/float64(value))))
        tagsBuffer.WriteString("\n")
    }
    WriteFile(rankTypeName+"_tags_score.csv", tagsBuffer.String())
}

func WriteFile(name, content string) {
    data := []byte(content)
    if ioutil.WriteFile(name, data, 0644) == nil {
        fmt.Println("寫入文件成功:", name)
    }
}

func ReqRankPage(page int) {
    res, err := http.Get("https://www.taptap.com/ajax/top/" + rankTypeName + "?page=" + strconv.Itoa(page))
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    if res.StatusCode != 200 {
        log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
    }

    jsonBs, err := ioutil.ReadAll(res.Body)
    tPageJson := TPageJson{}
    err = json.Unmarshal(jsonBs, &tPageJson)
    if err != nil {
        fmt.Println("解析json錯誤", err)
    }

    var htmlRead io.Reader = strings.NewReader(tPageJson.Data.Html)
    doc, err := goquery.NewDocumentFromReader(htmlRead)
    if err != nil {
        log.Fatal(err)
    }

    doc.Find(".taptap-top-card").Each(func(i int, selection *goquery.Selection) {
        ParseGameInfoCell(selection)
    })
}

//解析一個遊戲信息
func ParseGameInfoCell(selection *goquery.Selection) {
    gameInfo := GameInfo{}
    nameA := selection.Find(".card-middle-title ")
    gameInfo.TapTapID = nameA.AttrOr("href", "")
    gameInfo.TapTapID = gameInfo.TapTapID[strings.LastIndex(gameInfo.TapTapID, "/")+1:]
    gameInfo.Name = nameA.Find("h4").Text()
    gameInfo.Company = selection.Find(".card-middle-author").Find("a").Text()
    score, _ := strconv.ParseFloat(selection.Find(".middle-footer-rating").Find("span").Text(), 64)
    gameInfo.Score = score
    gameInfo.IconUrl = selection.Find(".card-left-image").Find("img").AttrOr("src", "")
    tempRank, _ := strconv.ParseInt(selection.Find(".top-card-order-text").Text(), 10, 32)
    gameInfo.Rank = int(tempRank)

    gameInfo.Type = selection.Find(".card-middle-footer").Find("a").Text()

    tagsAList := selection.Find(".card-tags").Find("a")

    tagsAList.Each(func(i int, selectionA *goquery.Selection) {
        gameInfo.tags = append(gameInfo.tags, selectionA.Text())
    })

    GameInfoList = append(GameInfoList, gameInfo)
    //fmt.Printf("%v\n", gameInfo)
}

這樣就可以把爬取下來的數據,寫成文件,生成出一張張的詞雲進行分析啦

總結

第一次玩爬蟲,所以寫的不是很好,爬蟲還有很多技術,本文裏都沒有涉及。如防止反爬,賬號登陸等。寫這個也是想多寫一點go代碼,以後可能會把go作為我的主語言進行開發

接下來研究下爬取網易雲音樂~ 嘿嘿嘿

go語言爬蟲 - TapTap用戶都喜歡些什麽遊戲