Actor

drobnikj/linkedin-sign-in-example

  • Builds
  • latest 0.0.21 / 2017-11-30
  • Created 2017-11-13
  • Last modified 2017-11-30
  • grade 16

Description

This act shows how you can sign-in to LinkedIn with Apify act with puppeteer image. It is only example how to do it. Usage: 1. Copy&paste this code to your act. 2. Set up email forwarding for user, that use for sign-in: FROM: security-noreply@linkedin.com FORWARD TO: 8mxmzpus@robot.zapier.com With this email settings, all your emails with verification codes will be redirect to mail parser and it will set verification code to the running act. 3. Implement your code with puppeteer in act, start from comment '// Crawl linkedin as logged user'. NOTE: Act can't solve captcha at this moment. You can avoid of getting captcha, when you sign-in maximum five times per day with single user.


API

To run the actor, send a HTTP POST request to:

https://api.apify.com/v2/acts/drobnikj~linkedin-sign-in-example/runs?token=<YOUR_API_TOKEN>

The POST payload will be passed as input for the actor. For more information, read the docs.


Example input

Content type: application/json; charset=utf-8

{
    "user": "user@domain.cz",
    "pwd": "password",
    "proxyUrl": "http://user:pwd@191.96.252.4:80"
}

Source code

Based on the apify/actor-node-puppeteer Docker image (see docs).

const Apify = require('apify');

const humanDelay = ms => (Math.random() + 1) * ms;

const saveScreen = async (page, key) => {
    const screenshotBuffer = await page.screenshot();
    await Apify.setValue(key, screenshotBuffer, { contentType: 'image/png' });
};

/**
 * Use this class for sign in to linkedin
 * NOTE: linkedin sometimes can verified user with pin, which is sent to user email. In this case
 * forward email to 8mxmzpus@robot.zapier.com. It is email parser, which send pin direct to act.
 * usage: const browser = await Apify.launchPuppeteer({ proxyUrl: proxy.href, headless: true });
 * const page = await browser.newPage(); const linkedinUtils = new LinkedinUtils(page, user, pwd)
 * linkedinUtils.login();
 */
class LinkedinUtils {
    /**
     * @param page - instance of puppeteer page object
     * @param user - linkedin user
     * @param pwd - linkedin user password
     */
    constructor(page, user, pwd, debugMode = false) {
        this.page = page;
        this.user = user;
        this.pwd = pwd;
        this.loginPageUrl = 'https://www.linkedin.com/';
        this.debugMode = debugMode;
        this.signInCount = 0;
    }

    async login() {
        console.log('Sign in ...');
        if (this.signInCount > 5) {
            throw new Error('Failed to sign in, check user and password');
            return;
        }
        await this.page.goto(this.loginPageUrl);
        const userInput = await this.page.$('#login-email');
        await userInput.type(this.user, humanDelay(100));
        const pwdInput = await this.page.$('#login-password');
        await pwdInput.type(this.pwd, { delay: humanDelay(100) });
        await this.page.click('#login-submit', { delay: humanDelay(100) });
        await new Promise(resolve => setTimeout(resolve, humanDelay(5000)));

        // Page can contain verification code
        const isVerifCode = await this.isVerifCode();
        if (isVerifCode) await this.fillVerifCode();

        // Check if we signed in
        const isLogged = await this.isAfterLoginPage();
        if (!isLogged) {
            //noCaptchaIframe
            const isCaptcha = await this.isCaptcha();
            if (isCaptcha) await this.solveCaptcha();
            const isPhoneForm = await this.isPhoneForm();
            if (isPhoneForm) await this.page.click('button.secondary-action', { delay: humanDelay(100) });
            await saveScreen(this.page, 'failed-sign-in');
            console.log('Sign in again ...');
            this.signInCount++;
            return this.login();
        }
    }

    async isAfterLoginPage() {
        const profileButton = !!await this.page.$('.nav-item--profile');
        return profileButton;
    }

