激情上演頭文字D髮夾彎漂移?你是真的騷!

內容選自即將出版的《Python3 反爬蟲原理與繞過實戰》,本次公開書稿範圍爲第 6 章——文本混淆反爬蟲。本篇爲第 6 章中的第 2 小節,第 三、4 小節已發,直達連接:css

其他小節將逐步放送html

CSS 偏移反爬蟲

CSS 偏移反爬蟲指的是利用 CSS 樣式將亂序的文字排版爲人類正常閱讀順序的行爲。這個概念不是很好理解,咱們能夠經過對比兩段文字來加深對這個概念的理解。算法

  • HTML 文本中的文字:個人學號是 1308205,我在北京大學讀書。
  • 瀏覽器顯示的文字:個人學號是 1380205,我在北京大學讀書。

爬蟲提取到的學號是 1308205,但用戶在瀏覽器中看到的倒是 1380205。若是不細心觀察,爬蟲工程師很容易被爬取結果糊弄。這種混淆方法和圖片假裝同樣,是不會影響用戶閱讀的。讓人好奇的是,瀏覽器如何將 HTML 文本中的數字按照開發者的意願排序或放置呢?這種放置規則是如何運做的呢?咱們能夠經過一個具體的例子來了解 CSS 偏移反爬蟲的應用和繞過方法。瀏覽器

6.2.1 CSS 偏移反爬蟲繞過實戰

示例 5:CSS 偏移反爬蟲示例。bash

網址:www.porters.vip/confusion/f…app

任務:爬取航班查詢和機票銷售網站頁面中的航站名稱、所屬航空公司和票價,頁面內容如圖 6-4 所示。工具

圖 6-4 示例 5 頁面post

在編寫 Python 代碼以前,咱們須要肯定目標數據的元素定位。航空公司名稱元素定位如圖 6-5 所示。字體

圖 6-5 航空公司名稱元素定位結果網站

航空公司名稱包裹在沒有屬性的 span 標籤中,但該 span 標籤包裹在 class 屬性爲 air g-tips 的 div 標籤中。接下來咱們看一下航站名稱的元素定位,定位結果如圖 6-6 所示。

圖 6-6 航站名稱元素定位結果

航站名稱包裹在沒有屬性的 h2 標籤中,h2 標籤包裹在 class 爲 sep-lf 的 div 標籤中。

咱們再看一下票價的元素定位,定位結果如圖 6-7 所示。

圖 6-7 票價的元素定位結果

頁面中顯示的票價爲 467,可是在網頁中卻有兩組不一樣的數字,其中一組是[7, 7, 7],而另外一組是 [6, 4],這看起來就有點奇怪了。

難道是網頁顯示有問題?

按照正常排序來講,這架航班的票價應該是 77 764 纔對。咱們能夠查看第二架航班信息的價格,思考是網頁顯示問題仍是作了什麼反爬蟲措施。第二架航班的票價元素定位結果如圖 6-8 所示。

圖 6-8 第二架航班的票價元素定位結果

結果與第一架航班的票價顯示有一樣的問題:網頁顯示內容和 HTML 代碼中的內容不一致。咱們分析一下 HTML 代碼,看一看是否能找到什麼線索。第一架航班票價的 HTML 代碼爲:

<span class="prc_wp" style="width:48px"> 
    <em class="rel"> 
        <b style="width:48px;left:-48px"> 
            <i style="width: 16px;">7</i> 
            <i style="width: 16px;">7</i> 
            <i style="width: 16px;">7</i> 
        </b> 
        <b style="width: 16px;left:-32px">6</b> 
        <b style="width: 16px;left:-48px">4</b> 
    </em> 
</span>
複製代碼

代碼中有 3 對 b 標籤,第 1 對 b 標籤中包含 3 對 i 標籤,i 標籤中的數字都是 7,也就是說第 1 對 b 標籤的顯示結果應該是 777。而第 2 對 b 標籤中的數字是 6,第 3 對 b 標籤中的數字是 4。

這些數字與頁面所顯示票價 467 的關係是什麼呢?

這一步找到的標籤和數字有多是數據源,可是數字的組合有不少種可能,如圖 6-9 所示。

圖 6-9 數字組合推測

