Python Twitter Tools

In the first episode of playing with twitter, I demo'ed
Tweepy. In this episode, I will use the
twitter library from Mike
Verdone
.

In the README and other sources of documentation, the config for the
user/app
credentials are accessed via os.path utilizing a
read_file_token method.
This method of credential retrieval has perplexed me in the past
because I was
unable to find a sort of "canonical" way of repeating this in other
projects. So
before started evaluating the code base, I set out to see if I could
find a more
'pythonic' way of storing and retrieving credentials. Lo and behold, we
have a
ConfigParser module built right into the standard library. Further,
Doug Hellmann
has graciously covered the ConfigParser module in great detail
here.

To get started with this config option:

sample twitter.ini file

[twitter]  
CONSUMER_KEY = iowef03434ijsasdfkdmjlfasdasdf2039sdkon  
CONSUMER_SECRET = akldsjfsal3fadijfaosdfssasdk34  
oauth_token = kasdljnbfglkajdsnfq3asdf4908  
oauth_secret = dalkfewoieasdfjaosdkmcadfaslkjdfsdfalasdf

note: these values are bogus

from twitter import *
        from ConfigParser import SafeConfigParser
        
        parser = SafeConfigParser()
        parser.read('twitter.ini')
        
        consumer_key = parser.get('twitter', 'CONSUMER_KEY')
        consumer_secret = parser.get('twitter', 'CONSUMER_SECRET')
        oauth_access_token = parser.get('twitter', 'oauth_token')
        oauth_access_secret = parser.get('twitter', 'oauth_secret')
        
        twitter = Twitter(auth=OAuth(
            oauth_access_token, oauth_access_secret, consumer_key, consumer_secret))
        
        # Update your status
        twitter.statuses.update(
            status="Control the things that are in your control") 

        {u'contributors': None,
         u'coordinates': None,
         u'created_at': u'Tue Apr 23 05:15:56 +0000 2013',
         u'entities': {u'hashtags': [], u'urls': [], u'user_mentions': []},
         u'favorited': False,
         u'geo': None,
         u'id': 326565076066766848,
         u'id_str': u'326565076066766848',
         u'in_reply_to_screen_name': None,
         u'in_reply_to_status_id': None,
         u'in_reply_to_status_id_str': None,
         u'in_reply_to_user_id': None,
         u'in_reply_to_user_id_str': None,
         u'place': None,
         u'retweet_count': 0,
         u'retweeted': False,
         u'source': u'ipython-local',
         u'text': u'Control the things that are in your control',
         u'truncated': False,
         u'user': {u'contributors_enabled': False,
          u'created_at': u'Sun Oct 31 17:44:06 +0000 2010',
          u'default_profile': False,
          u'default_profile_image': False,
          u'description': u'Father. Firefighter. Mobile Crafter. Web Crafter. Garage-Business-Dude. ',
          u'entities': {u'description': {u'urls': []},
           u'url': {u'urls': [{u'display_url': u'michaelmartinez.in',
              u'expanded_url': u'http://michaelmartinez.in',
              u'indices': [0, 22],
              u'url': u'http://t.co/MTXlg5n87M'}]}},
          u'favourites_count': 1,
          u'follow_request_sent': False,
          u'followers_count': 118,
          u'following': False,
          u'friends_count': 441,
          u'geo_enabled': False,
          u'id': 210539658,
          u'id_str': u'210539658',
          u'is_translator': False,
          u'lang': u'en',
          u'listed_count': 3,
          u'location': u'Arizona',
          u'name': u'Michael Martinez',
          u'notifications': False,
          u'profile_background_color': u'706B5C',
          u'profile_background_image_url': u'http://a0.twimg.com/profile_background_images/543977615/tankdude1-Twit.jpg',
          u'profile_background_image_url_https': u'https://si0.twimg.com/profile_background_images/543977615/tankdude1-Twit.jpg',
          u'profile_background_tile': False,
          u'profile_image_url': u'http://a0.twimg.com/profile_images/1496212678/myMascot_normal.png',
          u'profile_image_url_https': u'https://si0.twimg.com/profile_images/1496212678/myMascot_normal.png',
          u'profile_link_color': u'009999',
          u'profile_sidebar_border_color': u'EEEEEE',
          u'profile_sidebar_fill_color': u'EFEFEF',
          u'profile_text_color': u'333333',
          u'profile_use_background_image': True,
          u'protected': False,
          u'screen_name': u'MonkMartinez',
          u'statuses_count': 1582,
          u'time_zone': u'Arizona',
          u'url': u'http://t.co/MTXlg5n87M',
          u'utc_offset': -25200,
          u'verified': False}}

