1. 程式人生 > 程式設計 >Python爬蟲之Selenium警告框(彈窗)處理

Python爬蟲之Selenium警告框(彈窗)處理

1. 內容概要

本文主要討論以下兩個問題:

  • JavaScript的位運算:先簡單回顧下位運算,平時用的少,相信不少人和我一樣忘的差不多了
  • 許可權設計:根據位運算的特點,設計一個許可權系統(新增、刪除、判斷等)

2.JavaScript位運算

2.1. Number

在講位運算之前,首先簡單看下 JavaScript 中的 Number,下文需要用到。

在 JavaScript 裡,數字均為基於 IEEE 754 標準的雙精度 64 位的浮點數,引用維基百科的圖片,它的結構長這樣:

  • sign bit(符號): 用來表示正負號
  • exponent(指數): 用來表示次方數
  • mantissa(尾數): 用來表示精確度

也就是說一個數字的範圍只能在 -(2^53 -1) 至 2^53 -1 之間。

既然講到這裡,就多說一句:0.1 + 0.2 算不準的原因也在於此。浮點數用二進位制表達時是無窮的,且最多 53 位,必須截斷,進而產生誤差。最簡單的解決辦法就是放大一定倍數變成整數,計算完成後再縮小。不過更穩妥的辦法是使用下文將會提到的 math.js等工具庫。

此外還有四種數字進位制:

// 十進位制
123456789
0

// 二進位制:字首 0b,0B
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607

// 八進位制:字首 0o,0O(以前支援字首 0)
0o755 // 493
0o644 // 420

// 十六進位制:字首 0x,0X
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF   // 81985529216486900
0XA                 // 10

好了,Number 就說這麼多,接下來看 JavaScript 中的位運算。

2.2. 位運算

按位操作符將其運算元當作 32 位的位元序列(由 0 和 1 組成)操作,返回值依然是標準的 JavaScript 數值。JavaScript 中的按位操作符有:

運算子用法描述
按位與(AND) a & b 對於每一個位元位,只有兩個運算元相應的位元位都是 1 時,結果才為 1,否則為 0。
按位或(OR) a \| b 對於每一個位元位,當兩個運算元相應的位元位至少有一個 1 時,結果為 1,否則為 0。
按位異或(XOR) a ^ b 對於每一個位元位,當兩個運算元相應的位元位有且只有一個 1 時,結果為 1,否則為 0。
按位非(NOT) ~a 反轉運算元的位元位,即 0 變成 1,1 變成 0。
左移(Left shift) a << b 將 a 的二進位制形式向左移 b (< 32) 位元位,右邊用 0 填充。
有符號右移 a >> b 將 a 的二進位制表示向右移 b (< 32) 位,丟棄被移出的位。
無符號右移 a >>> b 將 a 的二進位制表示向右移 b (< 32) 位,丟棄被移出的位,並使用 0 在左側填充。

下面舉幾個例子,主要看下AND和OR:

# 例子1
    A = 10001001
    B = 10010000
A | B = 10011001

# 例子2
    A = 10001001
    C = 10001000
A | C = 10001001
# 例子1
    A = 10001001
    B = 10010000
A & B = 10000000

# 例子2
    A = 10001001
    C = 10001000
A & C = 10001000

3. 位運算在許可權系統中的使用

傳統的許可權系統裡,存在很多關聯關係,如使用者和許可權的關聯,使用者和角色的關聯。系統越大,關聯關係越多,越難以維護。而引入位運算,可以巧妙的解決該問題。

在講“位運算在許可權系統中的使用”之前,我們先假定兩個前提,下文所有的討論都是基於這兩個前提的

  1. 每種許可權碼都是唯一的(這是顯然的)
  2. 所有許可權碼的二進位制數形式,有且只有一位值為 1,其餘全部為 0(2^n)

如果使用者許可權和許可權碼,全部使用二級制數字表示,再結合上面AND和OR的例子,分析位運算的特點,不難發現:

  • |可以用來賦予許可權
  • &可以用來校驗許可權

為了講的更明白,這裡用 Linux 中的例項分析下,Linux 的檔案許可權分為讀、寫和執行,有字母和數字等多種表現形式:

許可權字母表示數字表示二進位制
r 4 0b100
w 2 0b010
執行 x 1 0b001

可以看到,許可權用 1、2、4(也就是2^n)表示,轉換為二進位制後,都是隻有一位是 1,其餘為 0。我們通過幾個例子看下,如何利用二進位制的特點執行許可權的新增,校驗和刪除。

3.1. 新增許可權

let r = 0b100
let w = 0b010
let x = 0b001

// 給使用者賦全部許可權(使用前面講的 | 操作)
let user = r | w | x

console.log(user)
// 7

console.log(user.toString(2))
// 111

//     r = 0b100
//     w = 0b010
//     r = 0b001
// r|w|x = 0b111

可以看到,執行r | w | x後,user的三位都是 1,表明擁有了全部三個許可權。

