發現Github和知乎上關於steam的爬蟲和資料分析比較少,同時想找個專案實戰一下,而且steam對於爬蟲相對友好,非常適合作為一個練手的專案,於是就有了這篇文章。

分為兩大部分

一。爬取steam遊戲資訊:需要有相關包‘requests’,‘bs4’,‘pandas’,‘re’

二。清洗資料及資料視覺化:需要有相關包‘pandas’,‘re’,matplotlib’,‘pandas’,‘numpy’

一.爬取steam遊戲資訊

整體來說steam還是很好爬的,不加headers都可以成功get到頁面,但是因為有的遊戲頁面進入前會驗證年齡之類的,解決方法:重新抓重新定向的頁面。我嫌麻煩,直接在賬號設定裡可以關閉提示,然後加headers就沒有這個問題了。而且不需要代理池和限制頻率,還是很友好的。

1.1 先爬熱銷遊戲的列表

主要就提取遊戲的連結和ID,轉成dataframe然後儲存。steam每個遊戲都有自己的ID,比如csgo的連結是store。steampowered。com/app/730/CounterStrike_Global_Offensive/

730就是遊戲的ID,後面就是遊戲的名字,即使不加名字也是可以重定向到這個頁面。本來是想透過連結提取遊戲名字的,結果發現漢字名字在連結中不會顯示,比如三國志14:

https://

store。steampowered。com/

app/872410/14/

的只會顯示14,索性放棄,在後面1。2中再提取遊戲名字。另外get的網址後面已經加上過濾DLC的引數了。

如果你是Python初學者:一共需要修改3個引數 一個是headers,一個是爬取頁數n的值,以及儲存路徑path,然後run就可以了。

import

requests

from

bs4

import

BeautifulSoup

import

re

import

pandas

as

pd

headers

=

{

‘Accept’

‘’

‘Accept-Encoding’

‘’

‘Accept-Language’

‘’

‘Cache-Control’

‘’

‘Connection’

‘’

‘Cookie’

‘’

‘Host’

‘’

‘Sec-Fetch-Mode’

‘’

‘Sec-Fetch-Site’

‘’

‘Sec-Fetch-User’

‘’

‘Upgrade-Insecure-Requests’

‘’

‘User-Agent’

‘’

}

#替換你自己的headers

n

=

20

#n代表爬取到多少頁

path

=

‘1。xlsx’

#修改你的儲存位置

def

getgamelist

n

):

linklist

=

[]

IDlist

=

[]

for

pagenum

in

range

1

n

):

r

=

requests

get

‘https://store。steampowered。com/search/?ignore_preferences=1&category1=998&os=win&filter=globaltopsellers&page=

%d

%

pagenum

headers

=

headers

soup

=

BeautifulSoup

r

text

‘lxml’

soups

=

soup

find_all

href

=

re

compile

r

“https://store。steampowered。com/app/”

),

class_

=

“search_result_row ds_collapse_flag”

for

i

in

soups

i

=

i

attrs

i

=

i

‘href’

link

=

re

search

‘https://store。steampowered。com/app/(\d*?)/’

i

group

()

ID

=

re

search

‘https://store。steampowered。com/app/(\d*?)/(。*?)/’

i

group

1

linklist

append

link

IDlist

append

ID

print

‘已完成’

+

str

pagenum

+

‘頁,目前共’

+

str

len

linklist

)))

return

linklist

IDlist

def

getdf

n

):

#轉df

linklist

IDlist

=

getgamelist

n

df

=

pd

DataFrame

list

zip

linklist

IDlist

)),

columns

=

‘Link’

‘ID’

])

return

df

if

__name__

==

“__main__”

df

=

getdf

n

#n代表爬取到多少頁

df

to_excel

path

#儲存

df是這個樣子的:

用python對steam一萬個遊戲的資料分析

1.2爬詳細資訊。

基本思路是對df遍歷每行資料。對於每行來說,用requests。get(Link),再用bs4解析,然後提取出關鍵字(名字,價格,好評率,評論等),然後寫入df中。

