资讯动态

用Steam Web API和GitHub Actions在个人简介同步正在玩的游戏

发布时间:2026/9/26 17:16:33 来源:尧图企业网站定制
前阵子翻一个开发者主页的时候发现他的个人简介里有一张卡片实时显示着正在玩某款Steam游戏底下还挂着最近几个成就。第一反应是这玩意儿挺酷第二反应是我也要给自己整一个。于是就有了这个在个人简介同步正在玩的Steam游戏的小项目。整个过程其实不复杂你只需要一个Steam Web API Key、一个能定时跑的任务再加一个显示用的模板就能让简介里出现一张会自己更新的游戏状态卡。如果你想让GitHub主页、个人博客或导航页多点活人感这篇文章可以直接帮你跑通全链路。这个正在玩的状态信息其实不用自己抓网页Steam官方就提供了Web API接口稳定、返回字段干净个人项目完全够用。我后面会从接口申请讲到数据解析再讲展示层怎么做最后把定时刷新和排错一起说清楚你可以照着一步步实现。1. 这个项目要解决的真实问题简介不该全是静态列表1.1 从访客视角看个人简介写个人简介这件事大部分人做成了简历墙技能图标一行、项目列表一排、联系方式丢末尾。信息量没问题但内容完全静态。访客点进来扫一眼发现没有活人的感觉也就没有停留的理由。我最初看到别人简介里出现Currently Playing卡片时意识到动态内容天然具备互动属性。访客如果也是个玩家看到某款游戏第一反应是点进去看看你的时长、成就或者直接聊起来。哪怕不是玩家也会因为这人的主页会动而多停留几秒。这块内容放在博主自己的个人简介里就是一张天然的破冰名片。1.2 别急着写爬虫Steam官方API就够了有一类实现会往steam爬虫方向走试图直接抓个人主页的HTML再解析出当前游戏。我做过类似的事结论是大可不必。Steam个人主页的HTML结构会随Steam改版而变化Cookie过期、反爬验证、区域页面差异都是坑。Steam官方提供的Web API覆盖个人摘要、游戏列表、成就、用户关系等常见数据限速也比较宽松。个人项目拿它来做正在玩状态同步完全够用而且字段是结构化的JSON不用跟页面DOM较劲。1.3 项目的最小可行定义我把这个项目砍到最简就三个部分输入Steam Web API Key 你的Steam ID处理拉取个人摘要解析出当前正在玩的游戏名、AppID、游戏封面输出一张可嵌入个人简介的SVG卡片或者一段更新到README里的文本定时刷新由GitHub Actions负责不用自己买服务器。整个项目跑起来之后远程仓库会自动更新状态过程完全无人值守。2. Steam Web API是这套方案的地基2.1 API Key的获取与存放先登录Steam社区进入开发者API Key申请页面随便写个域名个人项目可以用自己的博客域名没有就填localhost就能拿到Key。这个Key基本秒批不需要审核但它的权限很大能读取你账号的游戏和隐私数据。所以存放要慎重。我建项目时直接把Key放在GitHub仓库的Actions secrets里命名为STEAM_API_KEY脚本通过环境变量读取绝不让Key出现在源码或README中。如果你是自己搭后端就放服务端的配置文件或环境变量前端页面永远不要引用它。2.2 先用ResolveVanityURL把昵称解析成数字IDSteam Web API的绝大多数接口都要求传steamID64也就是个人资料页URL里那一长串19位数字。如果你的账号开了自定义URLvanity URL可以用ResolveVanityURL接口把字母昵称解析成数字IDimport requests API_KEY 你的key CUSTOM_ID 你的自定义url # 例如 steamcommunity.com/id/xxx resp requests.get( https://api.steampowered.com/ISteamUser/ResolveVanityURL/v1/, params{key: API_KEY, vanityurl: CUSTOM_ID}, timeout10, ) data resp.json()[response] if data[success] 1: steam_id data[steamid] print(解析结果:, steam_id) else: print(未找到该自定义URL:, data)如果没开自定义URL那就更简单直接复制个人资料页URL里的数字串当steamid参数用。两个途径最终都落到同一串ID上后续接口只认这个。2.3 GetPlayerSummaries返回的字段里哪些才是正在玩定位正在玩的核心接口是GetPlayerSummaries v2它会返回账号的在线状态、昵称、头像以及当前正在运行的游戏信息resp requests.get( https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/, params{key: API_KEY, steamids: steam_id}, timeout10, ) player resp.json()[response][players][0] print(player)返回的players[0]里我们需要重点关注这几个字段字段含义personastate在线状态0离线、1在线、2忙碌、3离开、4打盹、5想交易、6想玩gameid当前正在玩的应用AppID停止玩游戏后会消失gameextrainfo游戏名称比如RustDota 2gameserverip正在连接的服务器地址联机游戏才有gameextrafield大部分时候为空个别游戏会存服务器名或模式信息正在玩的判断核心就两个字段gameid有没有值以及gameextrainfo能不能取到可读的游戏名。这里有个需要注意的点GetPlayerSummaries拿到的游戏详情依赖于对方也就是你自己在Steam隐私设置里公开了游戏详情。如果隐私设置里游戏详情是仅好友可见或私密gameid可能还在但gameextrainfo会缺失。个人项目给自己用记得在Steam隐私设置里把游戏详情设为公开。2.4 请求的边界加超时不加代理Steam Web API部署在不同区域的服务器上访客和你所在网络环境到它的链路质量并不稳定。我自己实测时遇到最多的不是鉴权问题而是超时。所以所有请求构造我都强制加timeout防止某个时刻API响应慢把定时任务卡死。关于代理我只想多说一句不要为了让请求看起来更快而引入公网中转个人项目不值得冒这个风险后面我会讲重试和缓存比任何优化都实在。3. 拿到数据之后的三个关键判断3.1 有gameid才算真正在玩但光这还不够最直接的做法是读gameid有值就认为在玩。但我在实际跑数据时发现两个边界情况一是刚退出游戏的几分钟内Steam有时仍会返回上一个游戏的gameid表现为最近游玩残留窗口。如果你的简介卡片刷新频率很高就会看到游戏名在刚退出的游戏和无游戏之间反复跳。比较有效的处理是做状态老化只有当连续两次请求都落在同一个gameid上或者间隔一段时间后依然存在才认定正在玩并更新展示。二是某些非游戏应用也会占据gameid。最典型的就是Wallpaper Engine这类工具软件打开后Steam会把它当成当前应用返回。展示一张正在玩壁纸引擎的卡片多少有点奇怪。我的做法是维护一个排除AppID集合遇到底下这些就按未在玩游戏处理。3.2 挂机类游戏的过滤策略Steam上有一类游戏本质是挂卡工具或放置挂机比如纯增量游戏、挂机养成甚至还有一些只为了集换式卡牌挂时长的工具。它们确实通过Steam客户端启动技术上符合正在玩但对访客来说看到你简介里连续几天都是同一个挂机游戏观感不太好。我建议把这些AppID也放进黑名单。注意这纯粹是展示层偏好不影响真实游戏时长统计也不需要把相关请求接口一起过滤。3.3 要不要顺便拿成就和游戏时长如果想让简介卡片的信息更丰富还可以用IPlayerService的GetOwnedGames拿全量游戏库和总时长或者用ISteamUserStats的GetPlayerAchievements拿当前游戏成就进度。前者可以展示最近玩过的游戏Top5后者可以做成成就进度条resp requests.get( https://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v1/, params{ key: API_KEY, steamid: steam_id, appid: game_id, l: schinese, # 想显示中文成就名就传这个 }, timeout10, ) achievements resp.json()[playerstats][achievements] earned sum(1 for a in achievements if a[achieved] 1) total len(achievements) print(f{earned}/{total})但要提前确认隐私设置。GetOwnedGames需要游戏详情公开GetPlayerAchievements要求具体游戏的成就数据公开。如果隐私没开接口会返回空列表或错误码这块的排错比核心状态同步更费时间。我的建议是先跑通正在玩这个最小闭环再加附加数据。4. 展示层方案选型我最终选了GitHub Actions README占位符4.1 三种常见实现路径对比正在玩这张卡片放哪、怎么生成我比较了三种思路方案维护成本适用位置主要问题现成在线服务生成卡片最低GitHub README、个人博客样式不自由依赖第三方服务稳定性自建后端定时生成SVG中个人博客、导航页要服务器要处理CORS、缓存GitHub Actions更新README低GitHub个人主页只适合README场景不适合外链我最终选的是第三套。GitHub个人主页本身就是README直接在仓库里加一个定时任务让它跑脚本、改README、再提交回去全程不产生额外服务器费用。数据展示也在同一平台省掉了外链的域名和HTTPS问题。当然第二套方案我也做过一个精简版——把同一份脚本迁到服务器上用crontab跑输出SVG文件供个人博客引用。两者的核心解析逻辑完全一致只换入口。4.2 SVG卡片模板的细节SVG相比PNG的好处是体积小、支持夜间模式适配而且可以灵活嵌入GitHub README。GitHub对README里的SVG相对宽松一个简单的卡片模板长这样svg width400 height120 xmlnshttp://www.w3.org/2000/svg defs linearGradient idbg x10 y10 x21 y21 stop offset0% stop-color#1b2838/ stop offset100% stop-color#2a475e/ /linearGradient /defs rect width400 height120 rx12 fillurl(#bg)/ image x16 y16 width88 height88 href{cover_url}/ text x122 y42 font-familysans-serif font-size20 fill#c7d5e0 {game_name} /text text x122 y70 font-familysans-serif font-size14 fill#66c0f4 正在游玩中 /text /svg游戏封面图可以直接用Steam CDN的StandardLibraryImage链接AppID配上固定URL模板就能拿。如果游戏不在库里或封面链接无效就画一个占位色块不要让整张卡片红叉。4.3 生成文本时最容易翻车的转义问题游戏名不是安全文本里面可能带、、这些字符。往SVG里填之前不转义轻则显示错乱重则SVG直接渲染失败。Python里一行就能解决import html safe_name html.escape(game_name, quoteTrue)README占位符替换同理。我在README里放一对注释标记!--STEAM_STATUS:START-- !--STEAM_STATUS:END--脚本读取README全文用正则把这对标记之间的内容整体替换掉避免重复堆叠旧记录import re pattern re.compile( r!--STEAM_STATUS:START--.*?!--STEAM_STATUS:END--, re.S, ) readme_text pattern.sub(new_status_block, readme_text)5. 定时刷新中的两个常见问题连接报错和自动提交5.1 定时任务用GitHub Actions怎么搭GitHub Actions的schedule用的是UTC时间cron表达式里写0 * * * *就是每到整点跑一次。对正在玩状态来说一小时一次完全够再频繁就纯粹是对API的浪费。我的workflow文件长这样name: update-steam-status on: schedule: - cron: 0 * * * * workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Set up Python uses: actions/setup-pythonv5 with: python-version: 3.12 - name: Install dependencies run: pip install requests - name: Run update script env: STEAM_API_KEY: ${{ secrets.STEAM_API_KEY }} STEAM_ID: ${{ secrets.STEAM_ID }} run: python scripts/update_status.py - name: Commit if changed run: | if [ -n $(git status --porcelain) ]; then git config user.name steam-status-bot git config user.email botexample.com git add . git commit -m chore: update steam status git push fi5.2 server failed to connect这类报错怎么处理看到server failed to connected to steam这类信息时先分清是Steam客户端弹的还是你脚本里报的。如果是客户端界面层的问题通常和Steam UI进程有关如果是脚本请求API时的网络错误那就是链路质量问题。对脚本来说最稳妥的做法是超时重试缓存。我给请求套了指数退避重试最多试三次import time import requests def fetch_json(url, params, max_retries3): for attempt in range(max_retries): try: resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() return resp.json() except requests.RequestException as exc: print(f第{attempt 1}次请求失败: {exc}) if attempt max_retries - 1: time.sleep(2 ** attempt) return None如果三次都失败就用上一次成功的结果继续渲染不更新README。这样网络抖动不会破坏已经生成的卡片内容也不至于因为一次请求失败就让Actions任务失败报警。5.3 自动提交的坑别让Actions自己跑死循环第一次把Commit if changed写进去后我很快遇到一个问题每次任务运行即使文件内容没变也照样提交导致提交记录刷屏。这就是为什么我在提交前必须检查git diffif [ -n $(git status --porcelain) ]; then只有确实有文件变化才提交。对于正在玩状态游戏不变时内容不会变这个判断能把无效提交直接拦掉。另外如果哪天你想强制刷新一次展示可以直接在GitHub仓库页手动触发workflow_dispatch不用等下一个整点。5.4 密钥和隐私的补充提醒最后回到Key本身。Steam Web API Key不区分应用绑定的是账号泄露后别人可以用它读取与你账号相关的公开数据。虽然有隐私设置兜底还是建议用环境变量或secrets管理。README本身是公开的所以我会刻意不在SVG里放任何不想公开展示的信息比如真实姓名、邮箱、交易链接。我在跑这个项目一个多月后的体会是动态内容给个人简介带来的互动感确实比静态列表强很多。把正在玩Steam游戏这件事同步到简介里技术上的难度不高但整套链路从API申请到数据解析、展示模板再到定时刷新每一环都有值得打磨的细节。如果你也打算做建议先拿API Key在浏览器里手动请求一次接口看清楚JSON长什么样再写代码这一步能帮你省掉大量排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