/ Node.js

How to create your own Instagram Bot with Node.js and Puppeteer

Just a short while ago, I finally gave in and created an Instagram account. I know, I'm pretty late in the game, but it seems like there's not much going on on Facebook today and Instagram is where it's all happening today. However, being so late, I thought it would be fun to see if I could come up with a way that gave me a little boost. Not that I want to be the next big Instagram celeb, but it would be nice if I could catch up with my friends a bit. So after some late nights and a bit of tweaking I wrote my own Bot. Soon after, my account had nearly 2000 followers :P

In this article, I'm going to explain step-by-step how I wrote the Bot. It's going to be a simplified version though, because at some point I also added machine learning and computer vision to like posts and follow people to my taste. I might do a follow-up that focuses on those features, but for now, we're going to just look at the basics of building a working Bot. Also, if you're not a coder yourself, or just want your own Bot up and running quickly, you can just head to the Github project, clone it, and run it yourself.

The purpose of my Instagram Bot

First, let's take a step back and look at might intentions for building my Instagram Bot. I'm well aware that there are already some similar Bots written in other languages and even some professional cloud-based Bots you can pay for. However, I found that these are mostly very aggressive Bots with a high likelihood of getting your account banned. I wanted something custom that generates enough interaction to help grow my account in a natural and safe way. So this is the basic functionality I had in mind:

  • Find accounts that interest me based on a collection of hashtags.
  • Randomly like posts that show up in the hashtag search results.
  • Randomly follow some people that show up in the hashtag search result.
  • Keep track of which people I'm following.
  • At some point unfollow accounts to keep my followers/following ration in balance.

Also, this is by no means a Bot that should run 24 hours day. You just start it once in a while to boost engagement. We don't want to get banned and it's not intended to create a fully superficial account. And, this might be obvious, but Bot or no Bot, you'll need to create valuable and engaging content for it to work. Just running this on an empty account or an account with low-grade content will not work.

Why Puppeteer?

Puppeteer is a headless Chrome Node API. But to understand why I went for Puppeteer, we first need to consider how most Instagram Bots work. In the early days, they mostly made use of the Instagram API, but Instagram recently closed down most of its API because there was a lot of abuse. Today's Bots try to emulate the server calls made to Instagram in order to create engagement. The problem with these methods is that it's not how a 'normal' user would use Instagram, and thus, also easily detected and banned. I wanted the behavior of the Bot be as close as possible as if I was browsing Instagram, liking posts and following users myself.

Enter the headless browser: Puppeteer. In simple terms, a headless browser is just like a normal browser, but without the graphical user interface. It runs entirely in memory and is programmatically controlled. So Puppeteer is a Node API which you use to control a headless version of Google Chrome. It's perfect for building scrapers, automated testing software or emulate a real user browsing a website. Essentially we're going to program Puppeteer to browse and interact with the Instagram website.

Coding your Instagram Bot from scratch

As I said before, if you're not into coding a Bot yourself, you can just clone the Github project, edit the configuration file and give it a go. If you want to learn how to work with Puppeteer and build your own Bot, follow from here.

Create a new project and install the required libraries

So let's begin. Create a folder for your project somewhere with a name that makes sense. Open your terminal, navigate to your project folder and initialize a new npm project with all the default options to create your package.json.

npm init -y

Next, we'll need to create an index.js file, as this is the default executable file. Run the following command in your terminal to create it.

touch index.js

Create and import the required libraries

To finish our initial project setup, we need to install a couple of libraries. The first one is Puppeteer, the second is PouchDB which we will use for our local storage solution and the last one, shuffle-array, is just a little helper to shuffle an array of hashtags.

npm install puppeteer --save

npm install pouchdb --save

npm install shuffle-array --save

Database operations to keep track of follows

Our Instagram Bot will need to keep track of the accounts we are following and for that, we will need a simple local database solution. I've selected PouchDB, which is a simple Javascript database that runs in the browser and works in Node projects as well. It's a very simple document-based non-relational database with limited functionality, but I'm going for ease of use and simplicity over complexity and performance. If you don't like PouchDB, you could easily replace this with something like SQlite or Mongo.

Here's the functionality we need:

  • Keep track of a user accounts we're following.
  • Keep track of when we started following a user.
  • Get a list of all the accounts we're following.
  • Retrieve a specific account to check if we're following that account or not.
  • Unfollow an account, and archive that account so that we don't follow it again.

