前言

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。

不知不觉,一年一度如火如荼的steam夏日促销悄然开始了。每年通过大大小小的促销,我的游戏库里已经堆积满还未下载过的游戏。但所谓“买到就是赚到,G胖一定大亏”的想法日渐流行,指不定以后就靠它们发达了呢。

有时候滚动steam的排行榜看自己喜欢的游戏的时候,未免会被右边的价格影响到。久而久之我发现我所不想买的游戏并不是因为它不好玩,而是它还没打折。又或者有些心水未被别人挖掘,在排行榜隐秘的角落里自怨自艾,等待“把玩”它的人出现~

于是我简单的用python爬取了steam排行榜前10000个游戏的信息,其中有游戏名,评价,价格,出版日期等,在更加简洁的列表界面选取自己感兴趣的游戏之时,也可以进行进一步的数据分析。

废话不多说,赶紧开始,不然被我拖更到促销结束了就蹭不到热度了。(本来也没有热度)

开始爬取

先说说这次爬虫选用数据的优缺点:

第一,我发现了steam在显示排行榜列表的时候后台会进行一个查询的申请,点开一看是一串json代码,而且在python进行request的时候不需要模拟浏览器进行填“headers”表的操作。通过访问而得到的json代码大大简化了循环复杂度,一次循环可以得到100个游戏信息。

第二,因为只需要遍历所有json代码,时间上可以比进入每一个游戏链接更加短。

第三,但就因为没有进入每个游戏的链接,所以像评论,简介,开发商等信息就没有爬取。但爬取游戏链接的爬虫攻略网上也有很多,这里就不弄斧了。

首先,进入官网的排行榜页面,为了避免游戏DLC、bundle等影响后期操作的类型出现,记得在右边的过滤器里只勾选游戏类目。

通过后台的XHR发现,页面每次刷新都只显示前50个游戏,当我们滚动页面往下看时,网站会发送一个神秘代码:

经过观察,我发现代码会自动申请返回从start参数的数字开始,一共count参数的数字的游戏信息。比如,下面的图显示它申请了从第51个到100个总共50个游戏的信息。

双击上上图的红框链接,返回的页面长这样:

所谓json格式,其实就是在字典里夹字典或者列表,目前许多大数据都是这样保存滴。所以在查询的时候其实很方便,但是我在抽取信息的时候还是会用到正则表达式,因为会方便很多。

知道这些之后,剩下的就可以用python一个个有用信息抽取出来,组成一个新的Dataframe列表,以便之后保存为csv格式。

# 导入需要用到的库
import requests
from bs4 import BeautifulSoup
import re
import json
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

我们尝试用requests打开上面json页面的链接,并用json load解析。

这里我更改了start和count 的参数,比较方便对照原网页来看信息是否一致。

url = 'https://store.steampowered.com/search/results/?query&start=0&count=100&dynamic_data=&sort_by=_ASC&category1=998&snr=1_7_7_globaltopsellers_7&filter=globaltopsellers&infinite=1'
content = requests.get(url).content
jsontext = json.loads(content)
soup = BeautifulSoup(jsontext['results_html'],'html.parser')

可以看看soup返回的结果,它显示了json里边'results_html'返回的东西,因为前边的内容我们已经不需要了,所有游戏信息都在这个键里边。

接着我们回到那个json页面看看我们想要的东西都藏在哪:

游戏名字藏在span的title class里:

name = soup.find_all('span',class_ ='title')

出版日期藏在div的另一个class里:

listdate = soup.find_all('div', class_ ='col search_released responsive_secondrow')

同样的,可以用上面的方法找到游戏的链接、ID,这里就不赘述了。

评分和打分人数藏在span标签里,如果用字典查找的话会比较麻烦,所以我们稍后使用正则表达式将它俩提取出来:

不幸运的是,有些游戏因为还没上架,所以没有人评论,我们用正则表达式得到的信息是乱码。所以我们用函数来防止出现乱码的可能性:

def get_reviewscore(review):
gamereview=[]
for i in range(len(review)):
try:
score = re.search('br>(\d\d)%',str(review[i]))[1]
except:
score = ''
gamereview.append(score)
return gamereview
###########################################
def get_reviewers(review):
reviewers=[]
for i in range(len(review)):
try:
ppl = (re.search('the\s(.*?)(\s)user',str(review[i]))[1])
except:
ppl = ''
reviewers.append(ppl)
return reviewers

如果看到这里的读者觉得很轻松,那我便可以继续往下述说,因为爬取价格比评论更加麻烦。但仅限于麻烦,并没有很高大上的操作;而我相信我并不是用聪明的方法爬取到想要的结果,因为对于这个体量的数据再优化的代码对于运行时间来说相差不大。反正结果都一样,管它呢。

其实要找物品的最终价格(即免费游戏,打折后或未打折的游戏价格)非常简单,因为他就藏在这里:

默认后边两位为小数点后两位,所以我们直接把这串数字揪出来并除以100:

def get_finalprice(price):
finalprice=[]
for i in range(len(price)):
pricelist = int(re.search('final(\W+?)(\d+)(\W)',str(price[i]))[2])/100
finalprice.append(pricelist)
return finalprice

但我们如果就想知道他的原价,以便之后做分析该怎么办呢?

先看一下steam排行榜上的价格有三种显示方法:

第一种,带有划线价格的打折商品,在源代码中长这样:

第二种,免费的:

头疼的是,免费的标识也有变体:

(连to的大小写也有不一样的……steam您用点心!)

不过Free还是老老实实在最前面,所以我们后边只要找到Free就好啦。

第三种,原价显示:

上面的图片都是我在抽查的时候发现的规律与变形,为了避免后续几千个游戏有“乌合之众”,我在代码里只查找这三种格式,如果有奇形怪状的数据出现,直接一棍子打成“空值”:

def get_price(price):
oripricelist=[]
for i in range(len(price)):
try:
oripricelist.append(price[i].find_all(class_="col search_price responsive_secondrow")[0].text)
except:
oripricelist.append(price[i].find_all(class_="col search_price discounted responsive_secondrow")[0].text) ori_price=[]
for i in range(len(oripricelist)):
try:
search = re.search('Free',oripricelist[i])[0].replace('Free','0')
except:
if oripricelist[i]== '\n':
search=''
else:
try:
search = re.search('HK.*?(\d+\.\d+)\D',oripricelist[i])[1]
except:
search=''
ori_price.append(search)
return ori_price

定义完这些想要的数据之后,我们就开始跑循环了。

先把我们要的数据列命好名字:

def get_data(games=1000):
num_games = games
gamename=[]
gamereview=[]
gamereviewers=[]
gamerelease=[]
oriprice=[]
final_price=[]
appid=[]
website=[]

接着我们以每个链接查询100个游戏的步伐开始跑循环并将里边的信息找出来,录入上面的列表里:

       page = np.arange(0,num_games,100)
for num in page:
url = 'https://store.steampowered.com/search/results/?query&start='+str(num)+'&count=100&dynamic_data=&sort_by=_ASC&category1=998&snr=1_7_7_globaltopsellers_7&filter=globaltopsellers&infinite=1'
print('the {} iteration: Trying to connect...'.format((num/100)+1))
content = requests.get(url).content
jsontext = json.loads(content)
soup = BeautifulSoup(jsontext['results_html'],'html.parser')
name = soup.find_all('span',class_ ='title')
review = soup.find_all('div', class_ ='col search_reviewscore responsive_secondrow')
listdate = soup.find_all('div', class_ ='col search_released responsive_secondrow')
price = soup.find_all('div', class_ = 'col search_price_discount_combined responsive_secondrow')
href = soup.find_all(class_='search_result_row ds_collapse_flag')
for i in name:
gamename.append(i.text)
getreview = get_reviewscore(review)
for i in getreview:
gamereview.append(i)
getreviewers = get_reviewers(review)
for i in getreviewers:
gamereviewers.append(i)
for i in listdate:
gamerelease.append(i.text)
getprice = get_price(price)
for i in getprice:
oriprice.append(i)
getfinalprice = get_finalprice(price)
for i in getfinalprice:
final_price.append(i)
for i in range(len(href)):
appid.append(eval(soup.find_all(class_='search_result_row ds_collapse_flag')[i].attrs['data-ds-appid']))
website.append(soup.find_all(class_='search_result_row ds_collapse_flag')[i].attrs['href'])
print('done')

我们在遍历中每次访问页面、完成每次循环的时候都让电脑打印一段字,以便出错的时候能快速找出出错的页面。

接下来就将得到的数据塞进一个数据表里:

    df = pd.DataFrame(data=[gamename,gamereview,gamereviewers,gamerelease,oriprice,final_price,appid,website]).T
df.columns = ['name','review_score','reviewers','release_date','ori_price','final_price','id','link']
return df #呼叫我们的函数:
df = get_data(10000) #这里的数字代表爬取10000个游戏

等待漫长的过程与欣赏成功的过程:

最后的数据集长这样:

接下来只要保存为csv格式,就可以开始分析数据了。但这已经不是爬虫文章的内容,所以不会往下继续分析啦。

总结与反思

我发现final_price也就是一开始提取的最终价格中,会有高于原价的现象。

比如CS:GO的最终价格并不是0,是因为它有一个升级包:

前1000个游戏里总共有3个这样的错误:

实况足球2020 是demo版免费,而想体验完整游戏确实需要78港币;

奇异人生1 是第一篇章免费,后边的篇章需要23.8港币。

这些代码跑起来虽然快,但得到的信息依旧太少,如果要深入研究steam的数据还是需要有强大的耐心遍历所有游戏链接呐。

这次的爬虫经历其实也发现了steam一些录入大数据库的时候的小差错,比如前面所提到的免费标识竟然有3种变体,但他们可能觉得问题不大。

终于整理结束,赶紧结尾:

这次的文章就到这里,希望对大家有所帮助~!