def

gamename

soup

):

#遊戲名字

try

a

=

soup

find

class_

=

“apphub_AppName”

k

=

str

a

string

except

a

=

soup

find

class_

=

“apphub_AppName”

k

=

str

a

text

return

k

def

gameprice

soup

):

#價格

try

a

=

soup

findAll

class_

=

“discount_original_price”

for

i

in

a

if

re

search

‘¥|free|免費’

str

i

),

re

IGNORECASE

):

a

=

i

k

=

str

a

string

replace

‘ ’

‘’

replace

\n

‘’

replace

\r

‘’

replace

‘ ’

‘’

except

a

=

soup

findAll

class_

=

“game_purchase_price price”

for

i

in

a

if

re

search

‘¥|free|免費’

str

i

),

re

IGNORECASE

):

a

=

i

k

=

str

a

string

replace

‘ ’

‘’

replace

\n

‘’

replace

\r

‘’

replace

‘ ’

‘’

return

k

def

taglist

soup

):

#標籤列表

list1

=

[]

a

=

soup

find_all

class_

=

“app_tag”

for

i

in

a

k

=

str

i

string

replace

‘ ’

‘’

replace

\n

‘’

replace

\r

‘’

if

k

==

‘+’

pass

else

list1

append

k

list1

=

str

\n

join

list1

))

return

list1

def

description

soup

):

#遊戲描述

a

=

soup

find

class_

=

“game_description_snippet”

k

=

str

a

string

replace

‘ ’

‘’

replace

\n

‘’

replace

\r

‘’

return

k

def

reviewsummary

soup

):

#總體評價

a

=

soup

find

class_

=

“summary column”

try

k

=

str

a

span

string

except

k

=

str

a

text

return

k

def

getdate

soup

):

#發行日期

a

=

soup

find

class_

=

“date”

k

=

str

a

string

return

k

def

userreviewsrate

soup

):

#總體數量好評率

a

=

soup

find

class_

=

“user_reviews_summary_row”

k

=

str

((

a

attrs

)[

‘data-tooltip-html’

])

return

k

def

developer

soup

):

#開發商

a

=

soup

find

id

=

“developers_list”

k

=

str

a

a

string

return

k

def

getreviews

ID

):

#獲取評論

r1

=

requests

get

‘https://store。steampowered。com/appreviews/

%s

?cursor=*&day_range=30&start_date=-1&end_date=-1&date_range_type=all&filter=summary&language=schinese&l=schinese&review_type=all&purchase_type=all&playtime_filter_min=0&playtime_filter_max=0&filter_offtopic_activity=1’

%

str

ID

),

headers

=

headers

timeout

=

10

soup

=

BeautifulSoup

r1

json

()[

‘html’

],

‘lxml’

a

=

soup

findAll

class_

=

“content”

list1

=

[]

for

i

in

a

list1

append

i

text

replace

‘ ’

‘’

replace

\n

‘’

replace

\r

‘’

replace

‘ ’

‘,’

))

k

=

str

\n

join

list1

))

return

k

def

getdetail

x

):

tag

des

reviews

date

rate

dev

review

name

price

=

‘ ’

‘ ’

‘ ’

‘ ’

‘ ’

‘ ’

‘ ’

‘ ’

‘ ’

global

count

try

r

=

requests

get

x

‘Link’

],

headers

=

headers

timeout

=

10

except

print

‘伺服器無響應1’

try

r

=

requests

get

x

‘Link’

],

headers

=

headers

timeout

=

10

except

print

‘伺服器無響應2’

try

r

=

requests

get

x

‘Link’

],

headers

=

headers

timeout

=

10

except

print

‘伺服器無響應3’

try

soup

=

BeautifulSoup

r

text

‘lxml’

name

=

gamename

soup

tag

=

taglist

soup

des

=

description

soup

reviews

=

reviewsummary

soup

date

=

getdate

soup

