反爬篇 | 手把手教你處理 JS 逆向之 CSS 偏移

CSS 偏移反爬是利用「 CSS 樣式 」對網(wǎng)頁元素進行一次自定義的排序,最后讓網(wǎng)頁以正確的數(shù)據(jù)展示出來

下面我們通過一個簡單的實例,講解應對 CSS 偏移網(wǎng)站常規(guī)解決方案

目標對象:

aHR0cDovL3d3dy5wb3J0ZXJzLnZpcC9jb25mdXNpb24vZmxpZ2h0Lmh0bWw=

1、分析一下

打開目標網(wǎng)站,在開發(fā)者工具面板中查看「 機票價格 」的網(wǎng)頁元素組成方式

我們發(fā)現(xiàn),機票價格由上、下兩個區(qū)域的數(shù)據(jù)元素,通過一定的偏移量偏移,最后在頁面上展示的

52608b3447e7a7d3ec8136dfd823bd0d.png

以第 1 條數(shù)據(jù)為例,機票實際價格為 467

區(qū)域一寬度設置為 48px,left 的值為 -48px 代表左邊距向左偏移 48px

其內(nèi)部的 i 標簽寬度都為 16px,完全占滿了父容器的寬度

即:如果區(qū)域二隱藏的話,機票價格應該為 777

我們繼續(xù)看區(qū)域二的內(nèi)容

第一個 b 標簽,內(nèi)容為 6,left 屬性值為 -32px,寬度為 16px,會覆蓋上面的第二個數(shù)字

第二個 b 標簽,內(nèi)容為 4,left 屬性值為 -48px,寬度同樣為 16px,會覆蓋掉上面的第一個數(shù)字

因此,最后網(wǎng)頁展示的機票價格就是 467

2、特殊處理

如果仔細觀察網(wǎng)頁元素,會發(fā)現(xiàn) b 元素下的第三個 i 標簽既然展示在第二個行,而不是和前面兩個 i 標簽在同一行展示

909375dcc03bfda78fa099aa9e8c30c1.png

其實,這是因為 i 元素標簽設置樣式 display 為 inline-block

PS:inline-block 默認元素之間會存在一定的間隙

因此,為了正確解析出數(shù)據(jù),我們需要針對網(wǎng)頁源代碼對部分控件樣式進行二次更新






3、實戰(zhàn)一下

首先,我們需要安裝依賴包

  1. # 依賴包
  2. # bs4 用于對網(wǎng)頁源碼的元素樣式進行二次更新
  3. pip3 install beautifulsoup4
  4. # lxml 用于爬取網(wǎng)頁數(shù)據(jù)
  5. pip3 install lxml

接下來,我們使用 bs4 解析網(wǎng)頁源碼,獲取所有的 em 元素,修改它下面「 b 標簽 」的 display 屬性值為浮動「 flex 」,然后重新導出數(shù)據(jù)

  1. import requests
  2. from bs4 import BeautifulSoup
  3. ...
  4. url = 'http://.../flight.html'
  5. resp = requests.get(url).text
  6. # 首次解析源碼
  7. soup = BeautifulSoup(resp, "lxml")
  8. # 查詢頁面中的em元素
  9. em_elements = soup.find_all("em", class_="rel")
  10. # 對第一個b標簽添加flex的屬性
  11. for em_element in em_elements:
  12.     first_b_element = em_element.find_all('b')[0]
  13.     # 添加flex屬性
  14.     first_b_element['style'] = first_b_element['style'] + ';display:flex;'
  15. # 重新導出進行數(shù)據(jù)解析
  16. resp = soup.prettify()
  17. ...
  18. # 寫入到本地文件查看
  19. # with open('temp.html''w', encoding='utf-8') as file:
  20. #     file.write(resp)
  21. ...

緊接著,我們利用 xpath 語法獲取所有航班 Item 元素控件

結(jié)合正則表達式拿到機票價格對應元素的 left 偏移量,通過這個偏移量可以計算出數(shù)據(jù)應該展示的位置索引

最后,根據(jù)索引將數(shù)據(jù)放置在列表的既定位置,組成真實的機票價格

  1. import re
  2. from lxml import etree
  3. ...
  4. # 數(shù)據(jù)解析
  5. html = etree.HTML(resp)
  6. # 查詢有幾個航班數(shù)據(jù)
  7. div_list = html.xpath('//div[@class="left col-md-9"]/div')
  8. print('航班數(shù)據(jù)數(shù)目:'len(div_list))
  9. for index in range(len(div_list)):
  10.     # 獲取所有b標簽
  11.     b_elements = div_list[index].xpath('.//em/b')
  12.     # 從第1個b標簽下面的子標簽數(shù)據(jù) ,這樣就可以獲取價格的位數(shù)(3位、4位)
  13.     price_num_list = b_elements[0].xpath('./i/text()')
  14.     print("打底機票價格為:"''.join([item.strip() for item in price_num_list]))
  15.     # 從第2個b標簽開始,獲取真實價格對應的數(shù)字
  16.     for index, b_element in enumerate(b_elements[1:]):
  17.         # 數(shù)據(jù)
  18.         price_num = int(b_element.xpath('./text()')[0])
  19.         # 獲取b標簽的style屬性值
  20.         style = b_element.xpath('./@style')[0]
  21.         # 利用正則表達式,獲取left屬性值
  22.         left_value = re.findall('left:(.*?)px', style)[0]
  23.         # 根據(jù)left值,計算數(shù)據(jù)在真實價格中的索引位置(-1/-2/-3)
  24.         price_index = int(int(left_value) / 16)
  25.         # 替換源數(shù)組中的數(shù)據(jù),按索引將數(shù)值設置進去
  26.         price_num_list[price_index] = price_num
  27.     # item都轉(zhuǎn)成字符串,合成一個新的數(shù)組
  28.     price_num_list = [str(item).strip() for item in price_num_list]
  29.     # 組成價格
  30.     price = int(''.join(price_num_list))
  31.     print("機票價格:", price)
  32. ...


作者:星安果


歡迎關(guān)注微信公眾號 :AirPython