5 個數字的組合結果太多了,咱們必須找出其中的規律,這樣就能知道網頁爲何顯示 467 而不是 764 或者 776 。在仔細查看事後,發現每一個帶有數字的標籤都設定了樣式。第 1 對 b 標籤的樣式爲:

width:48px;left:-48px
複製代碼

第 2 對 b 標籤的樣式爲:

width: 16px;left:-32px
複製代碼

第 3 對 b 標籤的樣式爲:

width: 16px;left:-48px
複製代碼

i 標籤對的樣式是相同的,都是:

width: 16px;
複製代碼

另外,還注意到最外層的 span 標籤對的樣式爲:

width:48px
複製代碼

若是按照 CSS 樣式這條線索來分析的話,第 1 對 b 標籤中的 3 對 i 標籤恰好佔滿 span 標籤對的位置,其位置如圖 6-10 所示。

圖 6-10 span 標籤對和 i 標籤對位置圖

此時網頁中顯示的價格應該是 777,可是因爲第 2 和第 3 對 b 標籤中有值,因此咱們還須要計算它們的位置。此時標籤位置的變化如圖 6-11 所示。

圖 6-11 標籤位置變化

右側是標籤位置變化後的結果,因爲第 2 對 b 標籤的位置樣式是 left:-32px,因此第 2 對 b 標籤中的值 6 就會覆蓋原來第 1 對 b 標籤中的中的第 2 個數字 7,此時頁面應該顯示的數字是 767。

按此規律推算,第 3 對 b 標籤的位置樣式是 left:-48px,這個標籤的值會覆蓋第 1 對 b 標籤中的第 1 個數字 7,覆蓋結果如圖 6-12 所示,最後顯示的票價是 467。

圖 6-12 覆蓋結果

根據結果來看這種算法是合理的,不過咱們還須要對其進行驗證,如今將第二架航班的 HTML 值 和 CSS 樣式按照這個規律進行推算。最後推算獲得的結果與頁面顯示結果相同,說明這個位置偏移的計算方法是正確的,這樣咱們就能夠編寫 Python 代碼獲取網頁中的票價信息了。由於 b 標籤包裹在 class 屬性爲 rel 的 em 標籤下,因此咱們要定位全部的 em 標籤。對應的 Python 代碼以下:

import requests 
import re 
from parsel import Selector 
url = 'http://www.porters.vip/confusion/flight.html' 
resp = requests.get(url) 
sel = Selector(resp.text) 
em = sel.css('em.rel').extract()
複製代碼

接着定位全部的 b 標籤。因爲 b 標籤中還有 i 標籤,並且 i 標籤的值是基準數據,因此能夠直接提取。對應的 Python 代碼以下:

for element in em: 
    element = Selector(element) 
    # 定位全部的<b>標籤
    element_b = element.css('b').extract() 
    b1 = Selector(element_b.pop(0)) 
    # 獲取第 1 對<b>標籤中的值(列表) 
    base_price = b1.css('i::text').extract()
複製代碼

接下來要提取其餘 b 標籤的偏移量和數字。對應的 Python 代碼以下:

alternate_price = [] 
for eb in element_b: 
   eb = Selector(eb) 
   # 提取<b>標籤的 style 屬性值
   style = eb.css('b::attr("style")').get() 
   # 得到具體的位置
   position = ''.join(re.findall('left:(.*)px', style)) 
   # 得到該標籤下的數字
   value = eb.css('b::text').get() 
   # 將<b>標籤的位置信息和數字以字典的格式添加到替補票價列表中
   alternate_price.append({'position': position, 'value': value})
複製代碼

而後根據偏移量決定基準數據列表的覆蓋元素,其實是完成圖 6-11 中的操做。

for al in alternate_price: 
   position = int(al.get('position')) 
   value = al.get('value') 
   # 判斷位置的數值是否正整數
   plus = True if position >= 0 else False 
   # 計算下標,以 16px 爲基準
   index = int(position / 16) 
   # 替換第一對<b>標籤值列表中的元素,也就是完成值覆蓋操做
   base_price[index] = value 
print(base_price)
複製代碼

最後將數據列表打印出來,獲得的輸出結果爲:

['4', '6', '7'] 
['8', '7', '0', '5']
複製代碼

