爬蟲之抓js教程

在初學的爬蟲過程當中,不少人還不知道有些字段是如何生成的,怎樣模擬生成這些字段來拼接頭部。爲了再次記念【宏彥獲水】成語初次面世,特意用【百度登錄】寫下一篇登錄百度的教程,以供你們參考。html

前面學習瞭如何在 get 的時候想服務器發送多變的請求數據,從而達到搜索的效果,而實際上 搜索是簡單的登錄 !因此本文將要介紹如何向百度服務器發送 post 數據,從而達到模擬登錄百度的效果。java

  1. 首先打開 firefox 瀏覽器,清除網頁全部的歷史紀錄,這是爲了防止之前的 Cookie 影響服務器返回的數據。
  2. F12 打開 firebug ,進入百度首頁,點擊 網絡 -> 清除 ,這是爲了刪掉打開百度首頁而彈出來的 html,方便後面的查找 html 數據。
  3. 點擊登錄按鈕,依次填寫帳號、密碼、驗證碼,點擊 登錄 ,在 firebug 中點擊 保持 ,這是爲了防止登錄成功後,登錄表單的 html 被清除。

firebug 中,找到以下一行 POST?loginpython

image

點擊前面的 + 號 -> post ,能夠看到提交的表單,這個就是點擊登錄後,網頁向百度服務器後端發送的 登錄請求表單,表單中包含了 帳號密碼其餘 等信息:json

image

若是百度後臺認爲此 登錄表單請求 是正確的後,會在 頭信息 -> 響應頭信息 中返回一個 Set-Cookie 。當咱們登錄成功後,關閉瀏覽器,下次再打開瀏覽器的時候發現百度仍是處於一種登錄的狀態,這就是和 Cookie 有關。在百度登錄成功後會返回一個 Cookie 儲存到瀏覽器中,下次再打開百度的時候,瀏覽器中的 頭信息 -> 請求頭信息 中會攜帶一個 Cookie ,這個 Cookie 就是百度服務器判斷你之前是否登錄過百度。而這個 Cooike 就是 Set-Cookie 加工而來的!那麼重點來了,若是要用代碼模擬登錄百度,應該要具有如下幾個步驟:後端

  1. 構造請求表單
  2. 請求成功後獲取 Cookie (這個 Cookie 並不是 Set-Cookie)
  3. 在請求頭部 header 中攜帶這個 Cookie ,就能夠以登錄事後的身份訪問百度

原理講清楚了,那麼下面開始實踐!api

構造請求表單

在上面的 POST?login 中發現百度的請求表單仍是挺多的,那麼如何表單中判斷哪些是變化的那些事不變的?再一次清空 firefox 的所有歷史紀錄,清除 firebughtml ,從新在百度首頁點擊 登錄 ,填寫 帳號錯誤的密碼驗證碼,複製 POST?login 中的 post 信息下來,而後重複前面的步驟,就能夠獲得不少 post 信息,拿出來對比就能夠知道哪些信息是變化的了。這裏要解釋一下爲何要填寫 錯誤的密碼,由於密碼錯誤啦,登錄框就會一直都在啊,免去了清除 所有歷史紀錄 和清除 html 的步驟。最後對比的狀況以下:瀏覽器

image

能夠發現,請求的表單有服務器

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

其中,被紅框框起來的表單是屢次請求變化的:cookie

callback
tt
token
ppui_logintime
rsakey
verifycode

dstk 在第一次請求網頁直接生成了:網絡

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,因此獲得 dstk:

jsonpCallbackb5819({"code":0,"data":{"tk":"1966\/wzQ9fSm481E1Dd6MPpdaM08fX2AB5MkNq1aMZHDBoekhU51\/8+yOdlYGlLXJVKpduaYRnOVNhfERmTiBXB1Vw==","as":"a82478bc","ds":"oLZa10fYIvKavmDHTaWvTF5D9f3NBzweejdgFGUJB9yI6TFVGHZ8EtWhXcLshwfDL0sU7ymlQe3uVByWIXCym03HZTZxZmGaXl8Jw+unuO5D3SN29KiMO0oj1fSH58BU"}})

其中很明顯能夠看出來的是:

  1. tt 時間戳
  2. verifycode 驗證碼

那麼就剩下幾個請求表單還暫時沒法得知,首先查找 callback

>1. 在 `firebug` 中勾選腳本,點擊 `{}`
>2. 搜索中勾選 **多個文件**
>3. 在搜索框中搜索 `callback`

image

一直搜索,最後發現 callbackgetUniqueId 的生成有關:

image

那麼換着 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

image

這個網址返回的是一個 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

  1. firebug 中勾選腳本,點擊 {}
  2. 搜索中勾選 多個文件
  3. 在搜索框中搜索 gid

搜索發現 gid 是由 gid: e.guideRandom 這個函數生成的:

image

那麼接着搜搜這個函數 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()
    }

這樣,根據 gidcallback 就能獲得 token 了!

下面繼續尋找 ppui_logintime ,按照規矩來:

  1. firebug 中勾選腳本,點擊 {}
  2. 搜索中勾選 多個文件
  3. 在搜索框中搜索 ppui_logintime ,找到了 timeSpan: 'ppui_logintime'

image

接着搜索 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
});

image

繼續查找 initApi ,找到位置:

image

原來是在登錄的時候發生點擊,內容改變,按鍵按下等事件的時候,會調用 _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

這些變量在前面都能找到,因此也就是說,根據 gidcallbacktoken 就能獲得 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

有了 callbackgidtt 後,能夠獲取到 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,
}

在獲得的返回值中提取出 rsakeypubkey 既可:

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("登陸失敗")

登錄成功後有兩種方式在登錄狀態下訪問網頁:

  1. 持續使用 session
  2. 獲取登陸後的 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

歡迎關注公衆號【 機器學習和大數據挖掘 】,聯繫做者以及獲取最新資訊

相關文章
相關標籤/搜索