We're going to put all that functionality in a separate Javascript file inside a source folder 'src'. Use the following command to create the source folder and our PouchDB javascript file.

mkdir src && touch src/pouchDB.js

In pouchDB.js we're going to initialize 2 databases: follows and followArchive. The first one is to keep track of the users we're following, and the last one is to archive users we've unfollowed. We're also going to create 4 functions to support the functionality we've discussed. addFollow to add user accounts we're following, getFollows to get all users we're following, unFollow to unfollow a user and archive that user, and inArchive to check if a user has been archived.

let PouchDB = require('pouchdb');

let db = new PouchDB('follows');
let db_archive = new PouchDB('followsArchive');

let addFollow = async function (username) {
    return db.put({_id: username, added: new Date().getTime()});
};

let getFollows = async function () {
    return db.allDocs({include_docs: true});
};

let unFollow = async function (username) {
    return new Promise(function (resolve, reject) {
        db.get(username).then(doc => {
            return db.remove(doc);
        }).then(() => {
            return db_archive.put({_id: username});
        }).then(() => {
            resolve(true);
        }).catch(e => reject(e));
    });
};

let inArchive = async function (username) {
    return db_archive.get(username);
};

module.exports.addFollow = addFollow;
module.exports.inArchive = inArchive;
module.exports.getFollows = getFollows;
module.exports.unFollow = unFollow;

Creating a configuration file

The last step before we can actually start coding our Bot is to create a configuration file. You don't want to go mess around in your code every time you need to tweak the Bot or update authentication credentials. By creating a configuration file we can easily change some settings later.

This is the information we need to keep in our config file:

  • Login credentials for Instagram.
  • A list of hashtags to guide our Bot.
  • General settings for how our Bot behaves.
  • CSS selectors so Puppeteer knows which links and buttons to click.

Run the following command to create config folder and a config.json file.

mkdir config && touch config/config.json

Copy this JSON object into your config file.

{
  "username": "YOUR INSTAGRAM USERNAME",
  "password": "YOUR INSTAGRAM PASSWORD",
  "hashtags": ["love", "fashion", "happy", "cute", "followme", "selfie", ...],
  "settings": {
    "run_every_x_hours": 1,
    "like_ratio": 0.75,
    "follow_ratio": 0.25,
    "do_unfollows": true,
    "unfollow_after_days": 2,
    "headless": true
  },
  "selectors": {
    "home_to_login_button": ".izU2O a",
    "username_field": "input[type=\"text\"]",
    "password_field": "input[type=\"password\"]",
    "login_button": "._1OSdk button",
    "post_heart_grey": "span.glyphsSpriteHeart__outline__24__grey_9",
    "post_username": "div.e1e1d > a",
    "post_follow_link": "span.oW_lN._1OSdk > button",
    "post_like_button": "span.fr66n > button",
    "post_follow_button": "span.oW_lN._1OSdk > button",
    "post_close_button": "button.ckWGn",
    "user_unfollow_button": "span > button",
    "user_unfollow_confirm_button": "div.mt3GC > button.aOOlW.-Cab_"
  }
}

Obviously you should update the config with your own username and password, and you should create a list of about 20 hashtags that define your style/interests. Like, if you're really into make-up, do some research and find out what the most popular make-up related hashtags are.

Most of the other settings are self-explanatory. They're mostly about how often you want to run your Bot and about behavior for likes, follows and unfollows. The ratios create a random factor to like posts and follow users. A ratio of 1 will like all posts, 0.5 will like 50% of the posts, 0 is no likes at all.

In selectors we save our CSS Selectors that Puppeteer will need to navigate and interact with the Instagram website. We save them in our configuration because sometimes these selectors can change and then we need to update them. If Instagram pushes a new design, some of these selectors might break. You want to be able to quickly update a selector without messing around in your code.

Setting up Puppeteer

On to the good stuff now. Run the following command to create puppeteer.js that will contain our Bot functionality.

touch src/puppeteer.js

Open puppeteer.js. We need to import a few libraries: the Puppeteer API, an array shuffle utility, our database operations and our configuration file.

const puppeteer = require('puppeteer');
const shuffle = require('shuffle-array');

let ops = require('../src/pouchDB');
let cnf = require('../config/config.json');

