go语言爬虫 - TapTap用户都喜欢些什么游戏

前面的废话

说到爬虫,首先想到的当然是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作为我的主语言进行开发

接下来研究下爬取网易云音乐~ 嘿嘿嘿

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 第一次写,有什么不合理的,希望你萌告诉我..... 1.产品概况 1.1.体验环境 产品版本:「TapTap」版本...
    铃音Lain阅读 10,042评论 2 8
  • 王成打卡 领导者的资质#8期#关爱组 皓庭(北京)科技有限公司 一、【知~勤学】《以奋斗者为本》有 二、【行~实践...
    王成皓庭新风阅读 109评论 0 0
  • 母亲是爱美的,我是长大后才发现的。 儿时印象里,母亲向来不喜欢雪花膏之类的护肤物。她说不喜欢闻那香味,...
    冀双七阅读 1,424评论 11 5
  • 香港乐坛的奖项从上个世纪八九十年代的时候就已经出现了,而“金针奖”这个奖项是香港电台举办的十大中文金曲中,所评选出...
    娱热文阅读 268评论 0 0
  • 文/冬日暖阳 初夏清晨,微风掠过,带着冰爽的凉气,一朵朵皎洁纯净的花朵,点缀在翠绿的枝头,洁白透亮的花蕊竞相怒放,...
    冬日暖阳love阅读 516评论 5 8