Note: This site is currently "Under construction". I'm migrating to a new version of my site building software. Lots of things are in a state of disrepair as a result (for example, footnote links aren't working). It's all part of the process of building in public. Most things should still be readable though.

Basic Twitter API Example Scripts in Ruby, Python, and Perl

I've been playing with Twitter's REST API to collect stats for work. Data like: follower counts, what Tweets are being faved, etc... There are libraries for working with the API, but no good examples for using it directly. So, I wrote my own^1^^.

It's not super complicated, but there are some tricky parts. I'm posting my code here for posterity's sake.

Each example follows the same basic flow outlined in Twitter's Application-only Authentication approach.

Step 5 being where real work would actually happen. It's also where looping for to make additional API requests would take place since the Access Token only needs to be pulled once^2^^.

Here's the code:

Ruby

Code

require "base64"
require "json"
require "net/http"
require "uri"

### Setup access credentials

consumer_key = "YOUR_CONSUMER_KEY_STRING"
consumer_secret = "YOUR_CONSUMER_SECRET_STRING"

### Get the Access Token

bearer_token = "#{consumer_key}:#{consumer_secret}"
bearer_token_64 = Base64.strict_encode64(bearer_token)

token_uri = URI("https://api.twitter.com/oauth2/token")
token_https = Net::HTTP.new(token_uri.host,token_uri.port)
token_https.use_ssl = true

token_request = Net::HTTP::Post.new(token_uri)
token_request["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
token_request["Authorization"] = "Basic #{bearer_token_64}"
token_request.body = "grant_type=client_credentials"

token_response = token_https.request(token_request).body
token_json = JSON.parse(token_response)
access_token = token_json["access_token"]

### Use the Access Token to make an API request

timeline_uri = URI("https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan")
timeline_https = Net::HTTP.new(timeline_uri.host,timeline_uri.port)
timeline_https.use_ssl = true

timeline_request = Net::HTTP::Get.new(timeline_uri)
timeline_request["Authorization"] = "Bearer #{access_token}"

timeline_response = timeline_https.request(timeline_request).body
timeline_json = JSON.parse(timeline_response)

puts JSON.pretty_generate(timeline_json)

Python

Code

import base64
import json
import urllib2

### Setup access credentials

consumer_key = "YOUR_CONSUMER_KEY_STRING"
consumer_secret = "YOUR_CONSUMER_SECRET_STRING"

### Get the Access Token

bearer_token = "%s:%s" % (consumer_key, consumer_secret)
bearer_token_64 = base64.b64encode(bearer_token)

token_request = urllib2.Request("https://api.twitter.com/oauth2/token") 
token_request.add_header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
token_request.add_header("Authorization", "Basic %s" % bearer_token_64)
token_request.data = "grant_type=client_credentials"

token_response = urllib2.urlopen(token_request)
token_contents = token_response.read()
token_data = json.loads(token_contents)
access_token = token_data["access_token"]

### Use the Access Token to make an API request

timeline_request = urllib2.Request("https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan")
timeline_request.add_header("Authorization", "Bearer %s" % access_token)

timeline_response = urllib2.urlopen(timeline_request)
timeline_contents = timeline_response.read()
timeline_data = json.loads(timeline_contents)

print json.dumps(timeline_data, indent=2, sort_keys=True)

Perl

Code

use strict;
use Data::Dumper;
use HTTP::Request::Common;
use JSON;
use LWP::UserAgent;
use MIME::Base64;
use Mozilla::CA; # Gets HTTPS working on Mac OSX (10.10)

### Setup access credentials

my $consumer_key = "YOUR_CONSUMER_KEY_STRING";
my $consumer_secret = "YOUR_CONSUMER_SECRET_STRING";

### Get the Access Token

my $bearer_token = "$consumer_key:$consumer_secret";
my $bearer_token_64 = encode_base64($bearer_token, "");

my $user_agent = LWP::UserAgent->new;

my $token_request = POST(
  "https://api.twitter.com/oauth2/token",
  "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8",
  "Authorization" => "Basic $bearer_token_64",
  Content => { "grant_type" => "client_credentials" },
);

my $token_response = $user_agent->request($token_request);
my $token_json = decode_json($token_response->content);

my $timeline_request = GET(
  "https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan",
  "Authorization" => "Bearer " . $token_json->{access_token}
);

my $timeline_response = $user_agent->request($timeline_request);
my $timeline_json = decode_json($timeline_response->content);

print Dumper $timeline_json;

A few things to keep in mind with these examples:

  • Authentication credentials should not be hard coded in source files.

  • They are very high level. Just one step beyond pseudo-code.

  • The code is straight procedural and almost certainly not how you should actually do it.

  • There is no error handling in these examples.

  • There is no accounting for throttling based on the API rate-limits.

  • No step to transform strings into RFC 1738 (which the docs say to do) was added since there is no change at this point.

  • Probably some other caveats that I'm forgetting.

  • Authentication credentials should not be hard coded in source files (in case you missed it the first time).

Generally, I'm all for using code libraries to make life easier. Working with APIs is a different story. Unless whoever makes the API is also providing the library, I'd rather use the service directly. Avoiding third-party dependencies is well worth the little extra work it takes. And, if there are good examples available there's not a huge difference between the levels of effort.

Footnotes

  • (id:1)

    The person I'm helping with this is interested in using the pandas data analysis library which is written in Python. I wrote the first version in that language. I decided to port it to Ruby since that's my go-to language these days.

    Once I started down the path of multiple versions it was easy to thrown in Perl (my previous language of choice). This was the first time in years that I've written any Perl. It's also the first time I've ever done anything beyond "Hello, World" in Python.

    All said, it was a good language exercise. It's fun to see the difference between them.

  • (id:2)

    The Access Token only needs to be pulled once for as long as it's valid. It should stay that way unless a specific request to invalidate it occurs.