在初學的爬蟲過程中,很多人還不知道有些字段是如何生成的,怎樣模擬生成這些字段來拼接頭部。為了再次紀念【宏彥獲水】成語初次面世,特地用【百度登陸】寫下一篇登陸百度的教程,以供大家參考。
前面學習了如何在 get
的時候想服務器發送多變的請求數據,從而達到搜索的效果,而實際上 搜索是簡單的登陸
!所以本文將要介紹如何向百度服務器發送 post
數據,從而達到模擬登陸百度的效果。
- 首先打開
firefox
瀏覽器,清除網頁所有的歷史紀錄,這是為了防止以前的Cookie
影響服務器返回的數據。F12
打開firebug
,進入百度首頁,點擊 網絡 -> 清除 ,這是為了刪掉打開百度首頁而彈出來的html
,方便后面的查找html
數據。- 點擊登陸按鈕,依次填寫賬號、密碼、驗證碼,點擊 登陸 ,在
firebug
中點擊保持
,這是為了防止登陸成功后,登陸表單的html
被清除。
在 firebug
中,找到如下一行 POST?login
:
點擊前面的 +
號 -> post
,可以看到提交的表單,這個就是點擊登陸后,網頁向百度服務器后端發送的 登陸請求表單,表單中包含了 賬號、密碼、其他 等信息:
如果百度后台認為此 登陸表單請求 是正確的后,會在 頭信息 -> 響應頭信息 中返回一個 Set-Cookie
。當我們登陸成功后,關閉瀏覽器,下次再打開瀏覽器的時候發現百度還是處於一種登陸的狀態,這就是和 Cookie
有關。在百度登陸成功后會返回一個 Cookie
儲存到瀏覽器中,下次再打開百度的時候,瀏覽器中的 頭信息 -> 請求頭信息 中會攜帶一個 Cookie
,這個 Cookie
就是百度服務器判斷你以前是否登陸過百度。而這個 Cooike
就是 Set-Cookie
加工而來的!那么重點來了,如果要用代碼模擬登陸百度,應該要具備以下幾個步驟:
- 構造請求表單
- 請求成功后獲取
Cookie
(這個Cookie
並非Set-Cookie
)- 在請求頭部
header
中攜帶這個Cookie
,就可以以登陸過后的身份訪問百度
原理講清楚了,那么下面開始實踐!
構造請求表單
在上面的 POST?login
中發現百度的請求表單還是挺多的,那么如何表單中判斷哪些是變化的那些事不變的?再一次清空 firefox
的全部歷史紀錄,清除 firebug
的 html
,重新在百度首頁點擊 登陸 ,填寫 賬號、錯誤的密碼、驗證碼,復制 POST?login
中的 post
信息下來,然后重復前面的步驟,就可以得到很多 post
信息,拿出來對比就可以知道哪些信息是變化的了。這里要解釋一下為什么要填寫 錯誤的密碼,因為密碼錯誤啦,登陸框就會一直都在啊,免去了清除 全部歷史紀錄 和清除 html
的步驟。最后對比的情況如下:
可以發現,請求的表單有
staticpage
charset
token
tpl
subpro
apiver
tt
codestring
safeflg
u
isPhone
detect
gid
quick_user
logintype
logLoginType
idc
loginmerge
splogin
username
password
verifycode
mem_pass
rsakey
crypttype
ppui_logintime
countrycode
fp_uid
fp_info
loginversion
ds
tk
dv
traceid
callback
其中,被紅框框起來的表單是多次請求變化的:
callback
tt
token
ppui_logintime
rsakey
verifycode
而 ds
、tk
在第一次請求網頁直接生成了:
https://passport.baidu.com/viewlog?ak=1e3f2dd1c81f2075171a547893391274&as=74a154e4&fs=MEBGsUNpNVBMjs8Tdudr8aAjW%2BFklVpfnDnMkmxZ3DMXZisUPveYoxrXH2HKd5pgidlfhvibzXjlMLk28ZUvrUpDazz2aivj1lT0DHKRrMLZBBvYrMBdY%2BenNlaoF8h%2F8s18t0DQtONJZoRMOt%2FDotooaXA1bPuODU3XkP5iOBv9GpK6mApUn2xQXIpSEFTInDKJEiFBfC04IfPyCVCe766QJT%2FS4CHeqIJsjVLa7aoNnh3%2BHSdvRx1Uay1Fy60q%2Fkz5TJ%2B8Ib25o8yDfFBcOdbIdhVwmDHp3R87v3%2BY0M9rl2MUlr4ZJO2vn98yspz9t60LrqhUsObz7FZIdG9sWRP6JNt00%2BeQIY6Z7liRZI75mSRTWGDHYMT8LU7KdOELrxdrM7OfHfoD%2BlJ8PpCPFPT8dOgJUKGwa0tkL6t5UKpOUUXoxbx3lkRUNSj5NxdNcRt3YZbDShJmXnRbfza7yDpgvzKBRULis%2BzxhbBijS5onMCPOB59OVGE6lges8nr9xhi0ZNM9f96V7S4elo4fsXUgQzmJJwsM69ah0RSVNFQbBNoGszbT47%2BHDORP%2Fd7OLGOeG8D9i5tMIf%2BYRgN6ing5B5lLpn5nn3KtshIWiAwrR5mijWZai7uheFiE2cHCovVBRAlfCp3yDtKRWN4cE55F9b0wvoDHSJmHqlVKp1%2BgbE9b1oUFmqOGWcWMakVQfrEFg6phufPuuaQLLdtX3%2Bir7yeC8rx8tHdcTz6CtJsWtVcavFV8Q8j8Ta90bSKp%2BjQlmOXmct7PeM3tRM8%2B946o67jwNX7CP1EjKw%2FYk5lP%2BmCqNjwK3eZf46pQGLmZYUOLuGBK73HeCPAlj4YlEfGrZYpCuLp1vthWK%2B5oZZR9c%2BLpu1aOGotEqebe2N6UaKbXhC2qn6h3glylAV%2B2HfY4wut%2Bj%2Frr3iJEhWLj7J7qD0fr5ojR993ru8qrZSxKYu1f5W6NhdGPz7ZpWRfBrIaxtMjliEgdrIZ82RSe930OeXJaXMzytvoxvsZaUYvODivXMsPXDlnEQ%2FiZPPAO1B3F06Y8so67piru9hrXdkBwGLP6G07wo2dCMPvSFHHuLSvFYWduRFscftm3qJ1XUSDHDYIe8t5y5ClLJJd%2FCAkdlhQc3iOQJUgOXp4tAjoSkkiLnramq%2BIa6ycbi%2BcfzE6recOWVsuTFC4rX0t4RLdY5yf%2BRkED6qYcR8LLorK0dVKTX34rRsvLFElzgbi%2FW1%2Fq8y8tU9X%2F3pQXzHEsw28si6pjHvbPd4rJoQTIoI5asbCbxKqjRCJCfJPXRbUxo%2BZeWwik4F5UiTzwpas3pQ%3D&callback=jsonpCallbackb5819&v=4646
直接返回結果 json
,所以得到 ds
、tk
:
jsonpCallbackb5819({"code":0,"data":{"tk":"1966\/wzQ9fSm481E1Dd6MPpdaM08fX2AB5MkNq1aMZHDBoekhU51\/8+yOdlYGlLXJVKpduaYRnOVNhfERmTiBXB1Vw==","as":"a82478bc","ds":"oLZa10fYIvKavmDHTaWvTF5D9f3NBzweejdgFGUJB9yI6TFVGHZ8EtWhXcLshwfDL0sU7ymlQe3uVByWIXCym03HZTZxZmGaXl8Jw+unuO5D3SN29KiMO0oj1fSH58BU"}})
其中很明顯可以看出來的是:
tt
時間戳verifycode
驗證碼
那么就剩下幾個請求表單還暫時無法得知,首先查找 callback
:
>1. 在 `firebug` 中勾選腳本,點擊 `{}`
>2. 搜索中勾選 **多個文件**
>3. 在搜索框中搜索 `callback`
一直搜索,最后發現 callback
和 getUniqueId
的生成有關:
那么換着 getUniqueId
來搜索,得到如下 javascrip
代碼:
e.getUniqueId = function (e) {
return e + Math.floor(2147483648 * Math.random()).toString(36)
},
和上面的 JavaScrip
整理起來,那么 callback
的生成代碼為:
function callback(){
return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36)
}
找到 callback
后,接下來要去尋找 token
。在 firebug
中尋找 token
第一次在哪里出現。最后發現首次出現在網址:
https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&tt=1495185331704&class=login&gid=63F95D8-F402-4128-A98B-C7D3C19B8F89&logintype=dialogLogin&callback=bd__cbs__3cagws
這個網址返回的是一個 Json
:
bd__cbs__3cagws({"errInfo": {"no": "0"},
"data": {"rememberedUserName": "", "codeString": "", "token": "6245a75e6ba48d39033a8c31dfcb37c7",
"cookie": "1", "usernametype": "", "spLogin": "rate", "disable": "",
"loginrecord": {'email': [], 'phone': []}}})
第一次 token
出現的地方找到了,那么分析一下請求出 token
的網址,網址中涉及到的變量有:
tpl
apiver
tt
class
gid
logintype
callback
為了查看哪些變量是變化的,就再次進行多次登陸。最后發現,變化的是:
tt
gid
callback
其中 tt
為長時間戳, callback
在前面已經找到並且能生成了,那么只剩下 gid
這個變量。老規矩,按照下面的步驟再去找出 gid
:
- 在
firebug
中勾選腳本,點擊{}
- 搜索中勾選 多個文件
- 在搜索框中搜索
gid
搜索發現 gid
是由 gid: e.guideRandom
這個函數生成的:
那么接着搜搜這個函數 guideRandom
,找到如下 JavaScrip
代碼:
this.guideRandom = function () {
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) {
var t = 16 * Math.random() | 0,
n = 'x' == e ? t : 3 & t | 8;
return n.toString(16)
}).toUpperCase()
}(),
整理一下讓其可以在 python
中運行:
function gid(){
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) {
var t = 16 * Math.random() | 0,
n = 'x' == e ? t : 3 & t | 8;
return n.toString(16)
}).toUpperCase()
}
這樣,根據 gid
和 callback
就能得到 token
了!
下面繼續尋找 ppui_logintime
,按照規矩來:
- 在
firebug
中勾選腳本,點擊{}
- 搜索中勾選 多個文件
- 在搜索框中搜索
ppui_logintime
,找到了timeSpan: 'ppui_logintime'
接着搜索 timeSpan
,得到如下信息:
s.timeSpan = (new Date).getTime() - e.initTime
現在還是看不出什么東西,那么就繼續搜索 initTime
,得到如下代碼:
_initApi: function (e) {
var t = this;
t.initialized = !0,
t.initTime = (new Date).getTime(),
passport.data.getApiInfo({
apiType: 'login',
gid: t.guideRandom || '',
loginType: t.config && t.config.diaPassLogin ? 'dialogLogin' : 'basicLogin'
}).success(function (n) {
var i = t.fireEvent('getApiInfo', {
rsp: n
});
繼續查找 initApi
,找到位置:
原來是在登陸的時候發生點擊,內容改變,按鍵按下等事件的時候,會調用 _initApi
,再看源碼和 ppui_logintime
的數值可以發現, ppui_logintime
代表的是從輸入信息開始到點擊登陸后結束的時間差!那么在后面 post
的時候直接可以自己構造這個數據了。
那么最后還剩下一個變量 rsakey
,在查找網頁的時候發現第一次出現 rsakey
的地方是:
https://passport.baidu.com/v2/getpublickey?token=fcd1f6684072372c6812a44c1d94bf51&tpl=mn&apiver=v3&tt=1461222170065&gid=C539A37-9B0C-4538-9920-E150AC6AE0D5&callback=bd__cbs__tpdrlq
也就是這里面的 key
,雖然名字不一樣,但是值是一樣的:
bd__cbs__tpdrlq({"errno": '0', "msg": '',
"pubkey": '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiA2HxDW2iVnunx7faCBG3YGBy\nvvF+ysFAIXIVjFTseU7x\/f+Gpr1VTWe2Kxc2dlzBkn5NuRHVxbyXCawu0QlMUfb8\nI2ukM1cIlL0e+B1nBnIp03oXjFvQNhIu58SI6vCoihWX6Qwhb6ZOvJdA249zCNBU\nlTyd7RVwgwaAthI6gQIDAQAB\n-----END PUBLIC KEY-----\n',
"key": 'wS27H0665CWXK64i2VP02AYtjQUTujkb'})
分析一下這個網址,出現的變量有:
token
tpl
apiver
tt
gid
callback
這些變量在前面都能找到,所以也就是說,根據 gid
、 callback
、 token
就能得到 rsakey
了!
下面開始講解怎么用 python
來實現模擬百度登陸!首先需要構造一個持續登陸的鏈接,持續登陸的鏈接能夠一直自我保存打開網頁或者登陸后留下的 cookie
,當然這是存放在內存中的,例如:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0"}
session = requests.session()
session.get("https://passport.baidu.com/v2/?login", headers=headers)
session
就是一個持續的鏈接,一般用 python
訪問網頁是這樣子調用的 requests.get
,這樣子的訪問不會保存歷史訪問留下的 cookie
,而用 session.get
能持續保存 cookie
,只要后面訪問都用 session.get
或者 session.post
即可。
獲取callback
callback
是由 JavaScrip
生成的:
function callback(){
return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36)
}
所以直接在 python
中運行這個 JavaScrip
就行了。 python
運行 JavaScrip
需要安裝庫 pyexecjs
,在命令指示符下直接輸入 pip3 install pyexecjs
即可。調用方式為:
import execjs
js = '''function callback(){
return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36)
}
'''
ctx = execjs.compile(js)
callback = ctx.call("callback")
獲取traceid
traceid
同樣是可以用 JavaScrip
生成的,直接調用即可:
import execjs
js = '''function traceid(){
var e = {a: 1, b: 1, c: 1}
e.traceID = {
headID: e.traceID && e.traceID.headID || "",
flowID: e.traceID && e.traceID.flowID || "",
cases: e.traceID && e.traceID.cases || "",
initTraceID: function(e) {
var t = this;
e && e.length > 0 ? (t.headID = e.slice(0, 6),
t.flowID = e.slice(6, 8)) : t.destory()
},
createTraceID: function() {
var e = this;
return e.headID + e.flowID + e.cases
},
startFlow: function(e) {
var t = this
, n = t.getFlowID(e);
0 === t.flowID.length || t.flowID === n ? (t.createHeadID(),
t.flowID = n) : t.finishFlow(n)
},
finishFlow: function() {
var e = this;
e.destory()
},
getRandom: function() {
return parseInt(90 * Math.random() + 10, 10)
},
createHeadID: function() {
var e = this
, t = (new Date).getTime() + e.getRandom().toString()
, n = Number(t).toString(16)
, i = n.length
, s = n.slice(i - 6, i).toUpperCase();
e.headID = s
},
getTraceID: function(e) {
var t = this
, n = e && e.traceid || "";
t.initTraceID(n)
},
getFlowID: function(e) {
var t = {
login: "01",
reg: "02"
};
return t[e]
},
setData: function(e) {
var t = this;
return e.data ? e.data.traceid = t.createTraceID() : e.url = e.url + (e.url.indexOf("?") > -1 ? "&" : "?") + "traceid=" + t.createTraceID(),
e
},
destory: function() {
var e = this;
e.headID = "",
e.flowID = ""
}
};
e.traceID.initTraceID()
e.traceID.createHeadID()
return e.traceID.createTraceID() + "01"
}'''
ctx = execjs.compile(js)
traceid = ctx.call("traceid")
獲取gid
gid
同樣是可以用 JavaScrip
生成的,直接調用即可:
import execjs
js = '''function gid(){
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) {
var t = 16 * Math.random() | 0,
n = 'x' == e ? t : 3 & t | 8;
return n.toString(16)
}).toUpperCase()
}'''
ctx = execjs.compile(js)
gid = ctx.call("gid")
獲取時間tt
時間 tt
是一個毫秒級別的長時間,而 python
生成的時間戳是短時間,所以要在短時間戳后面加上毫秒的長度即可,這里處理的方法是:在短時間戳的后面加上 3
位數的隨機數,從而構造出長時間戳。
import time
import random
timerandom = random.randint(100, 999)
nowtime = int(time.time())
tt = str(nowtime) + str(timerandom)
獲取token
有了 callback
、 gid
、 tt
后,可以獲取到 token
,構造請求參數:
tokendata = {
"tpl": "pp",
"subpro": "",
"apiver": "v3",
"tt": tt,
"class": "login",
"gid": gid,
"logintype": "basicLogin",
"callback": callback
}
更新頭部,攜帶頭部訪問:
headers.update(dict(Referer="http://passport.baidu.com/", Accept="*/*", Connection="keep-alive", Host="passport.baidu.com"))
resp = session.get(url="https://passport.baidu.com/v2/api/?getapi", params=tokendata, headers=headers)
順利抓到包含 token
的返回值,將其放到字典里面即可:
data = json.loads(re.search(r".*?\((.*)\)", resp.text).group(1).replace(""", """))
token = data.get('data').get('token')
順利返回 token
!
獲取rsakey
獲取 rsakey
只需要構造如下請求參數即可:
tt = get_tt()
get_data = {
"token": token,
"tpl": "pp",
"subpro": "",
"apiver": "v3",
"tt": tt,
"gid": gid,
"callback": callback,
}
在得到的返回值中提取出 rsakey
和 pubkey
既可:
tt = get_tt()
rsakeydata = {
"token": token,
"tpl": "pp",
"subpro": "",
"apiver": "v3",
"tt": tt,
"gid": gid,
"callback": callback,
}
resp = session.get(url="https://passport.baidu.com/v2/getpublickey", headers=headers, params=rsakeydata)
data = json.loads(re.search(r".*?\((.*)\)", resp.text).group(1).replace("'", '"'))
dicts = {}
dicts["rsakey"] = data.get("key")
dicts["pubkey"] = data.get("pubkey")
加密密碼
百度登陸的密碼加密方式是很簡單的 RSA
加密,這個只是用 JavaScrip
就能實現,翻譯成 python
的代碼為:
def base64_password(password, pubkey):
pub = rsa.PublicKey.load_pkcs1_openssl_pem(pubkey.encode("utf-8"))
encript_passwd = rsa.encrypt(password.encode("utf-8"), pub)
return base64.b64encode(encript_passwd).decode("utf-8")
需要安裝庫 rsa
,只需要在命令指示符下輸入:
pip3 install rsa
登陸
構造一個登陸的 postdata
:
post_data = {
"staticpage": "https://passport.baidu.com/static/passpc-account/html/v3Jump.html",
"charset": "utf-8",
"token": token,
"tpl": "pp",
"subpro": "",
"apiver": "v3",
"tt": tt,
"codestring": "",
"safeflg": 0,
"u": "http://passport.baidu.com/disk/home",
"isPhone": "",
"detect": 1,
"gid": gid,
"quick_user": 0,
"logintype": "basicLogin",
"logLoginType": "pc_loginBasic",
"idc": "",
"loginmerge": "true",
"foreignusername": "",
"username": username,
"password": newpassword,
"mem_pass": "on",
# 返回的key
"rsakey": rsakey,
"crypttype": 12,
"ppui_logintime": 33554,
"countrycode": "",
"dv": dv,
"callback": "parent." + callback
}
直接登陸:
resp = session.post(url="https://passport.baidu.com/v2/api/?login", data=post_data, headers=headers)
如果檢測到自己在賬號包含在返回的 html
里面,則說明登陸成功:
if username in resp.content.decode("utf-8", "ignore"):
print("登錄成功")
else:
print("登錄失敗")
登陸成功后有兩種方式在登陸狀態下訪問網頁:
- 持續使用
session
- 獲取登錄后的
cookie
第一種方法在本次程序跑完后就會自動將后台保存下來的 cookie
丟棄掉,如果下次需要訪問則需要重新登陸;第二種方法只要在頭部增加這個 cookie
值,就能一直使用 cookie
保證是登陸狀態,獲取登錄后的 cookie
的方法為:
cookies = requests.utils.dict_from_cookiejar(session.cookies)
print(cookies)
等到的 cookies
為一個字典,將這個字典保存在本地的 json
中:
import json
file = open("cookie.json","w")
file.write(json.dumps(cookies))
file.close()
下次訪問攜帶這個 cookies
即可:
json_file = open("cookie.json")
cookies = json.load(json_file)
json_file.close()
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0',
'Host': 'passport.baidu.com',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3',
'Connection': 'keep-alive'}
home_page = requests.get("https://passport.baidu.com/center", headers=headers,cookies=cookies).content.decode("utf-8", "ignore")
print(home_page)
這里需要提醒的是 cookie
會過期,一般是 7
天,如果發現使用 cookie
登陸失敗,那么就需要重新使用賬號密碼登陸獲取 cookie
。
歡迎關注公眾號【 機器學習和大數據挖掘 】,聯系作者以及獲取最新資訊