五、推特

Twitter 是一个真正的成长于开放和可用的 API 之上的服务。 最初,没有 Twitter 客户端。 与 Twitter 的交流仅限于短信和后来的网站。 在开发 Twitter 的过程中,开发人员显然在短信费用、测试和构建系统方面积累了数百美元。 Twitter 的普及得益于数百名开发人员,他们使用开放的 Twitter api 或 Twitter RSS 源,构建了TweetDeckTweetree等工具。 因此,Twitter 提供了一个丰富的 API,可用于构建应用,在我们的例子中是可视化。

在开始构建可视化之前,让我们先看看 Twitter API,以及如何利用它。 在https://dev.twitter.com上有大量的 API 文档。 如果你想要更多的信息,这应该是你做进一步研究的第一站。

出于我们的目的,Twitter 提供了两种不同的模型来检索数据。 第一种是典型的 RESTful 模型,其中客户端向 Twitter 请求特定的资源,这些资源通过 HTTP 以 JSON 格式返回。 这个 API 可能与您以前见过的其他 API 类似。 它是无状态的,这意味着在请求之间不保留服务器端信息,并遵循 HTTP 的最佳实践。 如果您试图在 web 浏览器中使用来自 Twitter 的数据,那么这是您的选择。 第二个选项是流 API。 此方法使用一个持久的 HTTP 连接,当消息发生时,Twitter 将向该连接发送消息。 从浏览器中使用流 API 通常是一个糟糕的主意,所以你需要在浏览器和 API 之间有一个中介服务器,如下图所示:

Twitter

不幸的是,拥有服务器是所有使用 Twitter(甚至是 RESTful API)的可视化的要求,因为 Twitter 不支持使用纯基于浏览器的解决方案进行身份验证。 我们稍后会详细介绍,但首先我们需要在 Twitter 上建立一个开发者账户。

访问 api

如果你还记得在第 3 章OAuth中,其中一个要求是为我们想要交谈的每个站点获取一个应用键。 这也适用于 Twitter,让我们开始吧。

打开浏览器,转到https://dev.twitter.com,点击登录链接。 如果你已经有一个 Twitter 帐户,那么你可以在这里使用它来登录。 如果没有,你可以注册一个新的 Twitter 账户。 别担心,都是免费的。

一旦你登录,然后在右上角应该有一个链接到我的应用,如下截图:

Getting access to the APIs

点击它将带您进入一个页面,在那里您可以开始设置您的第一个应用。 你需要输入一些关于申请的信息。 您可以为大多数字段输入任何您选择的字段,但回调 URL 应该是http://127.0.0.1:8080/twitter1.html。 这是当 OAuth 阶段完成后 Twitter 会指向你的 URL。 我们在这里使用的是 localhost 值,但在生产中,您可能希望使用面向公共的 URL 进行可视化。 Application Details窗口的截图如下:

Getting access to the APIs

提示

这里不能使用本地主机域,但如果您不想看到 IP 地址,那么可以使用 URL 缩短服务为本地主机 URL 创建别名。 确保你的网址缩短保留查询参数,否则你将无法正确登录。

一旦你的应用已经被创建,你将能够看到从相同的我的应用选项我们以前使用过的各种设置。 我们需要的关键信息为OAuth settings,,如下图所示:

Getting access to the APIs

这些密钥将用于我们的应用的用户授权。 如果秘密密钥泄露了——例如,你可能已经把它粘贴到你正在写的书中——它可以使用reset keys链接进行重置。 这样做可以防止你书中的读者冒充你,以你的名义犯下难以言喻的恶行。

设置服务器

正如我提到的,Twitter 不允许从浏览器直接访问他们的身份验证结构,我们需要使用服务器。 幸运的是,我们可以在自己的计算机上针对服务器进行开发,而不需要外部服务器。 我们在这本书中使用了大量的 JavaScript,所以让我们继续这个主题,并使用 node.js 在本地托管我们的站点。 任何其他 HTTP 服务器也可以工作。

安装 node.js 非常简单。 如果你使用的是 Windows 系统,那么可以从http://nodejs.org下载安装程序。 在 OS X 上,在同一个站点上有一个基于.pkg的安装程序,或者它可以使用家酿软件安装。 如果您使用的是 Linux,最好是从源代码进行编译。 然而,如果你使用的是内置打包系统的发行版,比如aptyum,那么有一个 node.js 包可以通过以下两个命令安装:

sudo yum install nodejs            #Fedora or RedHat
sudo apt-get install nodejs        #Debian or Ubunt

