Skip to content
The code used in the "Don't @ Me: Hunting Twitter Bots at Scale" Black Hat presentation
Python Scala R
Branch: master
Clone or download
oanise93 updating urllib3
Updating `urllib3` because of potential security vulnerability
Latest commit a1b51d2 Apr 19, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
analysis Added data science code Aug 9, 2018
collection Initial release Aug 9, 2018
experiments making sure to grab correct date for retweets Oct 30, 2018
manager Initial release Aug 9, 2018
.gitignore Added --root-connections flag and fixed memory leaks Aug 28, 2018
LICENSE Initial commit Aug 5, 2018 Initial release Aug 9, 2018 Initial release Aug 9, 2018 Initial release Aug 9, 2018 Initial release Aug 9, 2018
modeling.Rmd Added data science code Aug 9, 2018
requirements.txt updating urllib3 Apr 19, 2019

Twitter Bot Analysis

This project contains the files used in the Black Hat "Don't @ Me: Hunting Twitter Bots at Scale" presentation to discover and aggregate a large sample of Twitter profile and tweet information.


Account and Tweet Collection

Note: We're working on creating Docker containers to automate this setup - Stay tuned!

The data gathering is split into two scripts: which collects accounts, and which collects tweets from previously gathered accounts.

The first step is to install the requirements:

pip install -r requirements.txt

The next step is to create a Twitter application (detailed instructions coming soon!). After creating the application and generating an access token and secret, you can set the following environment variables:

export TWEEPY_CONSUMER_KEY="your_consumer_key"
export TWEEPY_CONSUMER_SECRET="your_consumer_secret"
export TWEEPY_ACCESS_TOKEN="your_access_token"
export TWEEPY_ACCESS_TOKEN_SECRET="your_access_token_secret"

Once these environment variables are set, you can set up the database. We encourage setting up an instance of MySQL for storing data. Once this is created, you can set the connection string as an environment variable:

export DB_PATH="path_to_mysql"

Finally, you'll want to set up a Redis instance for caching. After this is done, you're ready to start gathering data!

Feature Extraction

The code associated with feature extraction utilizes Spark and is written in Scala; therefore, you'll need to install both. Also, if you'd like to run this code outside of an IDE (e.g., IntelliJ or Eclipse), you'll also need to install sbt.

After that initial setup, you should be able to assemble a jar by merely running assembly from the sbt interactive shell. The output will be twitter-bots.jar.


Tweet and Account Collection

The first iteration of the tool is designed to make it as easy as possible to control what types of account/tweet enumeration you want to do.

At a high level, there are two types of account enumeration:

  • enum - This process takes --min-id and --max-id flags and looks up accounts in bulk using Twitter's users/lookup API endpoint. If you want a random sampling of accounts between the min and max ID's, you can set the --enum-percentage flag.

  • stream - This process takes a --stream-query parameter if you want to filter the Twitter stream by a keyword. If this isn't provided, the sample endpoint will be used.

This is the usage for the account collection script:

$ python -h
usage: [-h] [--max-id MAX_ID] [--min-id MIN_ID] [--db DB]
                  [--enum-percentage ENUM_PERCENTAGE] [--no-stream]
                  [--no-enum] [--stream-query STREAM_QUERY]
                  [--account-filename ACCOUNT_FILENAME] [--stdout]

Enumerate public Twitter profiles and tweets

optional arguments:
  -h, --help            show this help message and exit
  --max-id MAX_ID       Max Twitter ID to use for enumeration
  --min-id MIN_ID       Minimum ID to use for enumeration
                        The percentage of 32bit account space to enumerate
  --no-stream           Disable the streaming
  --no-enum             Disable the account id enumeration
  --stream-query STREAM_QUERY, -q STREAM_QUERY
                        The query to use when streaming results
                        The filename to store compressed account JSON data
  --stdout              Print JSON to stdout instead of a file

After gathering accounts, you can start gathering tweets. Here's the usage for

$ python -h
usage: [-h] [--min-tweets MIN_TWEETS]
                         [--tweet-filename TWEET_FILENAME] [--no-lookup]

Enumerate public Twitter tweets from discovered accounts

optional arguments:
  -h, --help            show this help message and exit
  --min-tweets MIN_TWEETS, -mt MIN_TWEETS
                        The minimum number of tweets needed before fetching
                        the tweets
  --tweet-filename TWEET_FILENAME, -tf TWEET_FILENAME
                        The filename to store compressed tweet JSON data
  --no-lookup           Disable looking up users found in tweets
  --stdout              Print JSON to stdout instead of a file

Note on Running the Scripts

The and scripts can be run at the same time. It's recommended to start by running, since this starts populating the database with accounts. Then, can fetch accounts from the database, and grab the tweets.

A good solution for running both the scripts together is through something like screen.

Output Formats

The database created is mainly used as intermediary storage. It holds metadata about accounts, as well as whether or not tweets have been fetched for it.

The real value is in the ndjson files created. The creates a file, accounts.json.gz that contains the compressed JSON account information with one record on each line. Likewise, the script creates a file, tweets.json.gz that contains the compressed JSON tweet information.

Feature Extraction

This is the usage for the feature extraction script:

Performs feature extraction on twitter account and tweet data and saves data to local file system or S3.

Usage: spark-submit --class ExtractionApp twitter-bots.jar --account-data <account_data_path> --tweet-data <tweet_data_path>
       --destination <output_path> --extraction-type [unknown|training]

  -a, --account-data  <arg>      Twitter account data
  -d, --destination  <arg>       Destination for extracted data
  -e, --extraction-type  <arg>   Extracting Cresci data (training) or outputs
                                 from other scripts (unknown)
  -t, --tweet-data  <arg>        Tweet data
      --help                     Show help message

The files that are expected when --extraction-type unknown are JSON for both the account and tweet data. When --extraction-type training, the file formats expected are csv and parquet respectively.

Crawling Social Networks

We've included a script that makes it easy to crawl the social network for an account and generate a GEXF file that can imported into a tool like Gephi for visualization.

Basic usage is as simple as python jw_sec, where jw_sec is the account you want to crawl.

$ python -h
usage: [-h] [--graph-file GRAPH_FILE] [--raw RAW]
                        [--degree DEGREE] [--max-connections MAX_CONNECTIONS]

Crawl a Twitter user's social network

positional arguments:
  user                  User screen name to crawl

optional arguments:
  -h, --help            show this help message and exit
  --graph-file GRAPH_FILE, -g GRAPH_FILE
                        Filename for the output GEXF graph
  --raw RAW, -r RAW     Filename for the raw JSON data
  --degree DEGREE, -d DEGREE
                        Max degree of crawl
                        Max number of connections per account to crawl

Note: Social networks get large very quickly, and can take quite a while to crawl. We recommend setting the degree and max-connections flags to reasonable values (such as a 1-degree crawl with 1000 max connections per account) depending on your goals and available time.

Helpful Hints

The outputs of the tweet and account collection are very large files if run for an extended period of time; so, make sure that you have a cluster or single machine with the appropriate amount of available memory when running the feature extraction code.

You can’t perform that action at this time.