反爬篇 | 手把手教你處理 JS 逆向之 CSS 偏移
本篇文章將聊聊另外一種常見的反爬方案,即:「 CSS 偏移 」。
CSS 偏移反爬是利用「 CSS 樣式 」對網頁元素進行一次自定義的排序,最后讓網頁以正確的數據展示出來。
下面我們通過一個簡單的實例,講解應對 CSS 偏移網站常規解決方案。
目標對象:aHR0cDovL3d3dy5wb3J0ZXJzLnZpcC9jb25mdXNpb24vZmxpZ2h0Lmh0bWw=
1.分析一下
打開目標網站,在開發者工具面板中查看「 機票價格 」的網頁元素組成方式。
我們發現,機票價格由上、下兩個區域的數據元素,通過一定的偏移量偏移,最后在頁面上展示的。
以第 1 條數據為例,機票實際價格為 467。
區域一寬度設置為 48px,left 的值為 -48px 代表左邊距向左偏移 48px。
其內部的 i 標簽寬度都為 16px,完全占滿了父容器的寬度。
即:如果區域二隱藏的話,機票價格應該為 777。
我們繼續看區域二的內容
第一個 b 標簽,內容為 6,left 屬性值為 -32px,寬度為 16px,會覆蓋上面的第二個數字。
第二個 b 標簽,內容為 4,left 屬性值為 -48px,寬度同樣為 16px,會覆蓋掉上面的第一個數字。
因此,最后網頁展示的機票價格就是 467。
2.特殊處理
如果仔細觀察網頁元素,會發現 b 元素下的第三個 i 標簽既然展示在第二個行,而不是和前面兩個 i 標簽在同一行展示。
其實,這是因為 i 元素標簽設置樣式 display 為 inline-block。
PS:inline-block 默認元素之間會存在一定的間隙。
因此,為了正確解析出數據,我們需要針對網頁源代碼對部分網頁元素進行二次更新。
3.實戰一下
首先,我們需要安裝依賴包。
# 依賴包
# bs4 用于對網頁源碼的元素樣式進行二次更新
pip3 install beautifulsoup4
# lxml 用于爬取網頁數據
pip3 install lxml
接下來,我們使用 bs4 解析網頁源碼,獲取所有的 em 元素,修改它下面「 b 標簽 」的 display 屬性值為浮動「 flex 」,然后重新導出數據。
import requests
from bs4 import BeautifulSoup
...
url = 'http://.../flight.html'
resp = requests.get(url).text
# 首次解析源碼
soup = BeautifulSoup(resp, "lxml")
# 查詢頁面中的em元素
em_elements = soup.find_all("em", class_="rel")
# 對第一個b標簽添加flex的屬性
for em_element in em_elements:
first_b_element = em_element.find_all('b')[0]
# 添加flex屬性
first_b_element['style'] = first_b_element['style'] + ';display:flex;'
# 重新導出進行數據解析
resp = soup.prettify()
...
# 寫入到本地文件查看
# with open('temp.html', 'w', encoding='utf-8') as file:
# file.write(resp)
...
緊接著,我們利用 xpath 語法獲取所有航班 Item 元素控件。
結合正則表達式拿到機票價格對應元素的 left 偏移量,通過這個偏移量可以計算出數據應該展示的位置索引。
最后,根據索引將數據放置在列表的既定位置,組成真實的機票價格。
import re
from lxml import etree
...
# 數據解析
html = etree.HTML(resp)
# 查詢有幾個航班數據
div_list = html.xpath('//div[@class="left col-md-9"]/div')
print('航班數據數目:', len(div_list))
for index in range(len(div_list)):
# 獲取所有b標簽
b_elements = div_list[index].xpath('.//em/b')
# 從第1個b標簽下面的子標簽數據 ,這樣就可以獲取價格的位數(3位、4位)
price_num_list = b_elements[0].xpath('./i/text()')
print("打底機票價格為:", ''.join([item.strip() for item in price_num_list]))
# 從第2個b標簽開始,獲取真實價格對應的數字
for index, b_element in enumerate(b_elements[1:]):
# 數據
price_num = int(b_element.xpath('./text()')[0])
# 獲取b標簽的style屬性值
style = b_element.xpath('./@style')[0]
# 利用正則表達式,獲取left屬性值
left_value = re.findall('left:(.*?)px', style)[0]
# 根據left值,計算數據在真實價格中的索引位置(-1/-2/-3)
price_index = int(int(left_value) / 16)
# 替換源數組中的數據,按索引將數值設置進去
price_num_list[price_index] = price_num
# item都轉成字符串,合成一個新的數組
price_num_list = [str(item).strip() for item in price_num_list]
# 組成價格
price = int(''.join(price_num_list))
print("機票價格:", price)
...