rate

=

userreviewsrate

soup

dev

=

developer

soup

review

=

getreviews

str

x

‘ID’

]))

price

=

gameprice

soup

print

‘已完成: ’

+

name

+

str

x

‘ID’

])

+

‘第

%d

個’

%

count

except

print

‘未完成: ’

+

str

x

‘ID’

])

+

‘第

%d

個’

%

count

price

=

‘error’

count

+=

1

return

name

price

tag

des

reviews

date

rate

dev

review

if

__name__

==

“__main__”

df1

=

pd

read_excel

‘1。xlsx’

count

=

1

df1

‘詳細’

=

df1

apply

lambda

x

getdetail

x

),

axis

=

1

df1

‘名字’

=

df1

apply

lambda

x

x

‘詳細’

][

0

],

axis

=

1

df1

‘價格’

=

df1

apply

lambda

x

x

‘詳細’

][

1

],

axis

=

1

df1

‘標籤’

=

df1

apply

lambda

x

x

‘詳細’

][

2

],

axis

=

1

df1

‘描述’

=

df1

apply

lambda

x

x

‘詳細’

][

3

],

axis

=

1

df1

‘近期評價’

=

df1

apply

lambda

x

x

‘詳細’

][

4

],

axis

=

1

df1

‘發行日期’

=

df1

apply

lambda

x

x

‘詳細’

][

5

],

axis

=

1

df1

‘近期數量好評率’

=

df1

apply

lambda

x

x

‘詳細’

][

6

],

axis

=

1

df1

‘開發商’

=

df1

apply

lambda

x

x

‘詳細’

][

7

],

axis

=

1

df1

‘評論’

=

df1

apply

lambda

x

x

‘詳細’

][

8

],

axis

=

1

df1

to_excel

‘2。xlsx’

print

‘已完成全部’

大概說幾句,各個函式中實在懶得給變數起名字了。評價指標選擇的是近期,不是總體。因為我沒想到能夠用apply函式同時,在dataframe中寫入多列的方法。所以傳送一次get請求後把各類資訊作為元組打包寫入[‘詳細’],最終還要把[‘詳細’]裡的元組再依次提取出來寫入各列。結果如下圖,

用python對steam一萬個遊戲的資料分析

我一共爬了一萬個遊戲,不同時期獲取的結果也會有所不同,因為地區限制等原因某些遊戲不在國區賣,所以是直接df中drop還是自備梯子就取決於你自己了。

1.3爬線上人數

steam自己有個資料統計的網頁,直接while迴圈+time。sleep簡單粗暴,每10分鐘以當前時間為名把資料直接儲存為xlsx格式。我直接丟vps上24小時跑了。

while True:

try:

