Skip to content

Commit

Permalink
add login option and exports cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
zzbazza committed Sep 26, 2020
1 parent 9284062 commit 1692cf1
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 2 deletions.
17 changes: 15 additions & 2 deletions INPUT_SCHEMA.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@
"type": "string",
"description": "What to scrape from each Instagram page URL or search result. You need to provide correct parent pages, e.g. comments can be scraped only from posts, not from profiles.",
"editor": "select",
"enum": ["posts", "comments", "details", "stories"],
"enum": ["posts", "comments", "details", "stories", "cookies"],
"enumTitles": [
"Posts (from profile pages, hashtag or place search)",
"Comments (from post pages)",
"Details (of profiles, posts or search pages - from a profile, post or search pages)",
"Stories"],
"Stories",
"Cookies (will export cookies gained by login using IG credentials - loginUsername, loginPassword, these cookies can be used in later runs for authorization)"],
"default": "posts"
},
"resultsLimit": {
Expand Down Expand Up @@ -92,6 +93,18 @@
"sectionCaption": "Login using cookies",
"sectionDescription": "Instagram limits access to certain data (likes, post user data, followers and others) if a user is anonymous. You can provide your cookies here (copied from chrome plugin like 'EditThisCookie' to use a normal account. Do not use your own cookies here and rather create a new temporary account. When cookies are used, the run is limited to concurrency 1 and one session, so that the account does not get banned too soon."
},
"loginUsername": {
"title": "Login Username",
"type": "string",
"description": "Login for IG account. You will have to fill in code, that you receive to email connected to IG account.",
"editor": "textfield"
},
"loginPassword": {
"title": "Login Password",
"type": "string",
"description": "Password for IG account. You will have to fill in code, that you receive to email connected to IG account.",
"editor": "textfield"
},
"likedByLimit": {
"title": "Get post likes",
"type": "integer",
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@types/underscore": "^1.10.0",
"apify": "^0.20.2",
"got": "^9.6.0",
"http": "0.0.1-security",
"request-promise-native": "^1.0.8",
"safe-eval": "^0.4.1",
"tough-cookie": "^3.0.1",
Expand Down
30 changes: 30 additions & 0 deletions src/asci-texts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports.pleaseOpen = `
_____ _
| __ \\| |
| |__) | | ___ __ _ ___ ___ ___ _ __ ___ _ __
| ___/| |/ _ \\/ _\` / __|/ _ \\ / _ \\| '_ \\ / _ \\ '_ \\
| | | | __/ (_| \\__ \\ __/ | (_) | |_) | __/ | | |
|_| |_|\\___|\\__,_|___/\\___| \\___/| .__/ \\___|_| |_|
| |
|_|
`

module.exports.liveView = `
_ _ _
| (_) (_)
| |___ _____ __ ___ _____ __
| | \\ \\ / / _ \\ \\ \\ / / |/ _ \\ \\ /\\ / /
| | |\\ V / __/ \\ V /| | __/\\ V V /
|_|_| \\_/ \\___| \\_/ |_|\\___| \\_/\\_/
`

module.exports.localhost = `
_ _ _ _ ____ ___ ___ ___
| | | | | | | _|___ \\ / _ \\ / _ \\ / _ \\
| | ___ ___ __ _| | |__ ___ ___| |_(_) __) | | | | | | | | | |
| |/ _ \\ / __/ _\` | | '_ \\ / _ \\/ __| __| |__ <| | | | | | | | | |
| | (_) | (_| (_| | | | | | (_) \\__ \\ |_ _ ___) | |_| | |_| | |_| |
|_|\\___/ \\___\\__,_|_|_| |_|\\___/|___/\\__(_)____/ \\___/ \\___/ \\___/
`
1 change: 1 addition & 0 deletions src/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
COMMENTS: 'comments',
DETAILS: 'details',
STORIES: 'stories',
COOKIES: 'cookies',
},
// Types of search queries available in instagram search
SEARCH_TYPES: {
Expand Down
1 change: 1 addition & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ module.exports = {
searchTypeIsRequired: () => new Error(`When "query" parameter is provided, searchType parameter must be one of "${Object.values(SEARCH_TYPES).join('", "')}"`),
unsupportedSearchType: type => new Error(`Type "${type}" is not supported. Allowed types are "${Object.values(SEARCH_TYPES).join('", "')}"`),
notPostPage: () => new Error('Comments can only be loaded from posts detail page.'),
credentialsRequired: () => new Error('You need to provide login credentials.'),
};
8 changes: 8 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const { getItemSpec, parseExtendOutputFunction, getPageTypeFromUrl } = require('
const { GRAPHQL_ENDPOINT, ABORT_RESOURCE_TYPES, ABORT_RESOURCE_URL_INCLUDES, ABORT_RESOURCE_URL_DOWNLOAD_JS, SCRAPE_TYPES, PAGE_TYPES } = require('./consts');
const { initQueryIds } = require('./query_ids');
const errors = require('./errors');
const { login } = require('./login');

async function main() {
const input = await Apify.getInput();
Expand Down Expand Up @@ -57,6 +58,7 @@ async function main() {
if (!proxy) throw errors.proxyIsRequired();
if (!resultsType) throw errors.typeIsRequired();
if (!Object.values(SCRAPE_TYPES).includes(resultsType)) throw errors.unsupportedType(resultsType);
if (SCRAPE_TYPES.COOKIES === resultsType && (!input.loginUsername || !input.loginPassword) ) throw errors.credentialsRequired();
} catch (error) {
Apify.utils.log.info('-- -- -- -- --');
Apify.utils.log.info(' ');
Expand Down Expand Up @@ -109,6 +111,10 @@ async function main() {
} else if (input.loginUsername && input.loginPassword) {
await login(input.loginUsername, input.loginPassword, page)
cookies = await page.cookies();
if (SCRAPE_TYPES.COOKIES === resultsType) {
await Apify.pushData(cookies);
return;
}
}

// TODO: Refactor to use https://sdk.apify.com/docs/api/puppeteer#puppeteerblockrequestspage-options
Expand Down Expand Up @@ -196,6 +202,8 @@ async function main() {
};

const handlePageFunction = async ({ page, puppeteerPool, request, response }) => {
if (SCRAPE_TYPES.COOKIES === resultsType) return;

if (response.status() === 404) {
Apify.utils.log.error(`Page "${request.url}" does not exist.`);
return;
Expand Down
90 changes: 90 additions & 0 deletions src/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const Apify = require('apify');
const { pleaseOpen, liveView, localhost } = require('./asci-texts.js');
const { authorize, close } = require('./submit-page.js');
const http = require('http');

/**
* Attempts log user into instagram with provided username and password
* @param {String} username Username to use during login (can also be an email or telephone)
* @param {String} password Password to use during login
* @param {Object} page Puppeteer Page object
* @return Does not return anything
*/
const login = async (username, password, page) => {
await Apify.utils.log.info(`Attempting to log in`);

try {
await page.goto('https://www.instagram.com/accounts/login/?source=auth_switcher');
await page.waitForSelector('input[name="username"]');
await page.waitForSelector('input[name="password"]');
await page.waitForSelector('button[type="submit"]');
await Apify.utils.sleep(1000);

await page.type('input[name="username"]', username, { delay: 150 });
await page.type('input[name="password"]', password, { delay: 180 });
await Apify.utils.sleep(1000);

await page.click('button[type="submit"]');

await page.waitForNavigation();
await Apify.utils.sleep(1000);

await page.waitForSelector('form button');
await page.click('form button');

// Wait fo code sent to email
const port = Apify.isAtHome() ? process.env.APIFY_CONTAINER_PORT : 3000;
const information = Apify.isAtHome() ? liveView : localhost;

console.log(pleaseOpen);
console.log(information);
let code;

const server = http.createServer((req, res) => {
if (req.url.includes('/authorize')) {
let data = '';
req.on('data', (body) => {
if (body) data += body;
});
req.on('end', () => {
code = decodeURIComponent(data.replace('code=', ''));
res.end(close());
});
} else {
res.end(authorize());
}
});

server.listen(port, () => console.log('server is listening on port', port));

const start = Date.now();
while (!code) {
const now = Date.now();
if (now - start > 5 * 60 * 1000) {
throw new Error('You did not provide the code in time!');
}
console.log(`waiting for code...You have ${300 - Math.floor((now - start) / 1000)} seconds left`);
await new Promise((resolve) => setTimeout(resolve, 10000));
}
server.close(() => console.log('closing server'));

await page.waitForSelector('input[name="security_code"]');
await page.waitForSelector('form button');
await page.type('input[name="security_code"]', code, { delay: 150 });
await Apify.utils.sleep(1000);
await page.click('form button');

await page.waitForNavigation();

await Apify.utils.log.info(`Successfully logged in`);
await Apify.utils.sleep(3000);
} catch (error) {
await Apify.utils.log.info('Failed to log in');
await Apify.utils.log.error(error);
process.exit(1);
}
}

module.exports = {
login,
};
30 changes: 30 additions & 0 deletions src/submit-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const pCss = `color: #9fa5a9; line-height: 20px!important; text-align: center; font-family: Graphik,sans-serif;`
const inputCss = `padding-left: 40px; cursor: pointer; font-size: 13px;font-weight: 700;color: #fff;background-color: #5cb85c;border-color: #4cae4c;text-align: center;vertical-align: middle;touch-action: manipulation;padding: 10px 20px;border-radius: 3px;line-height: 1.42857; border: 1px solid transparent;`

module.exports.authorize = () =>
`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Apify Instagram authorization</title>
</head>
<body>
<p style="${pCss} margin-top: 200px;"> Please fill in code you received to email connected to provided username.</p>
<form method="POST" action=/authorize style="text-align: center; margin-top: 50px">
<input placeholder= "your code" name="code" id="code" style="font-size: 14px;font-family: monospace,serif;color: #11181c; border-radius: 3px; border: 1px solid #ccc; padding: 10px 15px; width: 400px; height: 20px; margin: auto"/>
<input type="submit" style="${inputCss}"/>
</form>
</body>
</html>`

module.exports.close = () =>
`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Apify Instagram authorization</title>
</head>
<body>
<p style="${pCss} margin-top: 200px;"> You are now authorized, your actor should finish in seconds. </p>
</body>
</html>`

0 comments on commit 1692cf1

Please sign in to comment.