preface

This article mainly records my experience of developing a NPM package: PIxiv-login, which interweaves with some daily development processes and skills, hoping to inspire the novice, big guys have a look.

pixiv-login

The function of pixiv-login is to simulate a user to login to the website pixiv and obtain cookies

The source code

npm

Installation:

npm install --save pixiv-login
Copy the code

Use:

const pixivLogin = require('pixiv-login'); PixivLogin ({username: 'your username ', password:' your password '}). Then ((cookie) => {console.log(cookie); }).catch((error) => { console.log(error); });Copy the code

The development tools

In daily development, I often use VScode + WebStorm +sublime, among which VScode is favored by most developers because of its fast startup, multiple functions and convenient debugging. In the rest of the tutorial, I’ll use vscode to demonstrate this. As for the terminal, since it is a Windows platform, I chose CMder instead of the native CMD, since cmder supports most Linux commands.

Initialize the project

mkdir pixiv-login
cd pixiv-login
npm init
Copy the code

Just drive back to the car

Install dependencies

To simulate login, we’re going to need an HTTP library, so I’m going to choose Axios, and we’re going to parse the HTML string that we get, and Cheerio is the first choice

npm i axios cheerio --save
Copy the code

debug

It has been several months since I started working after graduation. One of the most important skills I have learned is debug. In college, debug is console.log. Breakpoints are usually not used for tracing. I still remember when I saw my colleagues’ fancy DEBUG, I couldn’t help feeling in my heart: why are you so skilled?

Node debugging with vscode is very convenient

First create a new index.js file with the following project structure (my local NPM version is 5.x, so there will be an extra package-lock.json file, NPM 3.x does not have this file) :

Then click the fourth icon on the left to add the configuration

The configuration file is as follows:

{ // Use IntelliSense to learn about possible Node.js debug attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "Configurations ": [{"type": "node", "request": "launch", "name": "launch Program"," Program" : "${workspaceRoot}\\index.js" }] }Copy the code

The most important of these is “program”: “${workspaceRoot}\\index.js”, which indicates that when debugging, the project’s startup file is index.js

At this point, the debug configuration is complete.

Now write the index.js file

const axios = require('axios');
const cheerio = require('cheerio');

axios.get('https://www.pixiv.net')
    .then(function(response) {
        const $ = cheerio.load(response.data);
        const title = $('title').text();
        debugger;
        console.log(title);
    })
    .catch(function(error) {
        console.log(error);
    });
Copy the code

Press F5 to start debug mode, and if everything works, it looks like this:

As you can see, the program is stuck on line 8

If you hover your mouse over the response variable, you can see that vscode automatically displays the value of the variable, which is much cleaner than going directly to console.log(response)


To continue, press F5 or the green arrow in the upper right corner

When the program is finished, the console prints the title value of the pixiv home page

In addition to using the debugger statement break point, you can also directly click the line count break point of the code

For example, in the image above, I hit a breakpoint at line 8, which has the same effect

Another trick is that in Debug mode, you can change the value of a variable as much as you want. For example, if the program is stuck on line 8, you can change the value of title on the console

Press Enter and continue executing the code, at which point the console outputs the title value as ‘deepred’ rather than the actual title value

This technique is useful when you need to bypass some validation during normal development

The official start of the

Although we will eventually write an NPM package, we will first implement the function of getting cookies, and then think about how to package it into an NPM package for others to use.

Enter the login page login page, let’s log in once to see what data the front end sends to the back end

In particular, we need to check ‘Preserve log’ so that the HTTP request is still logged even if the page refresh jumps



As you can see, post_key is the key point for logging in, and p station uses this value to prevent CSRF

How do I get post_key?

After page analysis, it was found that there was a hidden form field in the login page (later it was found that it had been written on the home page) :

We can clearly see that post_key is already written, and we just need to use the Cheerio to parse the input value

const post_key = $('input[name="post_key"]').val();
Copy the code

Get post_key

const axios = require('axios');
const cheerio = require('cheerio');

const LOGIN_URL = 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index';
const USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36';
const LOGIN_API = 'https://accounts.pixiv.net/api/login?lang=zh';


const getKey = axios({
    method: 'get',
    url: LOGIN_URL,
    headers: {
        'User-Agent': USER_AGENT
    }
}).then((response) => {
    const $ = cheerio.load(response.data);
    const post_key = $('input[name="post_key"]').val();
    const cookie = response.headers['set-cookie'].join('; ');
    if (post_key && cookie) {
        return { post_key, cookie };
    }
    return Promise.reject("no post_key");
}).catch((error) => {
    console.log(error);
});


getKey.then(({ post_key, cookie }) => {
    debugger;
})
Copy the code

F5 runs the code


Note: when opening the registration page, the registration page will return some cookies. These cookies also need to be sent along with the password and user name when logging in

Once we get the post_key, cookie, we can happily send the login data to the background interface

const querystring = require('querystring'); getKey.then(({ post_key, cookie }) => { axios({ method: 'post', url: LOGIN_API, headers: { 'User-Agent': USER_AGENT, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://accounts.pixiv.net', 'Referer': 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index', 'X-Requested-With': 'XMLHttpRequest', 'Cookie': Cookie}, data: queryString.stringify ({pixiv_id: 'your username ', password:' Your password ', captcha: '', g_recaptcha_response: '', post_key: post_key, source: 'pc', ref: 'wwwtop_accounts_index', return_to: 'http://www.pixiv.net/' }) }).then((response) => { if (response.headers['set-cookie']) { const cookie = response.headers['set-cookie'].join(' ; '); debugger; } else { return Promise.reject(new Error("no cookie")) } }).catch((error) => { console.log(error); }); });Copy the code

Notice this code:

Data: queryString.stringify ({pixiv_id: 'your username ', password:' your password ', captcha: ', g_RECAPtcha_response: ', post_key: post_key, source: 'pc', ref: 'wwwtop_accounts_index', return_to: 'http://www.pixiv.net/' })Copy the code

There is a huge pit here, axios converts data to JSON by default, and if you want to send Application/X-www-form-urlencoded data, you need to use the QueryString module

For details, see: using- Applicationx-www-form-urlencoded -format

If all is well, the effect is as follows:

PHPSESSID and Device_token are the login ids returned by the server, indicating that the login is successful

While the program is running, you are likely to receive a login email from the P site

Okay, so far, we’ve managed to get cookies, which is pretty basic.

Pay special attention to

Do not run the program too many times, because each time you run, you log in to P station, if P station detects frequent login, it will turn on the captcha mode, in this case, you need to send user name and password, also need to send the background captcha value

Data: queryString. stringify({pixiv_id: 'your username ', password:' your password ', captcha: 'You also need to fill in a captcode ', g_RECAPtcha_response: '', post_key: post_key, source: 'pc', ref: 'wwwtop_accounts_index', return_to: 'http://www.pixiv.net/' })Copy the code

The CAPTCHA field is no longer null!

Complete code for basic functions

const axios = require('axios'); const cheerio = require('cheerio'); const querystring = require('querystring'); const LOGIN_URL = 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'; Const USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'; const LOGIN_API = 'https://accounts.pixiv.net/api/login?lang=zh'; const getKey = axios({ method: 'get', url: LOGIN_URL, headers: { 'User-Agent': USER_AGENT } }).then((response) => { const $ = cheerio.load(response.data); const post_key = $('input[name="post_key"]').val(); const cookie = response.headers['set-cookie'].join('; '); if (post_key && cookie) { return { post_key, cookie }; } return Promise.reject("no post_key"); }).catch((error) => { console.log(error); }); getKey.then(({ post_key, cookie }) => { axios({ method: 'post', url: LOGIN_API, headers: { 'User-Agent': USER_AGENT, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://accounts.pixiv.net', 'Referer': 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index', 'X-Requested-With': 'XMLHttpRequest', 'Cookie': Cookie}, data: queryString.stringify ({pixiv_id: 'your username ', password:' Your password ', captcha: '', g_recaptcha_response: '', post_key: post_key, source: 'pc', ref: 'wwwtop_accounts_index', return_to: 'http://www.pixiv.net/' }) }).then((response) => { if (response.headers['set-cookie']) { const cookie = response.headers['set-cookie'].join(' ; '); console.log(cookie); } else { return Promise.reject(new Error("no cookie")); } }).catch((error) => { console.log(error); }); });Copy the code

Packaged as an NPM package

If we want to make it easy for other developers to call the function of logging in to P site to get cookies, we can consider packaging it as a NPM package and release it, which is also a contribution to the open source community.

First let’s recall what we did when we called other NPM packages.

const cheerio = require('cheerio');
const $ = cheerio.load(response.data);
Copy the code

Similarly, we now specify the use of piXiv-login:

const pixivLogin = require('pixiv-login'); PixivLogin ({username: 'your username ', password:' your password '}). Then ((cookie) => {console.log(cookie); }).catch((error) => { console.log(error); })Copy the code

Pixiv-login exposes a function that takes a configuration object that records the username and password

Now, let’s transform index.js

const pixivLogin = ({ username, password }) => {

};


module.exports = pixivLogin;
Copy the code

The basic skeleton is to define a function and then export that function

Since we need to support the Promise writing, the exported pixivLogin itself returns a Promise

const pixivLogin = ({ username, password }) => {
    return new Promise((resolve, reject) => {

    })
};
Copy the code

After that, you just plug in the original code

Complete code:

const axios = require('axios'); const cheerio = require('cheerio'); const querystring = require('querystring'); const LOGIN_URL = 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'; Const USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'; const LOGIN_API = 'https://accounts.pixiv.net/api/login?lang=zh'; const pixivLogin = ({ username, password }) => { return new Promise((resolve, reject) => { const getKey = axios({ method: 'get', url: LOGIN_URL, headers: { 'User-Agent': USER_AGENT } }).then((response) => { const $ = cheerio.load(response.data); const post_key = $('input[name="post_key"]').val(); const cookie = response.headers['set-cookie'].join('; '); if (post_key && cookie) { return { post_key, cookie }; } reject(new Error('no post_key')); }).catch((error) => { reject(error); }); getKey.then(({ post_key, cookie }) => { axios({ method: 'post', url: LOGIN_API, headers: { 'User-Agent': USER_AGENT, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'https://accounts.pixiv.net', 'Referer': 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index', 'X-Requested-With': 'XMLHttpRequest', 'Cookie': cookie }, data: querystring.stringify({ pixiv_id: username, password: password, captcha: '', g_recaptcha_response: '', post_key: post_key, source: 'pc', ref: 'wwwtop_accounts_index', return_to: 'http://www.pixiv.net/' }) }).then((response) => { if (response.headers['set-cookie']) { const cookie = response.headers['set-cookie'].join(' ; '); resolve(cookie); } else { reject(new Error('no cookie')); } }).catch((error) => { reject(error); }); }); }) } module.exports = pixivLogin;Copy the code

Release NPM package

README

Each NPM package usually comes with an introductory paragraph telling the user how to install it. For example, the lodash home page

Create a readme.md and fill in the relevant information

Sometimes, we see some NPM packages with nice version number ICONS:

These ICONS can actually be made at https://shields.io/

Log on to the site and scroll down to the bottom

Enter the text you want, version number, color, and click the button

You can get the access address of the image

Modify the readme. md and add our version number.

gitignore

Our current folder directory should look like this:

Node_modules and.vscode don’t need to be uploaded at all, so we’ll create a.gitignore to avoid Posting with these folders

.vscode/
node_modules/
Copy the code

registered

Register an account with NPMJS

Then enter it at the terminal

npm adduser
Copy the code

Enter the user name, password, email can login successfully

Here’s another pit!

If your NPM is using taobao mirror, it is unable to land successfully

The simplest solution:

npm i nrm -g
nrm use npm
Copy the code

NRM is an NPM image management tool that can easily switch image sources

After a successful login, enter

npm whoami
Copy the code

If your username appears, you have logged in successfully

release

Special attention:

Since the name piXiv-login is already occupied by me, you need to change it to another name

Modify the name field in pacakge.json

npm publish
Copy the code

You can publish successfully!

download

After a successful launch, we were able to download our own packages

npm i pixiv-login
Copy the code

Use pixiv – login package

We can do some interesting things with PiXiv-Login

Such as:

Download images of the R-18 weekly leaderboards

Users who are not logged in cannot access R18, so we need to simulate logging in

const fs = require('fs'); const axios = require('axios'); const pixivLogin = require('pixiv-login'); const cheerio = require('cheerio'); Const USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'; PixivLogin ({username: 'your username ', password: Fs.writefilesync ('cookie.txt', cookie); fs.writefilesync ('cookie.txt', cookie); }).then((response) => { const cookie = fs.readFileSync('cookie.txt', 'utf8'); axios({ method: 'get', url: 'https://www.pixiv.net/ranking.php?mode=weekly_r18', headers: { 'User-Agent': USER_AGENT, 'Referer': 'https://www.pixiv.net', 'Cookie': cookie }, }) .then(function(response) { const $ = cheerio.load(response.data); const src = $('#1 img').data('src'); return src; }).then(function(response) { axios({ method: 'get', url: response, responseType: 'stream' }) .then(function(response) { const url = response.config.url; const fileName = url.substring(url.lastIndexOf('/') + 1); Response.data.pipe (fs.createWritestream (fileName)).on('close', function() {console.log(' ${fileName} download completed '); });; }); })})Copy the code

Also, our piXiv-Login supports async await!

Const pixivStart = async() => {try {const cookie = await pixivLogin({username: 'your username ', password:' your password '}); fs.writeFileSync('cookie.txt', cookie); const data = fs.readFileSync('cookie.txt', 'utf8'); const response = await axios({ method: 'get', url: 'https://www.pixiv.net/ranking.php?mode=weekly_r18', headers: { 'User-Agent': USER_AGENT, 'Referer': 'https://www.pixiv.net', 'Cookie': cookie }, }); const $ = cheerio.load(response.data); const src = $('#1 img').data('src'); const pic = await axios({ method: 'get', url: src, responseType: 'stream' }); const fileName = pic.config.url.substring(pic.config.url.lastIndexOf('/') + 1); Pic.data.pipe (fs.createWritestream (fileName)).on('close', function() {console.log(' ${fileName} download completed '); });; } catch (err) { console.log(err) } }; pixivStart();Copy the code

reference

1. Log in to pixiv.net

2. Getting started with a Python crawler: Crawl pixiv

Blog Address:

Write an NPM package from scratch