The nice thing about PTT is that you can inspect the returned fields of
the
Twitter API requested as you work. This may save time in that you don't
need to
look at the API docs as much. It may also aid in the access of data, as
nested
dict's and tuples are not easy to iterate if you don't know how many
layers of
nesting to uncover. For example:

home_timeline = twitter.statuses.home_timeline() # returns sweet, parseable decoded json
        home_timeline[0] # the most recent tweet in my timeline

        {u'contributors': None,
         u'coordinates': None,
         u'created_at': u'Tue Apr 23 05:16:38 +0000 2013',
         u'entities': {u'hashtags': [],
          u'symbols': [],
          u'urls': [{u'display_url': u'oreilly.com/openbook/',
            u'expanded_url': u'http://oreilly.com/openbook/',
            u'indices': [20, 42],
            u'url': u'http://t.co/vtk5YWe44y'}],
          u'user_mentions': []},
         u'favorite_count': 0,
         u'favorited': False,
         u'geo': None,
         u'id': 326565255465558017,
         u'id_str': u'326565255465558017',
         u'in_reply_to_screen_name': None,
         u'in_reply_to_status_id': None,
         u'in_reply_to_status_id_str': None,
         u'in_reply_to_user_id': None,
         u'in_reply_to_user_id_str': None,
         u'lang': u'en',
         u'place': None,
         u'possibly_sensitive': False,
         u'retweet_count': 0,
         u'retweeted': False,
         u'source': u'<a href="http://www.tweetdeck.com" rel="nofollow">TweetDeck</a>',
         u'text': u"Free O'Reilly books http://t.co/vtk5YWe44y",
         u'truncated': False,
         u'user': {u'contributors_enabled': False,
          u'created_at': u'Tue Mar 13 20:12:40 +0000 2007',
          u'default_profile': False,
          u'default_profile_image': False,
          u'description': u'Now putting podcasts in the cloud at http://t.co/kuIpq9dj. Previously Google, Osmosoft. #HTML5 #Rails #UX #DevExp michael@mahemoff.com',
          u'entities': {u'description': {u'urls': [{u'display_url': u'player.fm',
              u'expanded_url': u'http://player.fm',
              u'indices': [37, 57],
              u'url': u'http://t.co/kuIpq9dj'}]},
           u'url': {u'urls': [{u'expanded_url': None,
              u'indices': [0, 19],
              u'url': u'http://mahemoff.com'}]}},
          u'favourites_count': 1747,
          u'follow_request_sent': None,
          u'followers_count': 5715,
          u'following': True,
          u'friends_count': 1138,
          u'geo_enabled': False,
          u'id': 1112431,
          u'id_str': u'1112431',
          u'is_translator': False,
          u'lang': u'en',
          u'listed_count': 486,
          u'location': u'London + Melbourne',
          u'name': u'Michael Mahemoff',
          u'notifications': None,
          u'profile_background_color': u'022330',
          u'profile_background_image_url': u'http://a0.twimg.com/profile_background_images/97774687/twilk_background_4bddb8dda6006.jpg',
          u'profile_background_image_url_https': u'https://si0.twimg.com/profile_background_images/97774687/twilk_background_4bddb8dda6006.jpg',
          u'profile_background_tile': False,
          u'profile_image_url': u'http://a0.twimg.com/profile_images/1150432577/speakhdr_normal.jpg',
          u'profile_image_url_https': u'https://si0.twimg.com/profile_images/1150432577/speakhdr_normal.jpg',
          u'profile_link_color': u'717273',
          u'profile_sidebar_border_color': u'000000',
          u'profile_sidebar_fill_color': u'99CC33',
          u'profile_text_color': u'3E4415',
          u'profile_use_background_image': True,
          u'protected': False,
          u'screen_name': u'mahemoff',
          u'statuses_count': 12996,
          u'time_zone': u'London',
          u'url': u'http://mahemoff.com',
          u'utc_offset': 0,
          u'verified': False}}

     home_timeline[0]['text']

        u"Free O'Reilly books http://t.co/vtk5YWe44y"