r = requests。get(‘https://store。steampowered。com/stats/Steam-Game-and-Player-Statistics?l=schinese’,headers = headers ,timeout=15)

if r。status_code == 200:

soup = BeautifulSoup(r。text, ‘lxml’)

a = soup。findAll(class_=“player_count_row”)

NOW =[]

MAX=[]

ID=[]

for i in a :

NOW。append(str(i。contents[1]。span。string))

MAX。append(str(i。contents[3]。span。string))

ID。append(re。search(‘\d+’,str(i。contents[7]。a。attrs[‘href’]))。group())

df = pd。DataFrame(list(zip(NOW, MAX,ID)),

columns =[‘now’,‘max’, ‘ID’])

df1 = df。set_index(‘ID’)

path_stats = str(time。strftime(“%Y年%m月%d日%H時%M分%S秒”,time。localtime()))+‘。xlsx’

df1。to_excel(path_stats)

print(path_stats)

time。sleep(600)

except:

pass

二。清洗資料及資料視覺化:

2。1資料清洗

這部分相對簡單,因為爬取的資料已經相對來說比較規範了。由於我忘記備份最初始的原始文件了,所以以下範例只用200條遊戲資料做的。

首先

import pandas as pd

import re

然後讀取之前爬蟲的結果

path

=

‘’

df

=

pd

read_excel

path

index_col

=

0

df

info

()

#看一下資料結構

結果如下

用python對steam一萬個遊戲的資料分析

首先看一下價格這一列

用python對steam一萬個遊戲的資料分析

實際在爬蟲爬取的遊戲中,大多數都是價格,少部分是‘免費遊玩’‘Free’之類的,極少部分是遊戲demo,遊戲demo這個就很煩,還要根據實際情況判斷遊戲價格,由於數量極少,直接歸為免費遊戲。

def price(x):

try:

pricenum = int(x[‘價格’]。replace(‘¥’,‘’))

except:

pricenum = 0

return pricenum

df[‘價格’] = df。apply(lambda x:price(x),axis=1)

df[‘價格’] = pd。to_numeric(df[‘價格’])#轉為int64

df。info()

結果如下,可以看到價格一列已經變成int64

用python對steam一萬個遊戲的資料分析

接下來提取評價人數和好評率

def getreviewsnum(x):

x1 = x[‘總體數量好評率’]

x2 = x[‘總體評價’]

if re。search(‘過去 30 天內的 (。*?) 篇使用者評測中有 (\d*%) 為好評。’,x1):

num = re。search(‘過去 30 天內的 (。*?) 篇使用者評測中有 (\d*%) 為好評。’,x1)。group(1)

elif re。search(‘(\d*) 篇使用者的遊戲評測中有 (\d*%) 為好評。’,x1):

num = re。search(‘(\d*) 篇使用者的遊戲評測中有 (\d*%) 為好評。’,x1)。group(1)

elif re。search(‘\d* 篇使用者評測’,x2):

num = re。search(‘(\d*) 篇使用者評測’,x2)。group(1)

else:

num = ‘0’

return num

def getreviewsrate(x):

x = x[‘總體數量好評率’]

if re。search(‘過去 30 天內的 (。*?) 篇使用者評測中有 (\d*%) 為好評。’,x):

rate = re。search(‘過去 30 天內的 (。*?) 篇使用者評測中有 (\d*%) 為好評。’,x)。group(2)

elif re。search(‘(\d*) 篇使用者的遊戲評測中有 (\d*%) 為好評。’,x):

rate = re。search(‘(\d*) 篇使用者的遊戲評測中有 (\d*%) 為好評。’,x)。group(2)

else :

rate=‘’

return rate

df[‘評價數量’]=df。apply(lambda x:getreviewsnum(x),axis=1)

df[‘好評率’]=df。apply(lambda x:getreviewsrate(x),axis=1)

df

結果如下,可以看到後面多了2列

用python對steam一萬個遊戲的資料分析

然後同價格一樣,轉一下格式

df

‘評價數量’

=

df

‘評價數量’

apply

lambda

x

x

replace

‘,’

‘’

))

df

‘好評率’

=

df

‘好評率’

apply

lambda

x

str

x

replace

‘%’

‘’

))

df

‘評價數量’

=

pd

to_numeric

df

‘評價數量’

])

df

‘好評率’

=

pd

to_numeric

df

‘好評率’

])

df

‘ID’

=

df

‘ID’

astype

‘str’

#這裡順路把ID轉為str

df

to_excel

path

最後說一下發行時間這裡,這裡又有一些坑,因為存在部分資料沒有日或者月的情況,所以我並沒找到簡便的方式把日期變成datetime格式。這裡如果有大佬可以教我一下,不勝感激。

但是方法總是有的嘛,開啟excel,把這一列改為短日期格式,但是並沒有變化。這裡如果雙擊一下那個單元格,發現變成了我們想要的格式,

用python對steam一萬個遊戲的資料分析

怎麼批次改呢,我在網上搜到這樣的方式

資料→分列→下一步→下一步(選擇日期)→完成

然後就ok了,5秒搞定。

這時我們重新讀一下df發現已經變成datetime64格式了。

用python對steam一萬個遊戲的資料分析