    async isVerifCode() {
        const isVerifCode = !!await this.page.$('#verification-code');
        return isVerifCode;
    }

    async isCaptcha() {
        const isCaptcha = !!await this.page.$('#noCaptchaIframe');
        return isCaptcha;
    }

    async isPhoneForm() {
        const isPhoneForm = !!await this.page.$('input#phone-number');
        return isPhoneForm;
    }

    async solveCaptcha() {
        console.log('Star solving captcha...');
        throw new Error('We found captcha!');
    }

    async fillVerifCode() {
        const triedPins = [];
        const startedAt = new Date();
        while (true) {
            const isVerified = await this.isVerifCode();
            if (!isVerified) break;
            // check timeout
            if ((new Date() - startedAt) > 10 * 60 * 1000) {
                console.log('Waiting for verification code timeouts');
                break;
            }
            console.log('Verification code is needed');
            if (this.debugMode) await saveScreen(this.page, 'verifCodeForm');
            // Pin code with key email
            const object = await Apify.client.keyValueStores.getRecord({
                storeId: 'Zu2366Fzvw66gMjEk',
                key: this.user.replace('@', '(at)'),
            });
            const emailPin = (object && object.body) ? object.body: '';
            // Pin code without key email
            // NOTE: sometimes we can recognise email from email with pin and we save pin with nomail key
            const objectWithoutMail = await Apify.client.keyValueStores.getRecord({
                storeId: 'Zu2366Fzvw66gMjEk',
                key: 'nomail',
            });
            const noEmailPin = (objectWithoutMail && objectWithoutMail.body) ? objectWithoutMail.body : '';

            const tryPin = (!emailPin || triedPins.includes(emailPin)) ? noEmailPin : emailPin;
            console.log(`DEBUG: emailPin: ${emailPin}, noEmailPin: ${noEmailPin}, tryPin: ${tryPin}`);
            if (tryPin) {
                if (triedPins.includes(tryPin)) {
                    console.log(`We tried this ${tryPin} pin yet`);
                } else if (tryPin === 'newCode') {
                    console.log('Pin is newCode we clicked on reset pin button');
                    await this.page.click('#resendCodeATOPinChallenge');
                } else {
                    console.log(`Try fill pin from store: ${tryPin}`);
                    const codeInput = await this.page.$('#verification-code');
                    // clean exist value TODO: Find better approach
                    await this.page.$eval('#verification-code', (el) => {
                        el.value = '';
                    });
                    await codeInput.type(tryPin, { delay: humanDelay(100) });
                    await this.page.click('form[name="ATOPinChallengeForm"] input[name="signin"]', { delay: humanDelay(100) });
                    triedPins.push(tryPin);
                }
            } else {
                console.log('Pin is not in kvs');
            }
            await new Promise(resolve => setTimeout(resolve, humanDelay(8000)));
        }
    }
}

Apify.main(async () => {
    const input = await Apify.getValue('INPUT');

    const user = input.user || process.env.USER;
    const pwd = input.pwd || process.env.PWD;
    const proxyUrl = input.proxyUrl || process.env.PROXY_URL;

    console.log('Launching Puppeteer...');
    const opts = {
        args: [
            '--disable-web-security',
        ],
    };
    if (proxyUrl) opts.proxyUrl = input.proxyUrl;

    const browser = await Apify.launchPuppeteer(opts);

    const page = await browser.newPage();
    await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko');

    const linkedinUtils = new LinkedinUtils(page, user, pwd);
    await linkedinUtils.login();

    // Crawl linkedin as logged user
    await page.goto('https://www.linkedin.com/mynetwork/');
    await new Promise(resolve => setTimeout(resolve, humanDelay(8000)));
    const networkCount = await page.$eval('h3.mn-connections-summary__count', el => el.innerText);
    console.log(`You have ${networkCount} people in your linkedin network.`);

    console.log('Closing Puppeteer...');

    await browser.close();

    await Apify.setValue('OUTPUT', { networkCount });

    console.log('Done.');
});