urls is a nested dict inside a dict of from the tweet above. Lets see
how we
would access that data if we needed it...

    def double_nest():
        for i in home_timeline[0]['entities']['urls']:
            print i
            return i
    double_nest()

    {u'url': u'http://t.co/vtk5YWe44y'indices': [20, 42], u'expanded_url':
    u'http://oreilly.com/openbook/', u'display_url': u'oreilly.com/openbook/'} 
    {u'display_url': u'oreilly.com/openbook/',
     u'expanded_url': u'http://oreilly.com/openbook/',
     u'indices': [20, 42],
     u'url': u'http://t.co/vtk5YWe44y'}

So now that we have the basics down, lets poke around some of the more
interesting API endpoints. I'm going to investigate trends and this
requires a
WOEID
and because I am a bit lazy, this WOEID look up should suffice: WOEID
Look
up.

trends_available = twitter.trends.available() # returns a long list of places where trends currently "exist" 
        trends_available

        [{u'country': u'',
          u'countryCode': None,
          u'name': u'Worldwide',
          u'parentid': 0,
          u'placeType': {u'code': 19, u'name': u'Supername'},
          u'url': u'http://where.yahooapis.com/v1/place/1',
          u'woeid': 1},
         {u'country': u'Brazil',
          u'countryCode': u'BR',
          u'name': u'Manaus',
          u'parentid': 23424768,
          u'placeType': {u'code': 7, u'name': u'Town'},
          u'url': u'http://where.yahooapis.com/v1/place/455833',
          u'woeid': 455833},
          u'placeType': {u'code': 7, u'name': u'Town'},
          u'url': u'http://where.yahooapis.com/v1/place/455867',
          u'woeid': 455867},
         {u'country': u'Argentina',
          u'countryCode': u'AR',
          u'name': u'Cxf3rdoba',
          u'parentid': 23424747,
          u'placeType': {u'code': 7, u'name': u'Town'},
          u'url': u'http://where.yahooapis.com/v1/place/466861',
          u'woeid': 466861},
         {u'country': u'Argentina',
          u'countryCode': u'AR',
          u'name': u'Rosario',
          u'parentid': 23424747,
          u'placeType': {u'code': 7, u'name': u'Town'},
          u'url': u'http://where.yahooapis.com/v1/place/466862',
          u'woeid': 466862},
         ... cut to save space
    

Tucson, Arizona USA = 2508428. Note for passing parameters into
requests, the
magic underscore is nessesary as seen in _id="2508428

    trends_tucson = twitter.trends.place(_id="2508428")
    for i in trends_tucson:
        for b in i['trends']:
            print b['name']
 


    #5PainfulThings
    #IntialsOfSomeoneYouCareAbout
    #MediaQuestionsForDzhokhar
    #encorepaso
    #ThereGoesMyPants
    Tony Allen
    Eric Bledsoe
    Matt Barnes
    Brandon Belt
    Lamar Odom

Nothing really happening in Tucson as these closely match the global
trends...
but San Diego is mentioned, what about San Diego? WOEID = 2487889

    trends_SD = twitter.trends.place(_id="2487889")
        for i in trends_SD:
            for b in i['trends']:
                print b['name']


        #middleschoolmemories
        #IntialsOfSomeoneYouCareAbout
        #LHHATL
        #ItsTooEarlyTo
        #earthday
        Tony Allen
        Z Bo
        Jason's Lyric
        Matt Barnes
        Rudy Gay