Linux 下出現許可權問題時,最粗暴的解決方案就是 chmod 777 xxx,這裡的 7就代表了:可讀,可寫,可執行。而三個 7 分別代表:檔案所有者,檔案所有者所在組,所有其他使用者。

3.2. 校驗許可權

剛才演示了許可權的新增,下面演示許可權校驗:

let r = 0b100
let w = 0b010
let x = 0b001

// 給使用者賦 r w 兩個許可權
let user = r | w
// user = 6
// user = 0b110 (二進位制)

console.log((user & r) === r) // true  有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

如前所料,通過使用者許可權 & 許可權 code === 許可權 code就可以判斷出使用者是否擁有該許可權。

3.3. 刪除許可權

我們講了用|賦予許可權,使用&判斷許可權,那麼刪除許可權呢?刪除許可權的本質其實是將指定位置上的 1 重置為 0。上個例子裡使用者許可權是0b110,擁有讀和寫兩個許可權,現在想刪除讀的許可權,本質上就是將第三位的 1 重置為 0,變為0b010:

let r = 0b100
let w = 0b010
let x = 0b001

let user = 0b010;

console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

那麼具體怎麼操作呢?其實有兩種方案,最簡單的就是異或^,按照上文的介紹“當兩個運算元相應的位元位有且只有一個 1 時,結果為 1,否則為 0”,所以異或其實是 toggle 操作,無則增,有則減:

let r    = 0b100
let w    = 0b010
let x    = 0b001
let user = 0b110 // 有 r w 兩個許可權

// 執行異或操作,刪除 r 許可權
user = user ^ r

console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

console.log(user.toString(2)) // 現在 user 是 0b010

// 再執行一次異或操作
user = user ^ r

console.log((user & r) === r) // true  有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

console.log(user.toString(2)) // 現在 user 又變回 0b110

那麼如果單純的想刪除許可權(而不是無則增,有則減)怎麼辦呢?答案是執行&(~code),先取反,再執行與操作:

let r    = 0b100
let w    = 0b010
let x    = 0b001
let user = 0b110 // 有 r w 兩個許可權

// 刪除 r 許可權
user = user & (~r)

console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

console.log(user.toString(2)) // 現在 user 是 0b010

// 再執行一次
user = user & (~r)

console.log((user & r) === r) // false 沒有 r 許可權
console.log((user & w) === w) // true  有 w 許可權
console.log((user & x) === x) // false 沒有 x 許可權

console.log(user.toString(2)) // 現在 user 還是 0b010,並不會新增

4. 侷限性和解決辦法

前面我們回顧了 JavaScript 中的 Number 和位運算,並且瞭解了基於位運算的許可權系統原理和 Linux 檔案系統許可權的例項。

上述的所有都有前提條件:1、每種許可權碼都是唯一的;2、每個許可權碼的二進位制數形式,有且只有一位值為 1(2^n)。也就是說,許可權碼只能是 1, 2, 4, 8,...,1024,...而上文提到,一個數字的範圍只能在 -(2^53 -1) 和 2^53 -1 之間,JavaScript 的按位操作符又是將其運算元當作32 位位元序列的。那麼同一個應用下可用的許可權數就非常有限了。這也是該方案的侷限性。

為了突破這個限制,這裡提出一個叫“許可權空間”的概念,既然許可權數有限,那麼不妨就多開闢幾個空間來存放。

基於許可權空間,我們定義兩個格式:

  1. 許可權 code,字串,形如index,pos。其中pos表示 32 位二進位制數中 1 的位置(其餘全是 0);index表示許可權空間,用於突破 JavaScript 數字位數的限制,是從 0 開始的正整數,每個許可權code都要歸屬於一個許可權空間。index和pos使用英文逗號隔開。
  2. 使用者許可權,字串,形如1,16,16。英文逗號分隔每一個許可權空間的許可權值。例如1,16,16的意思就是,許可權空間 0 的許可權值是 1,許可權空間 1 的許可權值是 16,許可權空間 2 的許可權是 16。

幹說可能不好懂,直接上程式碼:

// 使用者的許可權 code
let userCode = ""

// 假設系統裡有這些許可權
// 純模擬,正常情況下是按順序的,如 0,0 0,1 0,2 ...,儘可能佔滿一個許可權空間,再使用下一個
const permissions = {
  SYS_SETTING: {
    value: "0,0",   // index = 0, pos = 0
    info: "系統許可權"
  },
  DATA_ADMIN: {
    value: "0,8",
    info: "資料庫許可權"
  },
  USER_ADD: {
    value: "0,22",
    info: "使用者新增許可權"
  },
  USER_EDIT: {
    value: "0,30",
    info: "使用者編輯許可權"
  },
  USER_VIEW: {
    value: "1,2",   // index = 1, pos = 2
    info: "使用者檢視許可權"
  },
  USER_DELETE: {
    value: "1,17",
    info: "使用者刪除許可權"
  },
  POST_ADD: {
    value: "1,28",
    info: "文章新增許可權"
  },
  POST_EDIT: {
    value: "2,4",
    info: "文章編輯許可權"
  },
  POST_VIEW: {
    value: "2,19",
    info: "文章檢視許可權"
  },
  POST_DELETE: {
    value: "2,26",
    info: "文章刪除許可權"
  }
}