steam夏日促销悄然开始,用Python爬取排行榜上的游戏打折信息的更多相关文章

  1. 使用python爬取MedSci上的期刊信息

    使用python爬取medsci上的期刊信息,通过设定条件,然后获取相应的期刊的的影响因子排名,期刊名称,英文全称和影响因子.主要过程如下: 首先,通过分析网站http://www.medsci.cn ...

  2. 利用Python爬取OPGG上英雄联盟英雄胜率及选取率信息

    一.分析网站内容 本次爬取网站为opgg,网址为:” http://www.op.gg/champion/statistics” 由网站界面可以看出,右侧有英雄的详细信息,以Garen为例,胜率为53 ...

  3. python爬取网易云音乐歌曲评论信息

    网易云音乐是广大网友喜闻乐见的音乐平台,区别于别的音乐平台的最大特点,除了“它比我还懂我的音乐喜好”.“小清新的界面设计”就是它独有的评论区了——————各种故事汇,各种金句频出.我们可以透过歌曲的评 ...

  4. Python爬取十四万条书籍信息告诉你哪本网络小说更好看

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: TM0831 PS:如有需要Python学习资料的小伙伴可以加点击 ...

  5. 全国315个城市,用python爬取肯德基老爷爷的店面信息

    我觉得我生活在这世上二十多年里,去过最多的餐厅就是肯德基小时候逢生日必去,现在长大了,肯德基成了我的日常零食下班后从门前路过饿了便会进去点分黄金鸡块或者小吃拼盘早上路过,会买杯咖啡.主要快捷美味且饱腹 ...

  6. Python爬取网站上面的数据很简单,但是如何爬取APP上面的数据呢

  7. python爬取中国知网部分论文信息

    爬取指定主题的论文,并以相关度排序. #!/usr/bin/python3 # -*- coding: utf-8 -*- import requests import linecache impor ...

  8. Python 爬取 11 万 Java 程序员信息竟有这些重大发现!

    一提到程序猿,我们的脑子里就会出现这样的画面: 或者这样的画面: 心头萦绕的字眼是:秃头.猝死.眼镜.黑白 T 恤.钢铁直男-- 而真实的程序猿们,是每天要和无数数据,以及数十种编程语言打交道.上能手 ...

  9. 用Python爬取猫眼上的top100评分电影

    代码如下: # 注意encoding = 'utf-8'和ensure_ascii = False,不写的话不能输出汉字 import requests from requests.exception ...

随机推荐

  1. Java实现第九届蓝桥杯哪天返回

    哪天返回 题目描述 小明被不明势力劫持.后被扔到x星站再无问津.小明得知每天都有飞船飞往地球,但需要108元的船票,而他却身无分文. 他决定在x星战打工.好心的老板答应包食宿,第1天给他1元钱. 并且 ...

  2. java实现第三届蓝桥杯火柴游戏

    火柴游戏 [编程题](满分34分) 这是一个纵横火柴棒游戏.如图[1.jpg],在3x4的格子中,游戏的双方轮流放置火柴棒.其规则是: 不能放置在已经放置火柴棒的地方(即只能在空格中放置). 火柴棒的 ...

  3. 浅谈js运行机制

    前言 因为js的运行机制十分重要,理解起来也十分抽象,仍还是在这里做个记录,加深自己的记忆. 总之,希望本文的内容能够对您的学习或者工作有所帮助.另,如果有任何的错误或者不足请指正! 如何理解js单线 ...

  4. Centos宝塔安装NextCloud

    官方版本列表链接:https://download.nextcloud.com/server/releases/ 我下载的是 16.0.6版本,下载链接:https://download.nextcl ...

  5. android日常开发总结的技术经验60条

    全部Activity可继承自BaseActivity,便于统一风格与处理公共事件,构建对话框统一构建器的建立,万一需要整体变动,一处修改到处有效. 数据库表段字段常量和SQL逻辑分离,更清晰,建议使用 ...

  6. 开发者大赛 | aelf轻型DApp开发训练大赛结果公布!

    6月9日,由aelf基金会发起的轻型DApp开发训练大赛圆满收官.本次训练赛基于aelf公开测试网展开,主要针对轻型DApp,旨在激励更多的开发者参与到aelf生态中来. 活动于4月21日上线后,ae ...

  7. RabbitMQ系列之【启动过程中遇到问题及解决方案】

    1.如果显示找不到主机,请在hosts文件中添加: vi /etc/hosts 127.0.0.1 localhost 2.从3.3.1版本开始,RabbitMQ默认不允许远程ip登录,即只能使用lo ...

  8. nginx介绍及其原理

    nginx介绍及其原理 nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like协议下发行. nginx由俄罗斯程序设计师lgor Sy ...

  9. Jenkins登录无效

    解决办法: 进入Jenkins安装目录: 1:进入D:\jenkins\users\admin 这个目录下找到config.xml  可以看到里面的用户名是admin 2:进入D:\jenkins\s ...

  10. numpy中array数组对象的储存方式(n,1)和(n,)的区别

    资料:https://stackoverflow.com/questions/22053050/difference-between-numpy-array-shape-r-1-and-r 这篇文章是 ...