在這個系列的「IT 鐵人排程發文」這篇文章底下,
pilipala 提到想知道如何用 request 來做登入的動作,那我們今天就來實作 iThelp 的登入吧。
在我們登出後,若要登入 iThelp,那麼就會被導向 https://member.ithome.com.tw/login ,接著我們輸入帳號密碼,按登入後會出現一個「登入中...」的畫面,然後畫面會被帶向 https://ithelp.ithome.com.tw/ ,看看右上角有出現登入者的名稱。
接著我們來觀察一下 request,首先會有個 login 的 post request,裡面帶著我們的帳號密碼,另外還有一組 _token
,而這組 _token
肯定是在登入的頁面裡面所產生的,我們檢查一下,在一個 hidden input 中找的到 _token
。
接著會有一個 oauth 的 get request,雖然看不到 response,但我們可以直接在新的 tab 打開,確認這個頁面其實就是顯示著「登入中...」這個頁面。觀察一下 html,這裡面的 javascript 會將我們在導向 https://ithelp.ithome.com.tw/users/callback ,同時 query string 會帶一個 code。
接著我們就跟著 js 的轉址,送出了 callback request,然後就會看到 response 有 Set-Cookie 將驗證登入的 cookie 傳給我們了,如此一來,就能完成登入並且將我們在導向 iThome 首頁。
在探訪的過程中,我們也發現到,登入的網域其實和 iThelp 首頁的網域是不同的,網域不同 cookie 就不同,而 iThelp 使用了最常見的 oAuth 的機制來串連起兩邊的登入狀態。
要確保執行登入,那麼會拆解成以下四個步驟:
_token
_token
這部分我們直接 request https://member.ithome.com.tw/login 然後將 _token
select 出來就可以了。
我們使用 postman 並帶上剛剛的取得 token 的 cookie 來測試一下,對 login post 我們的帳號密碼,然後再去 request https://member.ithome.com.tw//oauth/authorize?client_id=ithelp&redirect_uri=https%3A%2F%2Fithelp.ithome.com.tw%2Fusers%2Fcallback&response_type=code ,然後觀察一下裡面的 js redirect,裡面有包含 oAuth code 的 url,那就沒問題了。
接著我們模擬 帶上 code 的 oAuth request,然後檢查一下 response,看到有 Set-Cookie 就表示有網站有受理這個 request。
最後我們 request 一下首頁,然後檢查看看 .menu__account
裡面有出現帳號名稱,那麼就確保登入完成了。
首先我們先來製作 getToken function,這部分只需要一個 get request 然後在 select 出 token,接著再丟給 callback 就行了。
function getToken(callback){
request('https://member.ithome.com.tw/login', (err, res, body)=>{
var $ = cheerio.load(body)
var token = $('input[name=_token]').val()
callback(token)
})
}
接著我們來製作 login request,只需要一個 post request,同時帶上帳號、密碼、token 就能夠順利送出。
function login(token, callback){
var options = {
url: 'https://member.ithome.com.tw/login',
method: 'POST',
form: {
_token: token,
account: 'account',
password: 'password',
}
}
request(options, (err, res, body)=>{
callback()
})
}
接著我們來製作 oAuth 的動作,先發出一個 get request 去取得 response 中 javascript 的 code
,然後在帶上 code
去呼叫 callback url,這樣就完成了。
function oauth(callback){
request('https://member.ithome.com.tw//oauth/authorize?client_id=ithelp&redirect_uri=https%3A%2F%2Fithelp.ithome.com.tw%2Fusers%2Fcallback&response_type=code', (err, res, body)=>{
request('https://ithelp.ithome.com.tw/users/callback?code=' + body.match(/code=(.+)'/)[1], (err, res, body)=>{
callback()
})
})
}
最後我們來檢查是否已經成功登入,只需要 request 首頁,然後將 .menu__account
的內容丟給 callback 就完成了。
然後我們將這四個 function 組合起來,因為都是 async function,所以就出現傳說中的 callback hell,其實這個用 promise 來做會更適合。
getToken((token)=>{
login(token, ()=>{
oauth(()=>{
checkLoginStatus((result)=>{
console.log(result);
})
})
});
})
const request = require('request').defaults({jar: true});
const cheerio = require('cheerio');
getToken((token)=>{
login(token, ()=>{
oauth(()=>{
checkLoginStatus((result)=>{
console.log(result);
})
})
});
})
function getToken(callback){
request('https://member.ithome.com.tw/login', (err, res, body)=>{
var $ = cheerio.load(body)
var token = $('input[name=_token]').val()
callback(token)
})
}
function login(token, callback){
var options = {
url: 'https://member.ithome.com.tw/login',
method: 'POST',
form: {
_token: token,
account: 'account',
password: 'password',
}
}
request(options, (err, res, body)=>{
callback()
})
}
function oauth(callback){
request('https://member.ithome.com.tw//oauth/authorize?client_id=ithelp&redirect_uri=https%3A%2F%2Fithelp.ithome.com.tw%2Fusers%2Fcallback&response_type=code', (err, res, body)=>{
request('https://ithelp.ithome.com.tw/users/callback?code=' + body.match(/code=(.+)'/)[1], (err, res, body)=>{
callback()
})
})
}
function checkLoginStatus(callback){
request('https://ithelp.ithome.com.tw/', (err, res, body)=>{
var $ = cheerio.load(body)
callback($('.menu__account').text())
})
}
現行有許多網站是透過 fb 或 google 登入,而這些也就是 oAuth 的應用。常常我們在玩爬蟲的時候也都需要去做登入驗證,一定都會碰到 oAuth 的狀況,透過這次的 case,我們大概可以了解到,通常 oAuth 不是去要 token 就是去要 code,有時候是直接 token 用到底,有些時候他就會塞 cookie 給你,換成 cookie 驗證,不過這一切都能透過觀察和探訪取得這些資訊。
form: {
my[email]: 'account@emal.com',
my[password]: 'password',
}
請教request時若form的欄位名與欄位值有特殊符號時要怎處理(如上例[]@)?
謝謝!
每個 server 所接收和 encode 的方式可能不同,最正確的做法,是去看 source,看他到底是送了什麼出去。另外也能看 curl,同時測試看看。
node在中括號那就直接報錯了
我會了,欄位名加""就好了!