把爬取過程中產生的沒用的列刪掉,最後儲存

df=df。drop(‘Unnamed: 0。1’, axis=1)

df。to_excel(path)

關於遊戲玩家實時數量的資料我打算攢夠一個月再做清洗分析,等資料量夠了我再補這部分的內容。

2.2 資料視覺化

首先

#coding:utf-8

import

pandas

as

pd

import

numpy

as

np

import

matplotlib。pyplot

as

plt

from

matplotlib

import

cm

import

datetime

plt

rcParams

‘font。sans-serif’

=

‘SimHei’

#用來正常顯示中文標籤

plt

rcParams

‘axes。unicode_minus’

=

False

#用來正常顯示負號

讀取檔案,把發行日期的年份提取到額外的一列[‘year’]後面會用到,然後選取前一千行製作一份copy,選取評價數量為0的也就是未發售的遊戲也製作一份copy。

dfraw = pd。read_excel(path,index_col=0)

dfraw[‘year’] = dfraw。apply(lambda x:str(x[‘發行日期’])[0:4],axis = 1)

df = dfraw。copy()

df_top1k = dfraw[0:1000]。copy()

df_now = dfraw[dfraw[‘評價數量’]。values!=0]。copy()

看一下整體情況,這裡我爬了一萬條,但是有些未在國區發售,去除掉之後還剩9887條。

df。info()

df。describe()

用python對steam一萬個遊戲的資料分析

可以看到平均遊戲價格41。91,平均好評率79。28,畫出一個散點圖看一下分佈情況

Y

=

df_now

‘價格’

# 每一個點的Y值

X

=

df_now

‘發行日期’

# 每一個點的X值

plt

style

use

‘seaborn’

#畫布風格

plt

rcParams

‘font。sans-serif’

=

‘Microsoft YaHei’

#字型

plt

figure

figsize

=

20

5

))

#大小

#這裡散點大小是熱銷排行的倒數,也就是說越熱銷的遊戲,圓點也就越大

#顏色取決於好評率高低,colorbar也就是cmap選擇‘RdYlBu’風格

plt

scatter

X

Y

s

=

15000

/

df_now

index

+

200

),

c

=

df_now

‘好評率’

],

alpha

=。

9

cmap

=

plt

get_cmap

‘RdYlBu’

))

plt

colorbar

()

set_label

‘好評率’

fontsize

=

20

plt

xlabel

‘年份’

fontsize

=

20

plt

ylabel

‘價格’

fontsize

=

20

plt

show

()

用python對steam一萬個遊戲的資料分析

可以看到已釋出的遊戲絕大多數分佈在2010年到2020年,0到200元以內,少部分遊戲突破了400元。如果區域性放大一下

Y = df_now[‘價格’] # 每一個點的Y值

X = df_now[‘發行日期’]# 每一個點的X值

plt。style。use(‘seaborn’)#畫布風格

plt。rcParams[‘font。sans-serif’]=[‘Microsoft YaHei’]#字型

plt。figure(figsize=(20, 5))#大小

#這裡散點大小是熱銷排行的倒數,也就是說越熱銷的遊戲,圓點也就越大

#顏色取決於好評率高低,colorbar也就是cmap選擇‘RdYlBu’風格

plt。scatter(X,Y, s=15000/(df_now。index+200), c=df_now[‘好評率’], alpha=。9,cmap=plt。get_cmap(‘RdYlBu’))

datenow = datetime。datetime(2021,1,1)

dstart = datetime。datetime(2010,1,1)

plt。xlim(dstart, datenow)

plt。ylim(0, 500)

plt。xlabel(‘年份’,fontsize=20)

plt。ylabel(‘價格’,fontsize=20)

plt。colorbar()。set_label(‘好評率’,fontsize=20)

plt。show()

用python對steam一萬個遊戲的資料分析

接下來以年分組進行平均價格和平均好評率的計算繪入圖中,

