Mathew Inkson

Delete Old Tweets Selectively Using Python and Tweepy

  |   technology

For some time I’ve used an online service to delete tweets that are more than one week old. I do this because I use Twitter for levity, for throwaway comments and retweets on issues of the day, and I don’t really want those saved for posterity. Thanks to search crawlers and caches I can never be certain that tweets are gone forever, but this is a small step in that direction.

When I joined Keybase I discovered that I needed to prevent my ‘proof’ tweet from being deleted, and the simple method used by the online deletion service was no longer an option. My solution uses an exception list containing the IDs of the tweets I wish to save, and these are ignored when their contemporaries are merged with the infinite.

I’ve written a Python script that uses Tweepy to scan the contents of my timeline and delete any tweet that meets two criteria - more than seven days old and not in my exception list. It’s very simple, there are probably better ways of doing it (please let me know), but it works well for me as a nightly cron job.

Please note that since I’ve been deleting my old tweets this way for some time I’ve never had issues with the Twitter API rate limits. Every deletion is an API call, so if you have many tweets you may need to consider initially limiting the number returned via the .items() method. This is demonstrated in the Tweepy cursor tutorial.

To get the required authentication keys you will need to register a Twitter application.

Update

Since my initial post I’ve added functionality to unfavor (or ‘unfavorite’) tweets, too. I’ve included the full script below.

#!/usr/bin/env python
 
import tweepy
from datetime import datetime, timedelta
 
# options
test_mode = False
verbose = False
delete_tweets = True
delete_favs = True
days_to_keep = 7
 
tweets_to_save = [
	573245340398170114, # keybase proof
	573395137637662721, # a tweet to this very post
]
favs_to_save = [
	362469775730946048, # tony this is icac
]
 
# auth and api
consumer_key = 'XXXXXXXX'
consumer_secret = 'XXXXXXXX'
access_token = 'XXXXXXXX'
access_token_secret = 'XXXXXXXX'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)
 
# set cutoff date, use utc to match twitter
cutoff_date = datetime.utcnow() - timedelta(days=days_to_keep)
 
# delete old tweets
if delete_tweets:
	# get all timeline tweets
	print "Retrieving timeline tweets"
	timeline = tweepy.Cursor(api.user_timeline).items()
	deletion_count = 0
	ignored_count = 0
 
	for tweet in timeline:
		# where tweets are not in save list and older than cutoff date
		if tweet.id not in tweets_to_save and tweet.created_at < cutoff_date:
			if verbose:
				print "Deleting %d: [%s] %s" % (tweet.id, tweet.created_at, tweet.text)
			if not test_mode:
				api.destroy_status(tweet.id)
			 
			deletion_count += 1
		else:
			ignored_count += 1
 
	print "Deleted %d tweets, ignored %d" % (deletion_count, ignored_count)
else:
	print "Not deleting tweets"
	 
# unfavor old favorites
if delete_favs:
	# get all favorites
	print "Retrieving favorite tweets"
	favorites = tweepy.Cursor(api.favorites).items()
	unfav_count = 0
	kept_count = 0
 
	for tweet in favorites:
		# where tweets are not in save list and older than cutoff date
		if tweet.id not in favs_to_save and tweet.created_at < cutoff_date:
			if verbose:
				print "Unfavoring %d: [%s] %s" % (tweet.id, tweet.created_at, tweet.text)
			if not test_mode:
				api.destroy_favorite(tweet.id)
			 
			unfav_count += 1
		else:
			kept_count += 1
 
	print "Unfavored %d tweets, ignored %d" % (unfav_count, kept_count)
else:
	print "Not unfavoring tweets"

Comments

Comments are no longer supported—please contact me elsewhere.
The following have been migrated from an old site and may have formatting errors.
Georges-Henry PORTEFAIT's gravatar

Georges-Henry PORTEFAIT

Hello anytime I try to destroy a status I got an error message : [“TweepError: [{u’message’: u’This method requires a POST.’, u’code’: 86}]\n”]

I can post statuses with api.update_status

but not destroy statuses !

Do You have any clues ? I goggled and found mostly nothing Here is my code

def teardown_fixtures(): “““Cleaning function””” print(‘In Teardown’)

    try:
        # Consumer keys and access tokens, used for OAuth
        consumer_key = ''
        consumer_secret = ''
        access_token = ''
        access_token_secret = ''
        auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
        auth.set_access_token(access_token, access_token_secret)
        # Creation of the actual interface, using authentication
        api = tweepy.API(auth)
       
        # Delete tweets one by one
        for status in tweepy.Cursor(api.user_timeline).items(200):
            api.destroy_status(status.id)

        print "Twitter timeline removed!"
    except:
        print('Exception occurred in teardown_fixtures')
        exc_type, exc_value, _ = sys.exc_info()
        print(traceback.format_exception_only(exc_type, exc_value))
    finally:
        pass

# add the cleaner to the session
request.addfinalizer(teardown_fixtures)
return api
Mathew's gravatar

Mathew

in reply to Georges-Henry PORTEFAIT

I can’t see anything obvious there, looks fine to me. The only thing I have different is that I’m not restricting the timeline to 200 items, but I can’t imagine that would matter.
Andrew Curry (@andrewcurryla)'s gravatar

Andrew Curry (@andrewcurryla)

in reply to Georges-Henry PORTEFAIT

make sure your apps permissions are correct.

Dan

I came across your script and have made a few changes to suit my needs, have you got a license for this so I can release the changes?
Mathew's gravatar

Mathew

in reply to Dan

I’ve never been concerned about the brief snippets I post, but it’s probably a good idea, thanks. I’ve added a license to cover code on the site, go for it: https://www.mathewinkson.com/license

mike

thank you!

i used your example for my own script

Francois

Thanks, this script works really well to delete not-too-old tweets. To delete even older tweets (not served through the API), I wrote another script : https://gist.github.com/flesueur/bcb2d9185b64c5191915d860ad19f23f
Mathew's gravatar

Mathew

in reply to Francois

Ah, I didn’t realise the API had that limitation. Thanks for the update.