// 新增許可權
const addPermission = (userCode, permission) => {
  const userPermission = userCode ? userCode.split(",") : []
  const [index, pos] = permission.value.split(",")

  userPermission[index] = (userPermission[index] || 0) | Math.pow(2, pos)

  return userPermission.join(",")
}

// 刪除許可權
const delPermission = (userCode, permission) => {
  const userPermission = userCode ? userCode.split(",") : []
  const [index, pos] = permission.value.split(",")

  userPermission[index] = (userPermission[index] || 0) & (~Math.pow(2, pos))

  return userPermission.join(",")
}

// 判斷是否有許可權
const hasPermission = (userCode, permission) => {
  const userPermission = userCode ? userCode.split(",") : []
  const [index, pos] = permission.value.split(",")
  const permissionValue = Math.pow(2, pos)

  return (userPermission[index] & permissionValue) === permissionValue
}

// 列出使用者擁有的全部許可權
const listPermission = userCode => {
  const results = []

  if (!userCode) {
    return results
  }

  Object.values(permissions).forEach(permission => {
    if (hasPermission(userCode, permission)) {
      results.push(permission.info)
    }
  })

  return results
}

const log = () => {
  console.log(`userCode: ${jsON.stringify(userCode, null, " ")}`)
  console.log(`許可權列表: ${listPermission(userCode).join("; ")}`)
  console.log("")
}

userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1"
// 許可權列表: 系統許可權

userCode = addPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "1,,16"
// 許可權列表: 系統許可權; 文章編輯許可權

userCode = addPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1073741825,,16"
// 許可權列表: 系統許可權; 使用者編輯許可權; 文章編輯許可權

userCode = addPermission(userCode, permissions.USER_DELETE)
log()
// userCode: "1073741825,131072,16"
// 許可權列表: 系統許可權; 使用者編輯許可權; 使用者刪除許可權; 文章編輯許可權

userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 許可權列表: 系統許可權; 使用者刪除許可權; 文章編輯許可權

userCode = delPermission(userCode, permissions.USER_EDIT)
log()
// userCode: "1,131072,16"
// 許可權列表: 系統許可權; 使用者刪除許可權; 文章編輯許可權

userCode = delPermission(userCode, permissions.USER_DELETE)
userCode = delPermission(userCode, permissions.SYS_SETTING)
userCode = delPermission(userCode, permissions.POST_EDIT)
log()
// userCode: "0,0,0"
// 許可權列表: 

userCode = addPermission(userCode, permissions.SYS_SETTING)
log()
// userCode: "1,0,0"
// 許可權列表: 系統許可權

除了通過引入許可權空間的概念突破二進位制運算的位數限制,還可以使用math.js的bignumber,直接運算超過 32 位的二進位制數,具體可以看它的文件,這裡就不細說了。

5. 適用場景和問題

如果按照當前使用最廣泛的RBAC模型設計許可權系統,那麼一般會有這麼幾個實體:應用,許可權,角色,使用者。使用者許可權可以直接來自許可權,也可以來自角色:

  • 一個應用下有多個許可權
  • 許可權和角色是多對多的關係
  • 使用者和角色是多對多的關係
  • 使用者和許可權是多對多的關係

在此種模型下,一般會有使用者與許可權,使用者與角色,角色與許可權的對應關係表。想象一個商城後臺許可權管理系統,可能會有上萬,甚至十幾萬店鋪(應用),每個店鋪可能會有數十個使用者,角色,許可權。隨著業務的不斷髮展,剛才提到的那三張對應關係表會越來越大,越來越難以維護。

而進位制轉換的方法則可以省略對應關係表,減少查詢,節省空間。當然,省略掉對應關係不是沒有壞處的,例如下面幾個問題:

  • 如何高效的查詢我的許可權?
  • 如何高效的查詢擁有某許可權的所有使用者?
  • 如何控制權限的有效期?

所以進位制轉換的方案比較適合剛才提到的應用極其多,而每個應用中使用者,許可權,角色數量較少的場景。

資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com

6. 其他方案

除了二進位制方案,當然還有其他方案可以達到類似的效果,例如直接使用一個1和0組成的字串,許可權點對應index,1表示擁有許可權,0表示沒有許可權。舉個例子:新增 0、刪除 1、編輯 2,使用者A擁有新增和編輯的許可權,則 userCode 為 101;使用者B擁有全部許可權,userCode 為 111。這種方案比二進位制轉換簡單,但是浪費空間。

還有利用質數的方案,許可權點全部為質數,使用者許可權為他所擁有的全部許可權點的乘積。如:許可權點是 2、3、5、7、11,使用者許可權是 5 * 7 * 11 = 385。這種方案麻煩的地方在於獲取質數(新增許可權點)和質因數分解(判斷許可權),許可權點特別多的時候就快成 RSA 了,如果只有增刪改查個別幾個許可權,倒是可以考慮。