有没有办法获取我的 Stack Exchange 统计信息?
Posted
技术标签:
【中文标题】有没有办法获取我的 Stack Exchange 统计信息?【英文标题】:Is there a way to get my Stack Exchange stats? 【发布时间】:2019-12-16 17:05:45 【问题描述】:我正在重新创建 Stack Exchange 提供的闪光图像,并且重新创建的响应速度更快,因为我可以将鼠标悬停在站点图标上并显示给定 Stack Exchange 域的统计信息。我目前必须手动更新我的数据,我计划每月执行两次左右,除非有办法通过 Web 服务或类似服务直接从 Stack Exchange 加载该数据。
需要注意的几点:
我将把它托管在一个 ASP.NET Web 应用程序中,这样 C# API 就可以了。 Web 服务也会很完美,因为我可以从 javascript 调用它们。 我需要提供任何服务的文档链接。以下是我当前的手动重新创建,以防您好奇或不知道 SE 天赋是什么,尽管它确实需要清理并提高效率。
var siteNames = [ 'Stack Exchange',
'Puzzling',
'Stack Overflow',
'Software Engineering',
'Mathematics',
'Physical Fitness' ]
var reps = [ '6.2k', '4.3k', '954', '410', '224', '220' ];
var golds = [ '1', '0', '0', '1', '0', '0' ];
var silvers = [ '14', '7', '4', '2', '1', '0' ];
var bronzes = [ '98', '50', '20', '10', '8', '10' ];
function getSiteStats(siteID)
document.getElementById("site-name").innerText = siteNames[siteID];
document.getElementById("rep").innerText = reps[siteID];
document.getElementById("gold").innerText = golds[siteID];
document.getElementById("silver").innerText = silvers[siteID];
document.getElementById("bronze").innerText = bronzes[siteID];
function resetSiteStats()
getSiteStats(0);
html, body
margin: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #6aa4ed;
background-image: linear-gradient(45deg, #6aa4ed, #141d33);
background-image: -webkit-linear-gradient(45deg, #6aa4ed, #141d33);
h1, h5
color: #fff;
font-family: Arial, Helvetica, sans-serif;
font-weight: 100;
text-align: center;
margin: 0;
h1
font-size: 10vh;
h5
margin-bottom: 10px;
.flair
padding: 15px;
background-color: #fff;
border-radius: 5px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
display: flex;
.flair img
width: 40px;
height: 40px;
margin: 5px;
cursor: pointer;
.flair .profile
width: 175px;
height: 175px;
margin: 0;
margin-right: 15px;
box-shadow: 2px 2px 4px rgba(12,13,14,0.5);
cursor: default;
.flair a
color: #37f;
text-decoration: none;
margin: 5px;
.flair a:hover
color: #15a;
.flair ul
list-style-type: none;
margin: 0;
padding: 0;
.flair ul > li
display: inline-block;
margin: 5px;
.flair p
margin: 0;
margin-left: 5px;
.badge div
display: inline-block;
height: 7px;
width: 7px;
border-radius: 50%;
transform: translateY(-3px) translateX(3px);
.gold
background-color: #fc0;
.silver
background-color: #ccc;
.bronze
background-color: #da6;
<h1>Stack Exchange Flair</h1>
<h5>Not Mobile Friendly (Yet)</h5>
<h5>Hover Over Site Icons</h5>
<div class="flair">
<img class="profile" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/blue.jpg" />
<div class="account">
<a href="#">PerpetualJ</a>
<p id="site-name">Stack Exchange</p>
<ul>
<li><strong id="rep">6.2k</strong></li>
<li>
<div class="badge">
<div class="gold"></div>
<span id="gold">1</span>
</div>
</li>
<li>
<div class="badge">
<div class="silver"></div>
<span id="silver">14</span>
</div>
</li>
<li>
<div class="badge">
<div class="bronze"></div>
<span id="bronze">98</span>
</div>
</li>
</ul>
<ul>
<li onmouseover="getSiteStats(1);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/puzzling/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(2);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/***/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(3);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/softwareengineering/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(4);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/math/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(5);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/fitness/img/icon-48.png?v=f5a02f85db94"/></li>
</ul>
<p>How fast do you have to slap a chicken to cook it?</p>
</div>
</div>
我有什么方法可以调用 API、Web 服务或类似的东西,让我可以提取给定 Stack Exchange 网站的当前统计信息?
另外,我不希望进行任何类型的网页抓取或类似操作。我希望它来自合法的 Stack Exchange 服务。
注意:如果这属于元数据,请告诉我,以便可以迁移。
On-Topic:根据help center,此问题被视为主题:
我们认为最好的 Stack Overflow 问题都包含一些源代码,但如果您的问题通常涵盖……
程序员常用的软件工具;并且是 软件开发独有的实用且可解答的问题...那么您来对地方了!
鉴于以上引用,API 是程序员常用的工具,通过询问 Stack Exchange 是否有,这个问题是一个实用且可回答的问题。不过,我确实相信这可能更适合 Meta,但我无法迁移它。
【问题讨论】:
【参考方案1】:我最近发现 Stack Exchange 确实为这些类型的东西提供了一个 API。我强烈建议在使用 API 之前阅读他们的 documentation。为了完成我在这里询问的任务,我需要使用以下 API 调用:
/users/ids /users/ids/associated我同时利用这两个调用来重新创建 Stack Exchange 风格,以防万一您不知道这种风格是什么:
首先,我编写了一组简单的方法来处理我对 API 的请求:
function getWebServiceResponse(requestUrl, callback)
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function()
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
;
request.send();
function getSEWebServiceResponse(request, callback)
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) callback(response); );
这里需要key
来帮助防止throttling 的后续请求过多:
每个应用程序都受到基于 IP 的并发请求限制。如果单个 IP 每秒发出超过 30 个请求,则会丢弃新请求。
从这里开始,实施非常简单,是一个很棒的学习过程!
/users/ids
获取在
ids
中标识的用户。通常,当您从其他来源(例如
/questions
)获取用户 ID 时,将调用此方法来获取用户配置文件。
ids
最多可以包含 100 个以分号分隔的 ID。
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback)
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response)
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7)
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
);
/users/ids/关联
返回用户的所有关联帐户,给定他们在
ids
中的account_ids
。
ids
最多可以包含 100 个以分号分隔的 ID。
function getAssociatedAccounts(accountID, callback)
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response)
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++)
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function()
if (++accountsProcessed >= accounts.length)
callback();
);
);
全面实施
/* Definitions */
var CardType = Wheel: "wheel", Card: "card", Box: "box"
var userCard =
username: '',
profileImageUrl: '',
reputation: 0,
badges:
gold: 0,
silver: 0,
bronze: 0
,
siteUrls: []
/* Initial Calls */
var accountID = '13342919';
generateCard('user-flair-wheel', accountID, CardType.Wheel);
/* Required Events */
function showSitename(tooltipID, siteName)
var tooltip = document.getElementById(tooltipID);
tooltip.innerHTML = siteName.replace('Stack Exchange', '');
tooltip.classList.add('active');
function hideSitename(tooltipID)
document.getElementById(tooltipID).classList.remove('active');
/* UI Generation Functions */
function generateCard(containerid, accountid, cardType)
getAssociatedAccounts(accountID, function()
var className = cardType.toString().toLowerCase();
var container = document.getElementById(containerid);
container.classList.add("flair");
container.classList.add(className);
// Build the card.
addProfile(container);
addScores(container, className);
addSites(container, className);
container.innerHTML += '<div id="' + containerid +
'-tooltip" class="se-tooltip"></div>';
);
function addProfile(container)
container.innerHTML += '<img class="user-image" src="' +
userCard.profileImageUrl + '"/>';
container.innerHTML += '<h1 class="username display-4">' +
userCard.username + '</h1>';
function addScores(container, cardType)
var badges = '<ul class="badges">';
badges += '<li><i class="fas fa-trophy"></i> <span id="reputation-' +
cardType + '">' + userCard.reputation + '</span></li>';
badges += '<li><span id="gold-badges-' + cardType + '">' +
userCard.badges.gold + '</span></li>';
badges += '<li><span id="silver-badges-' + cardType + '">' +
userCard.badges.silver + '</span></li>';
badges += '<li><span id="bronze-badges-' + cardType + '">' +
userCard.badges.bronze + '</span></li>';
badges += '</ul>';
container.innerHTML += badges;
function addSites(container, cardType)
var sites = '<ul id="sites-' + cardType + '" class="sites">';
for (var i = 0; i < userCard.siteUrls.length; i++)
var site = '<li>';
var siteLinkSplit = userCard.siteUrls[i].split('|');
site += '<a href="' + siteLinkSplit[0] + '">';
var tooltipID = container.id +'-tooltip';
var linkElement = '<a href="' + siteLinkSplit[0] + '"';
linkElement += ' onmouseover="showSitename(\'' + tooltipID + '\',\'' + siteLinkSplit[2] + '\')"';
linkElement += ' onmouseout="hideSitename(\'' + tooltipID + '\');"';
site += linkElement + '>';
site += '<img src="' + (siteLinkSplit[1] == '<IMG>' ? '#' : siteLinkSplit[1]) + '"/></a></li>';
sites += site;
sites += '</ul>';
container.innerHTML += sites;
/* Stack Exchange API Based Functions */
function getAssociatedAccounts(accountID, callback)
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response)
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++)
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function()
if (++accountsProcessed >= accounts.length)
callback();
);
);
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback)
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response)
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7)
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
);
/* Helper Functions */
function getSEWebServiceResponse(request, callback)
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) callback(response); );
function getWebServiceResponse(requestUrl, callback)
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function()
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
;
request.send();
function sortAccountsByReputation(accounts)
return accounts.sort(function(a, b) return b.reputation - a.reputation; );
function getSiteIcon(siteName)
if (siteName == "meta")
return 'https://meta.stackexchange.com/content/Sites/stackexchangemeta/img/icon-48.png';
return 'https://cdn.sstatic.net/Sites/' + siteName + '/img/apple-touch-icon.png';
/* Flair Styles */
.flair
position: relative;
margin: 15px;
.flair > .se-tooltip
position: absolute;
left: 50%;
transform: translate(-50%);
width: 250px;
bottom: 50px;
opacity: 0;
background-color: #fff;
color: #555;
text-shadow: none;
border-radius: 25px;
padding: 5px 10px;
box-shadow: 2px 2px 3px #0005;
.flair > .se-tooltip.active
bottom: 10px;
opacity: 1;
/* Flair Wheel Styles */
.flair.wheel
width: 200px;
height: 250px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-shadow: 1px 1px 2px #0005;
.flair.wheel .user-image
width: 100px;
height: 100px;
border-radius: 50%;
box-shadow: 2px 2px 3px #0005;
.flair.wheel .username
font-size: 30px;
margin: 0;
.flair.wheel .badges > li > span position: relative;
.flair.wheel .badges > li:first-of-type > i color: #5c9;
.flair.wheel .badges > li:not(:first-of-type) > span::before
content: '';
position: absolute;
top: 50%;
left: -15px;
transform: translateY(-40%);
width: 10px;
height: 10px;
border-radius: 50%;
.flair.wheel .badges > li:nth-child(2) > span::before background-color: #fb3;
.flair.wheel .badges > li:nth-child(3) > span::before background-color: #aaa;
.flair.wheel .badges > li:nth-child(4) > span::before background-color: #c95;
.flair.wheel .sites
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: 55%;
.flair.wheel .sites > li position: absolute;
.flair.wheel .sites > li > a > img
width: 35px;
height: 35px;
background-color: #fffa;
border-radius: 50%;
padding: 2px;
box-shadow: 2px 2px 3px #0005;
cursor: pointer;
transition: 0.3s cubic-bezier(0.5, -2.5, 1.0, 1.2) all;
z-index: 1;
.flair.wheel .sites > li > a:hover > img
width: 40px;
height: 40px;
background-color: #fff;
.flair.wheel .sites > li:nth-child(1)
top: -15px;
left: 50%;
transform: translate(-50%);
.flair.wheel .sites > li:nth-child(2)
top: 0px;
left: 15%;
transform: translate(-20%);
.flair.wheel .sites > li:nth-child(3)
top: 0px;
left: 70%;
transform: translate(-20%);
.flair.wheel .sites > li:nth-child(4)
top: 45%;
left: 80%;
transform: translate(-20%, -50%);
.flair.wheel .sites > li:nth-child(5)
top: 45%;
left: -5px;
transform: translateY(-50%);
.flair.wheel .sites > li:nth-child(6)
top: 79%;
left: 3px;
transform: translateY(-50%);
.flair.wheel .sites > li:nth-child(7)
top: 79%;
right: 3px;
transform: translateY(-50%);
/* To Organize in a Row instead of Column */
.user-flair-container
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
/* Global Styles */
ul
padding: 0;
listy-style-type: none;
ul > li
display: inline-block;
padding: 0 10px;
/* Template Overrides */
html, body
margin: 0;
height: 100%;
background-color: #333 !important;
background-image: linear-gradient(45deg, #333, #555) !important;
background-image: -webkit-linear-gradient(45deg, #333, #555) !important;
.primary-content
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.primary-content > .lead font-size: 25px;
<link href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/PerpetualJ.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" rel="stylesheet"/>
<div id="primary-content" class="primary-content">
<div class="user-flair-container">
<div id="user-flair-wheel"></div>
</div>
</div>
祝大家在未来的努力中好运!
【讨论】:
以上是关于有没有办法获取我的 Stack Exchange 统计信息?的主要内容,如果未能解决你的问题,请参考以下文章