Now we'll create our main bot function in puppeteer.js and initialize Puppeteer.

let run = async function () {

    // set up Puppeteer
    const browser = await puppeteer.launch({
        headless: cnf.settings.headless,
        args: ['--no-sandbox']
    });

    const page = await browser.newPage();
    page.setViewport({width: 1200, height: 764});
    
    // Close browser
    browser.close();
};

module.exports = run;

This run function creates a new headless browser instance, creates a new page and sets a viewport size. Setting the size is important because Instagram.com is responsive site and we want to make sure we get to desktop layout and not the adaptive mobile layout.

The headless argument defines if Puppeteer should launch a headless browser or not. We import the headless setting from our config file because at times we might want to set this to false for debugging purposes. When Puppeteer is not running in headless mode, it will open an actual browser window and you'll be able to watch Puppeteer commanding the browser in real time.

Now might be a good time to test if everything is setup right. Open index.js and copy the following code.

let bot = require('./src/puppeteer');
bot();

Now execute your project with Node.

node index.js

If you don't see any errors in your terminal, you're good to continue. Otherwise, try to figure out what went wrong and try to fix it.

Login to Instagram with Puppeteer

The first thing our Bot needs to do is authenticate and log into Instagram. Copy the following code inside your run function in puppeteer.js just after we set the viewport.

// Load Instagram
await page.goto('https://www.instagram.com');
await page.waitFor(2500);
await page.click(cnf.selectors.home_to_login_button);
await page.waitFor(2500);

// Login
await page.click(cnf.selectors.username_field);
await page.keyboard.type(cnf.username);
await page.click(cnf.selectors.password_field);
await page.keyboard.type(cnf.password);

await page.click(cnf.selectors.login_button);
await page.waitForNavigation();

This will go to Instagram.com, navigate to the login page, enter your credentials and login. It's important to note this won't work if you've enabled two-factor authentication for your account. You either need to disable two-factor or add your own code to support it.

If you want to test this, set the headless setting in your config to false and run your project. You should see Puppeteer opening a browser window and logging into Instagram. If you get an error, it's probably because some of the selectors are outdated. Go and check the config file in the Github repo and make sure you're using the lastest selectors.

Searching hashtags and looping through posts

This is the point were our hashtags come into play. We will loop through all the hashtags, and for each hashtag we will loop through the 9 latest posts that included that hashtag. Puppeteer will try to select each post so we can later like it or follow the user. If for some reason Puppeteer can't select the post it will continue to the next post.

let hashtags = shuffle(cnf.hashtags);

for (let hl = 0; hl < hashtags.length; hl++) {

    // Search for hashtags
    await page.goto('https://www.instagram.com/explore/tags/' + hashtags[hl] + '/?hl=en');
    console.log('===> hashtag search: ' + hashtags[hl]);

    // Loop through the latest 9 posts
    for (let r = 1; r < 4; r++) {
        for (let c = 1; c < 4; c++) {

            //Try to select post, wait, if successful continue
            let br = false;
            await page.click('div:nth-child(4) > div > div:nth-child(' + r + ') > div:nth-child(' + c + ') > a').catch(() => {
                br = true;
            });
            if (br) continue;
            await page.waitFor(2250 + Math.floor(Math.random() * 250));
        }
    }
}

You'll notice that after selecting a post, we make Puppeteer wait for a randomized period. Liking posts too quickly can cause a temporary ban. There's a maximum number of likes Instagram lets you do a day, and this way we spread our likes and follows over time. We're going to use the same technique for every major engagement Puppeteer does.

Evaluating the Instagram post

Before we can decide to like a post or follow a user, we first need some information about the post. Inside our loop, and after we selected a post and waited for a short time, insert the following code.

// Get post info
let hasEmptyHeart = await page.$(cnf.selectors.post_heart_grey);

let username = await page.evaluate(x => {
    let element = document.querySelector(x);
    return Promise.resolve(element ? element.innerHTML : '');
}, cnf.selectors.post_username);

let followStatus = await page.evaluate(x => {
    let element = document.querySelector(x);
    return Promise.resolve(element ? element.innerHTML : '');
}, cnf.selectors.post_follow_link);

This does 3 things. It checks if we already liked the post or not. We're looking for a grey heart icon, if it's there it means we haven't liked the post yet. And, we read the account name and the follow status.