使人感到奇怪的是,輸出結果中第一組票價數字與頁面中顯示的相同,但第二組卻不一樣。這是由於第二架航班的票價基準數據有 4 個值。航班票價對應的 HTML 代碼以下:

<em class="rel"> 
   <b style="width:64px;left:-64px"> 
       <i style="width: 16px;">8</i> 
       <i style="width: 16px;">3</i> 
       <i style="width: 16px;">9</i> 
       <i style="width: 16px;">5</i>
   </b> 
   <b style="width: 16px;left:-32px">0</b> 
   <b style="width: 16px;left:-48px">7</b> 
   <b style="width: 16px;left:-16px">5</b> 
</em>
複製代碼

覆蓋操做是根據由偏移量計算得出的下標進行的,實際上就是列表元素的替換。當基準數據列表的元素數量超過包裹着 i 標籤的 b 標籤寬度時,咱們就對列表進行切片,不然按照原來的替換規則進行。所以,須要對代碼作一些調整。調整內容以下:

# 減號表明刪除此行代碼,加號表明新增代碼
+ import re 
- base_price = b1.css('i::text').extract() 
+ b1_style = b1.css('b::attr("style")').get() 
# 得到具體的位置
+ b1_width = ''.join(re.findall('width:(.*)px;', b1_style)) 
+ number = int(int(b1_width) / 16) 
# 獲取第 1 對 <b> 標籤中的值(列表) 
+ base_price = b1.css('i::text').extract()[:number]
複製代碼

若是列表中元素的數量超過標籤寬度,那麼後面的元素是不會顯示的。好比 width:32px,每一個標籤佔位寬度 16 px,那麼即便 b 標籤下有 5 個 i 標籤(base_price=[1, 2 ,3 ,4 , 5]),在頁面中也僅顯示前面的兩個數字。代碼調整完畢後,再次運行代碼。運行結果爲:

['4', '6', '7'] 
['8', '7', '0', '5']
複製代碼

第二架航班的票價結果仍然跟頁面顯示的內容不一樣,但根據 CSS 寬度規則,咱們以前分析的邏輯是正確的。爲何結果仍是跟頁面顯示的不同呢?

實際上並非咱們的邏輯和代碼有錯,而是頁面顯示錯誤。要注意的是,頁面數據顯示錯誤是常發生的事,咱們只須要按照正確的邏輯編寫代碼便可。

6.2.2 去哪兒網反爬蟲案例

去哪兒網是中國領先的在線旅遊平臺,覆蓋全球 68 萬餘條航線,並與國內的旅遊景點和航空公司進行了深度的合做。去哪兒網也有用到相似的反爬蟲手段,咱們一塊兒來了解一下。

打開瀏覽器並訪問 dwz.cn/d05zNKyq,頁面… 6-13 所示。

圖 6-13 去哪兒網航班信息

航班票價對應的 HTML 代碼如圖 6-14 所示。

圖 6-14 去哪兒網航班票價 HTML 代碼

去哪兒網航班票價所對應的 HTML 代碼結構和 CSS 與咱們在示例 5 中見到的相似。咱們能夠大膽猜想,去哪兒網航班票價的顯示規律與示例 5 中所用的方法也是相似的,感興趣的同窗能夠按照 6.2.1 節的思路進行票價推算。去哪兒網航班票價中第 1 對 b 標籤下的 i 標籤數量與 width 是相匹配的,並未出現顯示錯誤的問題。

6.2.3 小結

CSS 樣式能夠改變頁面顯示,但這種「改變」僅存在於瀏覽器(可以解釋 CSS 的渲染工具)中,即便爬蟲工程師藉助渲染工具,也沒法得到「見到」的內容。

新書福利

真是翹首以盼!《Python3 反爬蟲原理與繞過實戰》一書終於要跟你們見面了!爲了感謝你們對韋世東和本書的期待與支持,在新書發佈時會舉辦多場送書活動和限時折扣活動。

想要與做者韋世東交流或者參加新書發佈活動的朋友能夠掃描二維碼進羣與我互動哦!

轉載說明

本篇內容摘自出版圖書《Python3 反爬蟲原理與繞過實戰》,歡迎各位好友與同行轉載!

記得帶上相關的版權信息哦😊。

相關文章
相關標籤/搜索