node.js 是一个服务器端软件,设计用来异步执行所有的 I/O 任务。 这意味着像写入磁盘这样的操作不会阻塞主线程。 当 I/O 完成时,会通知主线程。 最常见的应用之一是将其用作 HTTP 服务器。 这个功能以 HTTP 模块的形式出现,但是该模块提供的接口非常轻量级。 相反,我们将使用 Express 框架。 Express 是一个轻量级框架,它围绕路由、会话和服务内容以及模板提供了一些基础设施。 可以使用节点包管理器npm进行安装,如下所示:

npm install express

今后我们将利用快递。

OAuth

当然,OAuth 可以手动配置和控制,但我们站在巨人的肩膀上是有充分理由的。 对我们来说,使用已经构建好的 OAuth 库要容易得多。 幸运的是,节点有这样一个库,创造性地称之为OAuth。 即使使用这个库,您也会发现与 OAuth 1.0a 端点的交互是复杂的。 要安装它,再次进入命令行并使用节点包管理器:

npm install oauth

这个库可以执行 OAuth 1.0a 和 OAuth 2.0 操作。 由于 Twitter 是 OAuth 1.0 的一个端点,我们将充分利用这一点。

首先要做的是设置 Express 应用。 Express 提供了应用模板,但对于本章中简单的应用来说,它们是多余的。 如果你打算在将来创建一个更复杂的应用,你会想要更多地了解应用生成和目录结构。 我们首先要求express,并使用加载的模块创建一个新的应用,如下面的代码所示:

var express = require("express");
var app = express();
var oAuth = require('oauth');

Require 是一个允许动态加载 JavaScript 库的库。 这是在节点应用中引入外部模块的最常见方法。 接下来,我们在express中配置一些设置,如下代码所示:

app.configure(function() {
  app.use(express.bodyParser());
  app.use(express.cookieParser() );
  app.use(express.session({ secret: "a secret key"}));
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

bodyParser允许express对发送到服务器的请求体进行简单解析。 在下一行,设置cookieParser。 与bodyParser类似,它允许解析 cookie 并使用从 cookie 中检索的值填充请求对象,在我们的示例中,是会话的信息。 接下来,我们设置会话功能。 这使我们能够共享从一个请求到另一个请求的信息。 在其默认配置中,它使用内存存储来保存会话信息。 这意味着重新启动应用将删除会话信息。 如果你在一个机器群上托管你的可视化,你需要利用外部数据存储机制,如MongoDBRedis。 我们传入一个用于生成session散列的秘密密钥。 它应该是一个随机字符串。 使用app.router将指示 express 监听路由请求,我们将在稍后定义。 最后,我们的.html.js文件将放在一个名为public的目录中,因此我们将指示express将该目录的内容作为静态资源提供给用户。

我们现在想要利用 OAuth 库。 这可以通过如下代码所示的函数来实现:

function getOAuth(){
  var twitterOauth = new oAuth.OAuth(
  'https://api.twitter.com/oauth/request_token',
  'https://api.twitter.com/oauth/access_token',
  consumerKey,
  consumerSecretKey,
  '1.0A',
  null,
  'HMAC-SHA1');
  return twitterOAuth;
}

我们创建一个与 Twitter 相关的 OAuth 对象。 我们给出两个端点,然后给出之前从 Twitter 收到的消费者密钥和消费者秘密。 OAuth 1.0a 需要嵌入消费者秘密,这就是为什么不能使用客户端代码从 Twitter 检索信息。 消费者的秘密不能通过发送给客户而泄露给外人。 1.0A作为 OAuth 版本传入; 不需要授权回调,因此将null作为第六个参数。 最后一个参数是签名方法:Twitter 使用HMAC-SHA1

接下来,我们将在 Express 应用中设置一条路由,从 Twitter 请求 OAuth 令牌:

app.get('/requestOAuth', function(req, res){
  function recieveOAuthRequestTokens(error, oauth_token, oauth_token_secret,results) {
    if (!error){
      req.session.oAuthVars = { oauth_token: oauth_token,oauth_token_secret: oauth_token_secret}; res.redirect('https://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token);
    }
  requestOAuthRequestTokens(recieveOAuthRequestTokens);
});
function requestOAuthRequestTokens(onComplete){
  getOAuth().getOAuthRequestToken(onComplete);
}

这里,我们连接/requestOAuth路由,首先请求一个 OAuth 令牌,然后使用它将用户重定向到 Twitter 上的登录页面。 我们构建一个匿名函数并将其传递给 OAuth,因为节点是高度异步的。 回调模型允许主节点线程服务另一个请求,同时等待 Twitter 用 OAuth 令牌返回给它。 一旦我们有了 OAuth 令牌,我们将它们保存在会话状态以供下一步使用,并重定向到 Twitter 授权页面。

一旦用户通过身份验证,Twitter 就会将用户重定向到我们在设置应用时定义的 URL。 在我们的例子中,这将由路由/receiveOAuth提供,如下代码所示:

app.get('/receiveOAuth', function(req, res){
  if(!req.session.oAuthVars){
    res.redirect("/requestOAuth");
    return;
  }
  if(!req.session.oAuthVars.oauth_access_token){
    var oa = getOAuth();
    oa.getOAuthAccessToken( req.session.oAuthVars.oauth_token, req.session.oAuthVars.oauth_token_secret, req.param('oauth_verifier'),
    function(error, oauth_access_token, oauth_access_token_secret,tweetRes) {
      req.session.oAuthVars.oauth_access_token = oauth_access_token;
      req.session.oAuthVars.oauth_access_token_secret = oauth_access_token_secret;
      GetRetweets(res, req.session.oAuthVars.oauth_access_token, req.session.oAuthVars.oauth_access_token_secret);
    });
  }
  else
    GetRetweets(res, req.session.oAuthVars.oauth_access_token, req.session.oAuthVars.oauth_access_token_secret);
});

这段代码将 Twitter 的redirect传递回的 OAuth 令牌,并执行最后一步,即查找访问令牌。 一旦我们有了这些访问令牌,就可以使用它们在GetRetweets函数中调用 API-here。 我们将在会话中保存所有生成的令牌,这样用户就不必不断授予对 Twitter API 的访问权。

厌倦了代币吗? 你应该! 这个交换设置 OAuth 1.0a 使用了大量的令牌。 幸运的是,我们已经完成了令牌和 OAuth。 现在我们可以开始用 Twitter 数据构建可视化了!

可视化

Twitter 为我们提供了大量的 api。 我们也许应该从发明一些我们想要可视化的东西开始,然后决定数据是否可用,以及我们如何展示它。 我很好奇我关注的人中谁的推特最多。 像@kellabyte这样的账户似乎总是在发推特,而像@ericevans 这样的账户几乎不发推特。

服务器端

让我们从获取服务器端的数据开始。 在 node.js 中,我使用以下代码建立了一个新的路由:

app.get('/friends', function(req, res){
  if(!req.session.oAuthVars || !req.session.oAuthVars.oauth_access_token){
    res.redirect('/requestOAuth');
    return;
  }
  var cursor = -1;
  receiveUserListPage(res, req.session.twitterVars.user_id, req.session.oAuthVars.oauth_access_token, req.session.oAuthVars.oauth_access_token_secret, cursor, new Array());
});

首先,我们检查以确保会话中有适当的令牌可用。 如果没有,那么我们重定向回requestOAuth页面,这将启动整个 OAuth 工作流。 接下来,我们设置一个初始游标值。 Twitter 限制了从其服务中返回的搜索结果的数量。 这避免了向消费者倾销大量记录,而这可能不是任何一方都想要的。 对于 API 调用,我们将使用设置为 20 的限制。 然而,Twitter 也提供了一个延续标记,他们称之为游标。 通过使用这个令牌再次调用服务,将返回结果的下一页。 初始值为-1给出第一页。 游标和所有必需的标记被传递到receiveUserListPage,它将执行实际的查找。

提示

速率限制

Twitter 限制了你向他们的服务发送请求的数量。 在开发可视化时,您可能会遇到这些限制。 等待 15 分钟再试一次。 在生产中,尝试缓存数据,这样您就不必如此频繁地查询 Twitter。

receiveUserListPage代码如下:

function receiveUserListPage(res, user_id, oauth_access_token, oauth_access_token_secret, currentCursor, fullResults){
  var oauth = getOAuth();
  oauth.get( 'https://api.twitter.com/1.1/friends/list.json?skip_status=true&user_id=' + user_id + "&cursor=" + currentCursor,
  oauth_access_token, 
  oauth_access_token_secret,
  function (e, data, oaRes){
  var jsonData = JSON.parse(data);
  if(jsonData.errors){
    projectResults(res, fullResults);
    return;
  }
  fullResults = _.union(fullResults, 
  _.map(jsonData.users, 
  function(item){return { name: item.name, 
    count: item.statuses_count
  }}));
  if(jsonData.next_cursor == 0){
    projectResults(res, fullResults);
  }
  else
    ReceiveUserListPage(res, user_id, oauth_access_token, oauth_access_token_secret, jsonData.next_cursor, fullResults);
  }
});
}

function projectResults(res, fullResults)
{
  var selectedResults = _.first(_.sortBy(fullResults, function(item){return item.count;}).reverse(), 10);
  res.end(JSON.stringify(selectedResults));
}

我们首先获取对 OAuth 库的引用,然后使用当前光标和当前user_id来查询 Twitter。 我们使用的 API 调用从我关注的人的列表中返回 20 个用户。 结果作为字符串返回,因此我们使用JSON.parse将其解析为一个对象。 如果结果对象包含一个名为errors的字段,那么我们可能已经达到了速率限制,因此我们返回到目前为止我们拉下的所有内容。 因为这个 API 电话的收费上限只有15,如果你跟踪超过 300 人,你就会达到上限。

如果有结果,则将其附加到当前数据集。 我们使用下划线的map函数只从字段中选择两个。 这节省了带宽,并使调试更容易,因为从 Twitter 返回的对象是非常重量级的,带有许多无用的字段。 如果next_cursor等于0,则意味着我们已经到达列表的末尾,可以返回当前的名称和计数集。 否则,我们求助于该函数,给它新的游标、名称集和项。 一旦我们遇到可以返回的情况,我们调用projectResults ,它将 10 个拥有最多 tweets 的用户发送到 JSON 格式的客户端。

提示

下划线 js

下划线 JavaScript 库是一个小型库,使使用数组变得更加容易。 它添加了集合函数,如unionintersect,,以及函数式编程概念,如 map 和 reduce。 可从http://underscorejs.org/下载。

客户端

客户端可视化代码可以放置在我们之前指示 Express 作为静态内容提供的公共目录中。

我想从视觉上展示最活跃的推特用户。 一个很好的方法是使用气泡图,并使气泡越大,他们有越多的 tweet。 让我们来构建代码:

function visualize(data){
  var graph = d3.select(".visualization")
  .append("svg")
  .attr("width", 1024)
  .attr("height", 768);
  var colorScale = d3.scale.category10();
  calculateBubbles(data, 1024, 768);
  var currentX = 0;
  graph.selectAll(".bubble")
  data(data)
  enter()
  append("circle")
  .style("fill", function(x,y){return colorScale(y);})
  .attr("cx",  function(d){return d.cx;})
  .attr("cy", function(d){ return d.cy;})
  .attr("r", 0)
  .attr("opacity", .5)
  .transition()
  .duration(750)
  .attr("r", function(d){return d.radius;});
  graph.selectAll(".label")
  .data(data)
  .enter()
  .append("text")
  .text(function(d){return d.name + "(" + d.count + ")";})
  .attr("x", function(d){return d.cx;})
  .attr("y", function(d){return d.cy;})
  .attr("text-anchor", "middle");
}

如果您对 d3 有所了解,那么这些代码中的大部分看起来都很熟悉。 传入的数据数组是从节点服务中检索的。 首先,我们在页面上创建任意大小的 SVG 元素。 然后,我们设置一个颜色比例,使我们的可视化将很好地着色。 calculateBubbles函数是一个辅助函数,用于计算气泡的位置。 它用圆的 x 和 y 坐标以及圆的半径来增加我们的数据数组。 我们不会在这里讨论这个,但是代码可以在 GitHub 上找到。 我们为每一个顶级推特用户创建一个气泡。 我们使用颜色比例给它上色,并使用来自数据数组的预计算值设置位置。 一开始,我们设置半径为0,但随后我们使用一个过渡来扩大圆圈,以获得一个很好的加载效果。

对于每个圆,我们想要标记这个圆代表什么。 这是通过在圆心添加一个文本元素来实现的。

根据我追踪的 10 个最活跃的人,得出的图表如下:

Client side

每个人都发了超过 35000 次推特。

小结

现在,您应该能够设置一个新的应用来查询 Twitter,使用 node.js 上的 OAuth 库创建适当的 OAuth 令牌,并构建一个气泡图。 Twitter API 非常丰富,并且有许多潜在的可视化功能。 我相信我们可以想出几十种潜在的可视化方法。 没有比试验 API 更好的学习方法了,所以不要害怕弄得一团糟。

在下一章中,我们将看一看流行问答网站 Stack Overflow 上的可视化数据。 他们的 API 很大程度上是开放的,大多数查询都不需要认证,所以我们应该暂时不用使用 OAuth 甚至 node.js。