Liking Instagram posts

Now that we have the post info, we can decide to like the post or not. For that, we use a random factor. By default, the config is set to like 7 in 10 posts. If we've like the post we make Puppeteer wait a short time before it can continue. Insert this code after reading out the post information.

if (hasEmptyHeart !== null && Math.random() < cnf.settings.like_ratio) {
    await page.click(cnf.selectors.post_like_button);
    console.log('---> like for ' + username);
    await page.waitFor(10000 + Math.floor(Math.random() * 5000));
}

Liking posts on a random base is not the most optimal way of doing it. Preferably you want to like only high-quality posts. For my own personal Bot I'm using machine learning and computer vision to decide to like a post, but including this here would make this post too long and too complicated for some people. I might do another post just on that topic later.

Following Instagram users

Following users is a bit more complicated than liking a post. First, we need to make sure we've never followed that user before by searching the followArchive database. Next, we check if we're not following that user right now. If all ok, we can decide to follow the user and add the account to our follows database.

let isArchivedUser;
await ops.inArchive(username).then(() => isArchivedUser = true).catch(() => isArchivedUser = false);

if (followStatus === 'Follow' && !isArchivedUser && Math.random() < cnf.settings.follow_ratio) {
    await ops.addFollow(username).then(() => {
        return page.click(cnf.selectors.post_follow_link);
    }).then(() => {
        console.log('---> follow for ' + username);
        return page.waitFor(10000 + Math.floor(Math.random() * 5000));
    }).catch(() => {
        console.log('---> Allready following ' + username);
    });
}

// Close post
await page.click(cnf.selectors.post_close_button).catch(() => console.log(':::> Error closing post'));

After that, we just need to close the post so Puppeteer can move on to evaluate another post.

Unfollowing Instagram users

We need to make sure we're not following too many users. We want to keep our follows/following ration in balance and Instagram also has a hard limit for the number of users you can follow. If the setting do_unfollows in the config is set to true the Bot will start unfollowing users that we started following a set number of days ago. The actual number of days is also defined in the config.

For every user we want to unfollow, Puppeteer is going to browse to the user's profile and unfollow that user. After Puppeteer unfollows a user we will also archive that user account in the followArchive database. There's also the case that a user account might not exist anymore. Also, in that case, we need to archive the user account.

if (cnf.settings.do_unfollows) {

    let cutoff = new Date().getTime() - (cnf.settings.unfollow_after_days * 86400000);
    let follows = await ops.getFollows();
    let unfollows = [];

    follows.rows.forEach(user => {
        if (user.doc.added < cutoff) {
            unfollows.push(user.doc._id);
        }
    });

    for (let n = 0; n < unfollows.length; n++) {

        let user = unfollows[n];
        await page.goto('https://www.instagram.com/' + user + '/?hl=en');
        await page.waitFor(1500 + Math.floor(Math.random() * 500));

        let followStatus = await page.evaluate(x => {
            let element = document.querySelector(x);
            return Promise.resolve(element ? element.innerHTML : '');
        }, cnf.selectors.user_unfollow_button);

        if (followStatus === 'Following') {
            console.log('---> unfollow ' + user);
            await page.click(cnf.selectors.user_unfollow_button);
            await page.waitFor(750);
            await page.click(cnf.selectors.user_unfollow_confirm_button);
            ops.unFollow(user);
            await page.waitFor(15000 + Math.floor(Math.random() * 5000));
        } else {
            console.log('---> archive ' + user);
            ops.unFollow(user);
        }
    }
}

Final steps and running your Instagram Bot

We now have a working Instagram Bot. However, if we execute our code, the Bot will only run once. If we want our Bot to run a couple of times a day we can set an interval based on our run_every_x_hours setting in the config. Replace the code in index.js with the following to do so.

let bot = require('./src/puppeteer');
let cnf = require('./config/config.json');

bot();
setInterval(bot, cnf.settings.run_every_x_hours * 3600000);

That's it. I hope you succeeded in getting your own bot up and running. And, if you run into any issue, leave a comment and I might be able to help you out.

Nicolas Lierman

Nicolas Lierman

The original glamdevhero. Digital Innovation Architect at MultiMinds and interested in all things analytics, data and web development.

Read More
How to create your own Instagram Bot with Node.js and Puppeteer
Share this

Subscribe and get the good stuff