Cool, some of these look interesting but I'll leave the investigation to
you ...
Boston had some major incidents this past week. Are Bostonians still
talking
about it? Boston WOEID = 2367105

    trends_Bos = twitter.trends.place(_id="2367105")
        for i in trends_Bos:
            for b in i['trends']:
                print b['name']


        #IntialsOfSomeoneYouCareAbout
        #ItsTooEarlyTo
        #LHHATL
        #RAW
        Happy Earth Day
        Stevie J
        #dragrace
        Jason's Lyric
        Tony Allen
        Eric Bledsoe

Hmmm... TV shows and big topics... One would assume (you know about
assumptions) that perhaps we could find
something about the recent events in Boston trending.

Lets see what a geo search for Tucson yields. There are a million of
cool and novel ways to use this data,
this little demo simply shows you how to access the data.

    geo_tweets = twitter.geo.search(query="tucson")
        geo_tweets['result']

        {u'places': [{u'attributes': {},
           u'bounding_box': {u'coordinates': [[[-111.059406, 32.033883],
              [-110.708206, 32.033883],
              [-110.708206, 32.32095],
              [-111.059406, 32.32095]]],
            u'type': u'Polygon'},
           u'contained_within': [{u'attributes': {},
             u'bounding_box': {u'coordinates': [[[-114.816591, 31.332177],
                [-109.045223, 31.332177],
                [-109.045223, 37.00426],
                [-114.816591, 37.00426]]],
              u'type': u'Polygon'},
             u'country': u'United States',
             u'country_code': u'US',
             u'full_name': u'Arizona, US',
             u'id': u'a612c69b44b2e5da',
             u'name': u'Arizona',
             u'place_type': u'admin',
             u'url': u'https://api.twitter.com/1.1/geo/id/a612c69b44b2e5da.json'}],
           u'country': u'United States',
           u'country_code': u'US',
           u'full_name': u'Tucson, AZ',
           u'id': u'e6a7d49161470b37',
           u'name': u'Tucson',
           u'place_type': u'city',
           u'url': u'https://api.twitter.com/1.1/geo/id/e6a7d49161470b37.json'},
          {u'attributes': {},
           u'bounding_box': {u'coordinates': [[[-110.977543, 32.185824],
              [-110.960795, 32.185824],
              [-110.960795, 32.203223],
              [-110.977543, 32.203223]]],
            u'type': u'Polygon'},
           u'contained_within': [{u'attributes': {},
             u'bounding_box': {u'coordinates': [[[-114.816591, 31.332177],
                [-109.045223, 31.332177],
                [-109.045223, 37.00426],
                [-114.816591, 37.00426]]],
              u'type': u'Polygon'},
             u'country': u'United States',
             u'country_code': u'US',
             u'full_name': u'Arizona, US',
             u'id': u'a612c69b44b2e5da',
             u'name': u'Arizona',
             u'place_type': u'admin',
             u'url': u'https://api.twitter.com/1.1/geo/id/a612c69b44b2e5da.json'}],
           u'country': u'United States',
           u'country_code': u'US',
           u'full_name': u'South Tucson, AZ',
           u'id': u'9ba0ab30258b7362',
           u'name': u'South Tucson',
           u'place_type': u'city',
           u'url': u'https://api.twitter.com/1.1/geo/id/9ba0ab30258b7362.json'},
          {u'attributes': {},
           u'bounding_box': {u'coordinates': [[[-111.218168, 32.154521],
              [-111.044793, 32.154521],
              [-111.044793, 32.248227],
              [-111.218168, 32.248227]]],
            u'type': u'Polygon'},
           u'contained_within': [{u'attributes': {},
             u'bounding_box': {u'coordinates': [[[-114.816591, 31.332177],
                [-109.045223, 31.332177],
                [-109.045223, 37.00426],
                [-114.816591, 37.00426]]],
              u'type': u'Polygon'},
             u'country': u'United States',
             u'country_code': u'US',
             u'full_name': u'Arizona, US',
             u'id': u'a612c69b44b2e5da',
             u'name': u'Arizona',
             u'place_type': u'admin',
             u'url': u'https://api.twitter.com/1.1/geo/id/a612c69b44b2e5da.json'}],
           u'country': u'United States',
           u'country_code': u'US',
           u'full_name': u'Tucson Estates, AZ',
           u'id': u'2df92895f5362a8a',
           u'name': u'Tucson Estates',
           u'place_type': u'city',
           u'url': u'https://api.twitter.com/1.1/geo/id/2df92895f5362a8a.json'},
          {u'attributes': {},
           u'bounding_box': {u'coordinates': [[[-110.789464, 31.932823],
              [-110.755804, 31.932823],
              [-110.755804, 31.963138],
              [-110.789464, 31.963138]]],
            u'type': u'Polygon'},
           u'contained_within': [{u'attributes': {},
             u'bounding_box': {u'coordinates': [[[-114.816591, 31.332177],
                [-109.045223, 31.332177],
                [-109.045223, 37.00426],
                [-114.816591, 37.00426]]],
              u'type': u'Polygon'},
             u'country': u'United States',
             u'country_code': u'US',
             u'full_name': u'Arizona, US',
             u'id': u'a612c69b44b2e5da',
             u'name': u'Arizona',
             u'place_type': u'admin',
             u'url': u'https://api.twitter.com/1.1/geo/id/a612c69b44b2e5da.json'}],
           u'country': u'United States',
           u'country_code': u'US',
           u'full_name': u'Corona de Tucson, AZ',
           u'id': u'4d35b22f6680264a',
           u'name': u'Corona de Tucson',
           u'place_type': u'city',
           u'url': u'https://api.twitter.com/1.1/geo/id/4d35b22f6680264a.json'}]}

