前言
本文主要記錄我開發(fā)一個npm包:pixiv-login
時的心得體會,其中穿插了一些日常開發(fā)的流程和技巧奔垦,希望對新手有所啟發(fā),大佬們看看就好_(:3」∠)
pixiv-login
pixiv-login
的功能就是模擬用戶登錄網(wǎng)站pixiv尸疆,獲取cookie
源碼 npm
安裝:
npm install --save pixiv-login
使用:
const pixivLogin = require('pixiv-login');
pixivLogin({
username: '你的用戶名',
password: '你的密碼'
}).then((cookie) => {
console.log(cookie);
}).catch((error) => {
console.log(error);
})
開發(fā)工具
日常開發(fā)中椿猎,我常用的IDE是vscode+webstorm+sublime,其中vscode因為其啟動快寿弱,功能多犯眠,調(diào)試方便,受到大多數(shù)開發(fā)者的青睞症革。在接下來的教程中筐咧,我就以vscode進(jìn)行演示了。至于終端噪矛,由于是在windows平臺量蕊,所以我選擇了cmder代替原生cmd,畢竟cmder支持大多數(shù)linux命令艇挨。
初始化項目
mkdir pixiv-login
cd pixiv-login
npm init
一路回車就好
安裝依賴
要模擬登陸残炮,我們就需要一個http庫,這里我選擇了axios缩滨,同時獲取的html字符串我們需要解析势就,cheerio就是首選了
npm i axios cheerio --save
debug
畢業(yè)參加工作也有幾個月了,其中學(xué)到了很重要的一個技能就是debug脉漏。說出來也不怕大家笑苞冯,在大學(xué)時,debug就是console.log
大法好侧巨,基本就不用斷點來追蹤舅锄,其實用好斷點,效率比console.log
要高很多刃泡。還記得當(dāng)初看到同事花式debug時巧娱,心中不禁感慨:為什么你們會這么熟練啊烘贴!
使用vscode進(jìn)行node調(diào)試是非常方便的
首先新建一個index.js文件禁添,項目結(jié)構(gòu)如下(我本地的npm版本是5.x,所以會多一個package-lock.json文件桨踪,npm版本3.x的沒有該文件):
然后點擊左側(cè)第4個圖標(biāo)老翘,添加配置
配置文件如下:
{
// 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"
}
]
}
其中最重要的一句是:"program": "${workspaceRoot}\\index.js"
,它表示debug時,項目的啟動文件是index.js
至此铺峭,debug的配置就完成了墓怀。
現(xiàn)在編寫index.js文件
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);
});
按下F5啟動調(diào)試模式,如果一切正常卫键,那么效果如下:
可以看到傀履,程序卡在了第8行
如果你把鼠標(biāo)移到response變量上,可以發(fā)現(xiàn)莉炉,vscode會自動顯示該變量的值钓账,這比直接console.log(response)
清晰簡潔多了
如果想繼續(xù)執(zhí)行程序,可以接著按下F5或者右上角的綠色箭頭
程序執(zhí)行完成絮宁,控制臺打出了pixiv首頁的title值
除了使用debugger
語句打斷點梆暮,你也可以直接點擊代碼的行數(shù)打斷點
比如上圖,我就在第8行處打了一個斷點绍昂,效果是一樣的
還有一個小技巧啦粹,在debug模式下,你可以隨意修改變量的值窘游,比如現(xiàn)在程序卡在了第8行唠椭,這時你在控制臺修改title的值
按下回車,然后繼續(xù)執(zhí)行代碼张峰,這時控制臺輸出的title值就是'deepred'泪蔫,而不是真正的title值
這個技巧,在平時開發(fā)過程中喘批,當(dāng)需要繞過某些驗證時,非常有用
正式開始
雖然我們最后是要寫一個npm包铣揉,但是首先饶深,我們先把獲取cookie的功能實現(xiàn)了,然后再思考怎么封裝為一個npm包逛拱,供其他人使用敌厘。
進(jìn)入登錄頁面 https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index,我們先登錄一次朽合,看看前端向后臺發(fā)送了哪些數(shù)據(jù)
這里需要特別注意俱两,我們要勾選preserve log
,這樣曹步,即使頁面刷新跳轉(zhuǎn)了宪彩,http請求記錄仍然會記錄下來
可以看到,post_key是登錄的關(guān)鍵點讲婚,P站使用了該值來防止CSRF
post_key怎么獲取呢尿孔?
經(jīng)過頁面分析,發(fā)現(xiàn)在登錄頁面,有個隱藏表單域(后來發(fā)現(xiàn)活合,其實在首頁就已經(jīng)寫出來了):
可以清楚看到雏婶,post_key已經(jīng)寫出來了,我們只需要用cheerio
解析出該input的值就ok了
const post_key = $('input[name="post_key"]').val();
獲取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;
})
F5運行代碼
注意:打開注冊頁時白指,注冊頁會返回一些cookie留晚,這些cookie在登錄時也是需要隨密碼,用戶名一起發(fā)送過去的
獲取到了post_key, cookie告嘲,我們就可以愉快的把登錄數(shù)據(jù)發(fā)送給后臺接口了
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: '你的用戶名',
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);
});
});
注意其中這段代碼:
data: querystring.stringify({
pixiv_id: '你的用戶名',
password: '你的密碼',
captcha: '',
g_recaptcha_response: '',
post_key: post_key,
source: 'pc',
ref: 'wwwtop_accounts_index',
return_to: 'http://www.pixiv.net/'
})
這里有個巨大的坑错维,axios
默認(rèn)把數(shù)據(jù)轉(zhuǎn)成json格式,如果你想發(fā)送application/x-www-form-urlencoded
的數(shù)據(jù)状蜗,就需要使用querystring
模塊
詳情見: using-applicationx-www-form-urlencoded-format
如果一切正常需五,那么效果如下:
其中的PHPSESSID和device_token就是服務(wù)器端返回的登錄標(biāo)識,說明我們登錄成功了
程序運行的同時轧坎,你也很可能收到P站的登錄郵件
好了宏邮,目前為止,我們已經(jīng)成功獲取到了cookie缸血,實現(xiàn)了最基本的功能蜜氨。
特別注意
程序不要運行太多次,因為每次運行捎泻,你就登錄一次P站飒炎,如果被P站監(jiān)測到頻繁登錄,它會開啟驗證碼模式笆豁,這時郎汪,你除了需要發(fā)送用戶名和密碼,還需要向后臺發(fā)送驗證碼值
data: querystring.stringify({
pixiv_id: '你的用戶名',
password: '你的密碼',
captcha: '你還需要填驗證碼',
g_recaptcha_response: '',
post_key: post_key,
source: 'pc',
ref: 'wwwtop_accounts_index',
return_to: 'http://www.pixiv.net/'
})
也就是闯狱,captcha
字段不再是空值了煞赢!
基本功能的完整代碼
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: '你的用戶名',
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);
});
});
封裝成一個npm包
登錄P站獲取cookie
這個功能,如果我們想讓其他開發(fā)者也能方便調(diào)用哄孤,就可以考慮將其封裝為一個npm包發(fā)布出去照筑,這也算是對開源社區(qū)做出自己的一份貢獻(xiàn)。
首先我們回想一下瘦陈,我們調(diào)用其他npm包時是怎么做的凝危?
const cheerio = require('cheerio');
const $ = cheerio.load(response.data);
同理,我們現(xiàn)在規(guī)定pixiv-login
的用法:
const pixivLogin = require('pixiv-login');
pixivLogin({
username: '你的用戶名',
password: '你的密碼'
}).then((cookie) => {
console.log(cookie);
}).catch((error) => {
console.log(error);
})
pixiv-login
對外暴露一個函數(shù)晨逝,該函數(shù)接受一個配置對象蛾默,里面記錄了用戶名和密碼
現(xiàn)在,我們來改造index.js
const pixivLogin = ({ username, password }) => {
};
module.exports = pixivLogin;
最基本的骨架就是定義一個函數(shù)咏花,然后把該函數(shù)導(dǎo)出
由于我們需要支持Promise寫法趴生,所以導(dǎo)出的pixivLogin
本身要返回一個Promise
const pixivLogin = ({ username, password }) => {
return new Promise((resolve, reject) => {
})
};
之后阀趴,只要把原先的代碼套進(jìn)去就好了
完整代碼:
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;
發(fā)布npm包
README
每個npm包,一般都需要配一段介紹文字苍匆,來告訴使用者如何安裝使用刘急,比如lodash的首頁
新建一個README.md,填寫相關(guān)信息
有時浸踩,我們會看到一些npm包有很漂亮的版本號圖標(biāo):
這些圖標(biāo)叔汁,其實可以在https://shields.io/ 上制作
登錄該網(wǎng)站,下拉到最下面
輸入你想要的文字检碗,版本號据块,顏色, 然后點擊按鈕
就可以得到圖片的訪問地址了
修改剛才的README.md折剃,加上我們的版本號吧!
gitignore
我們現(xiàn)在的文件夾目錄應(yīng)該如下所示:
其實node_modules以及.vscode是完全不用上傳的另假,所以為了防止發(fā)布時帶上這些文件夾,我們要新建一個.gitignore
.vscode/
node_modules/
注冊
到 https://www.npmjs.com/ 上注冊一個賬號
然后在終端輸入
npm adduser
輸入用戶名怕犁,密碼边篮,郵箱即可登入成功
這里還有一個坑!
如果你的npm使用的是淘寶鏡像奏甫,那么是無法登陸成功的
最簡單的解決方法:
npm i nrm -g
nrm use npm
nrm
是個npm鏡像管理工具戈轿,可以很方便的切換鏡像源
登陸成功后,輸入
npm whoami
如果出現(xiàn)了你的用戶名阵子,說明你已經(jīng)成功登陸了
發(fā)布
特別注意:
因為pixiv-login
這個名字已經(jīng)被我占用了思杯,所以你需要改成其他名字
修改pacakge.json文件的name
字段
npm publish
即可發(fā)布成功啦!
下載
發(fā)布成功后挠进,我們就可以下載自己的包了
npm i pixiv-login
使用pixiv-login包
我們可以用pixiv-login
做一些有趣(♂)的事
比如:
下載 R-18每周排行榜的圖片
沒登錄的用戶是無法訪問R18區(qū)的色乾,所以我們需要模擬登陸
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: '你的用戶名',
password: '你的密碼'
}).then((cookie) => {
// 把cookie寫入文件中,則下次無需再次獲取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}下載完成`);
});;
});
})
})
同時杈湾,我們的pixiv-login
是支持async await
的!
const pixivStart = async () => {
try {
const cookie = await pixivLogin({
username: '你的用戶名',
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}下載完成`);
});;
} catch (err) {
console.log(err)
}
};
pixivStart();