df_yearprice = df。groupby(‘year’)[‘價格’]。mean()。to_frame()。reset_index()。sort_values(by=‘year’)#按年分組,求平均價格

df_yearreview = df。groupby(‘year’)[‘好評率’]。mean()。to_frame()。reset_index()。sort_values(by=‘year’)#按年分組,求平均好評率

plt。figure(figsize=(20, 5))

plt。plot(df_yearreview[‘year’],df_yearreview[‘好評率’], c=‘g’,label=‘平均好評率%’)

plt。plot(df_yearprice[‘year’],df_yearprice[‘價格’], c=‘c’,label=‘平均價格’)

plt。xlabel(‘年份’,fontsize=20)

plt。legend()

plt。title(‘年份與價格、好評率’)

plt。xlim(4,35)

plt。ylim(0, 100)

plt。show()

用python對steam一萬個遊戲的資料分析

從1990年到2020年遊戲平均價格從20多增長到50多,相對比,2019年2個月豬肉就能翻一倍。但這樣對比其實並不科學,因為可以看出來2019年以前在2013年平均價格達到了峰值,之後因為湧入了大量的免(ke)費(jin)遊戲以及遊戲模式的改變(比如開箱子GO、DLC DAY 2等)導致遊戲的入門價格平均值在不斷的被拉低。好評率在30年內還算相對穩定。

接下來根據遊戲型別做一個整體統計,根據我們爬的遊戲標籤對遊戲做分類,首先做標籤的詞頻統計,這裡的思路就是把所有的標籤作為一個list,然後遍歷list統計為dict,然後做降序處理。這裡繪2次圖選擇不同的資料來源,一次全部一萬個遊戲,一次為前1000個遊戲。以下程式碼是全部一萬個,前1000只需要把所有df改為df_top1k就可以。

list1 = []

list1 = df[‘標籤’]。to_list()#全部一萬個

list1 = ‘\n’。join(list1)

list1 =list1。split(‘\n’)#把所有標籤加入list1

frequency = {}

frequency1 = {}

for word in list1:#詞頻統計

if word not in frequency:

frequency[word] = 1

else:

frequency[word] += 1

frequency = sorted(frequency。items(),key = lambda x :x[1], reverse=True)#根據詞頻降序做排列輸出一個元組

for i in frequency:

frequency1[str(i[0])[0:2]+‘\n’+str(i[0])[2:4]+‘\n’+str(i[0])[4:6]+‘\n’+str(i[0])[6:8]]=i[1]#元組轉為字典,再讓標籤每隔2個字加\n,後面柱狀圖會用到

dffre = df。copy()

for i in list(frequency)[0:50]:#檢驗50個tag覆蓋率

dffre = dffre[dffre[‘標籤’]。str。contains(i[0])== False]

print(len(dffre))

這裡輸出了21,說明50個遊戲標籤覆蓋了絕大多數遊戲,只有21個遊戲沒在這個範圍內。然後繪圖不再詳細說了,跟前面差不多。

Y = list(frequency1。keys())[0:50]#取前50個標籤

X = list(frequency1。values())[0:50]

plt。figure( figsize=(20, 5),)

plt。bar(Y,X, facecolor=‘#ff9999’, edgecolor=‘white’)

plt。xlabel(‘遊戲型別’,fontsize=20)

plt。ylabel(‘遊戲數量’,fontsize=20)

plt。xlim(-。5, 49。5)

for a,b in zip(Y,X):

plt。text(a, b,int(b), ha=‘center’, va= ‘bottom’,fontsize=10)

plt。show()

用python對steam一萬個遊戲的資料分析

資料來源為全部的結果

用python對steam一萬個遊戲的資料分析

資料來源為熱銷榜前一千的結果

從整體上講,都符合長尾型分佈,獨立 單人 動作 冒險這四種無論是否為熱銷遊戲,都是熱門標籤。令我最吃驚的居然是單機的標籤數量佔比居然這麼高,熱銷榜11。7%的遊戲居然有裸露標籤。

今天先更到這,後面改天再更