Cool, Now we'll perform a query for tweets containing python job and
iterate
the results:

    fun_search = twitter.search.tweets(q="python job",result_type='recent', count=20)
        #fun_search # uncomment to see the returned data for fields that look interesting...

        for b in fun_search['statuses']:
            for c in b['entities']['urls']:
                print b['text'], b['created_at'], c['url']


        Lead Software Engineer (Server side, Python) * http://t.co/qObCXSv46P
        * Empresa: F-Secure * Lugar: Helsinki #empleo #trabajo #finlandia Tue Apr 23
        05:22:07 +0000 2013 http://t.co/qObCXSv46P
        #Python Install and configure reddit clone script on amazon web services by
        gorrrra: Configure existing r... http://t.co/VgYuI3te4T #Job Tue Apr 23 05:14:09
        +0000 2013 http://t.co/VgYuI3te4T
        New Job Alert: Senior Software Engineers (Ruby on Rails, Python and/or Java)
        at Rafte... http://t.co/Ef7ZsAGdAe #python #jobs Tue Apr 23 05:07:38 +0000 2013
        http://t.co/Ef7ZsAGdAe
        Site Reliability Engineer http://t.co/J784yxGA3w #java #python #jobs #hiring
        #careers Tue Apr 23 05:00:47 +0000 2013 http://t.co/J784yxGA3w
        #uitgeverij #Belgie Python Developer gevraagd voor interne functie te
        Antwerpen: Comput... http://t.co/NstoYH6KIh http://t.co/6R25reAyu2 Tue Apr 23
        04:47:51 +0000 2013 http://t.co/NstoYH6KIh
        #uitgeverij #Belgie Python Developer gevraagd voor interne functie te
        Antwerpen: Comput... http://t.co/NstoYH6KIh http://t.co/6R25reAyu2 Tue Apr 23
        04:47:51 +0000 2013 http://t.co/6R25reAyu2
        [Full-time] Seeking Passionate Python developers at Soshio
        http://t.co/YVWCEEBBhA Tue Apr 23 04:36:33 +0000 2013 http://t.co/YVWCEEBBhA
        RT @zs_frontline: [Web求人情報]
         Python,Ruby,Perl,PHP,Javaなどを用いたWebアプリの開発やフロントエンド、デザイン案件まで多数あります!...
        http://t.co/t8WBRghjFF Tue Apr 23 04:03:26 +0000 2013 http://t.co/t8WBRghjFF
        [Web求人情報]
         Python,Ruby,Perl,PHP,Javaなどを用いたWebアプリの開発やフロントエンド、デザイン案件まで多数あります!...
        http://t.co/t8WBRghjFF Tue Apr 23 04:00:06 +0000 2013 http://t.co/t8WBRghjFF
        #job http://t.co/ew5ADWt8oE
         - Senior Python / MongoDB Developer, Array Tue Apr 23 03:55:12 +0000 2013
        http://t.co/ew5ADWt8oE
        #job http://t.co/4WMtLjTtJ0
         - Разработчик Python, Array Tue Apr 23 03:54:08 +0000 2013
        http://t.co/4WMtLjTtJ0
        Python Flask Data Collection http://t.co/cLcyyLaqri Tue Apr 23 03:53:40
        +0000 2013 http://t.co/cLcyyLaqri
        RT @findmjob: Site Reliability Engineer http://t.co/J784yxGA3w #php #git
        #java #rest #unix #ruby #mysql #redis #python #hadoop... http://t.… Tue Apr 23
        03:46:13 +0000 2013 http://t.co/J784yxGA3w
        Site Reliability Engineer http://t.co/J784yxGA3w #php #git #java #rest #unix
        #ruby #mysql #redis #python #hadoop... http://t.co/6xrPUo9zRr Tue Apr 23
        03:42:08 +0000 2013 http://t.co/J784yxGA3w
        Site Reliability Engineer http://t.co/J784yxGA3w #php #git #java #rest #unix
        #ruby #mysql #redis #python #hadoop... http://t.co/6xrPUo9zRr Tue Apr 23
        03:42:08 +0000 2013 http://t.co/6xrPUo9zRr
        Programador Python: Porto Alegre - RS -  Anúncio nº: 919784 Programador(a)
        PythonAnúncio p... http://t.co/fYoyZ35xLT #vagas #programador Tue Apr 23
        03:29:42 +0000 2013 http://t.co/fYoyZ35xLT
        New Job Alert: Software Engineer - Python at Jawbone (San Francisco, CA):
        technical design speci... http://t.co/02proruK5j #python #jobs Tue Apr 23
        03:09:55 +0000 2013 http://t.co/02proruK5j
        #job #python Write up 3 algorithm base on an interface and abstraction in
        OCaml by kennguy13: Sorry... http://t.co/PGb3klkXbV #freelance Tue Apr 23
        03:06:00 +0000 2013 http://t.co/PGb3klkXbV
        #C Write up 3 algorithm base on an interface and abstraction in OCaml by
        kennguy13: Sorry the project has... http://t.co/eV5enpwGcD #JOB Tue Apr 23
        03:02:37 +0000 2013 http://t.co/eV5enpwGcD
        #C Write up 3 algorithm base on an interface and abstraction in OCaml by
        kennguy13: Sorry the pro... http://t.co/V5cPAOv7PX #Programming Tue Apr 23
        03:02:36 +0000 2013 http://t.co/V5cPAOv7PX
        #Python Write up 3 algorithm base on an interface and abstraction in OCaml
        by kennguy13: Sorry the projec... http://t.co/57Kw3abxVV #Job Tue Apr 23
        02:18:35 +0000 2013 http://t.co/57Kw3abxVV

If the traditional method of accessing Twitter bores you or you want
finer
control over your use of the Twitter services, I think Python Twitter
Tools is a
viable option. It reliably hits and maps to the Twitter API and comes
packed
with an IRCBOT and commandline utilities. PTT is actively maintained as
of this
writing and looks well constructed in my opinion.