嚴重聲明:本文僅用於學習交流,不得用於商業用途,同時希望大家遵循robots協議,維護網絡和諧。
本猿最近在逛一些網站的時間。在打開瀏覽器的f12查看人家前端代碼咋寫的時候,經常會發現就是頁面上顯示的內容和源碼裏面的不一樣,然後自己請求一遍也還是不一樣,奇怪,猿,妙不可言?本着猿精神,上網查了下,這種屬於字體反爬策略。應用的還是不少的,所以,在這裏將在下對字體反爬的見解寫一下。
- 先觀察人人車網站,看看人家的實現: 人人車
-
打開一個詳情頁,f12看頁面標籤,可以發現標題裏面年款啥的頁面上展示給我們的和我們看到的不一樣。在查看了很多頁面後發現,人人車字體變化的都是數字:
-
查看標題css樣式的時候發現,標題標籤和一個css樣式利用屬性名關聯上了,所以這個屬性名肯定有問題:
-
查看服務器給我們發的響應包,直接發現一個跟上面標籤屬性名一樣的字體文件,利用谷歌瀏覽器的規範輸出,直接查看,發現最下面的一行數字順序是被打亂的。另外還有一個字體文件,但是數字順序是正常的:
-
我們看這兩串數字,因爲i標題採用了個亂序數字字體文件名的樣式,暫時總結出個規律:就是本應該在1的位置上他換上了2,2>1,3>4,4>3,5>8,8>5:
-
按照4中的規律再看首頁的源碼和展示給我們的標題,猿來如此:
-
應用上面過程查看其它頁面,發現都是上面這樣的套路,但是這個字體文件的url和內容是變化的。
-
那麼,作爲一個coder,怎麼會被這些迷惑操作遮住我們睿智的雙眼,下面是本猿利用python代碼實現的字體復原過程(完整測試代碼貼在最下面):
測試代碼,完整:
# !/usr/bin/python3 # -*- coding: utf-8 -*- """ # @Time : 2020/4/25 13:32 # @Author : hupoc # @File : second_hand_car_renrenche.py # @Desc : """ import io import os import requests from fontTools.ttLib import TTFont from scrapy import Selector def font_transfor(translate_text, html, url): """ 網頁上使用字體文件的文本轉換 :param translate_text: 需要轉換的文本 :param html: 經過scrapy.Selector()轉換後的響應對象 :param url: 請求的url :return: 轉換後的文本 """ # 獲取字體名所在標籤文本 font_interface_str = html.xpath("//div[@class='title']/h1/@class").extract_first() if font_interface_str and 'title-name' in font_interface_str: # 提取字體文件名稱 font_interface_name = font_interface_str.replace('title-name', '').strip() else: # 如果沒有title-name表示沒有應用字體文件 print('沒有提取到字體名所在標籤文本,url:{0}'.format(html.url)) return file_name = url.rsplit("/", 1)[-1] + '_' + font_interface_str # 拼接字體文件 在本地保存路徑 file_ttf = '/tmp/renrenche_font/{0}.woff'.format(file_name) if not os.path.exists(os.path.dirname(file_ttf)): os.makedirs(os.path.dirname(file_ttf)) # 下載字體文件,保存到本地路徑 url = 'https://misc.rrcimg.com/ttf/{0}.woff'.format(font_interface_name) resp = requests.get(url) if not resp: print('請求字體文件失敗') try: font = TTFont(io.BytesIO(resp.content)) except Exception as e: return font_obj = font['cmap'] font_tables = font['cmap'].tables uni_list = font['cmap'].tables[0].ttFont.getGlyphOrder() # 生成轉換規則 base_num_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] base_eng_list = {'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9'} mapping_list = [base_eng_list[_] for _ in uni_list[1:]] font_dict = dict(zip(mapping_list, base_num_list)) # 對需要轉換的文本進行轉換 transfor_str = [_ if not _.isdigit() else font_dict[_] for _ in translate_text] # 關閉字體文件 font.close() # 返回正確的文本 return ''.join(transfor_str) def get_renrenche_detail(): # 請求頁面獲取響應 detail_url = 'https://www.renrenche.com/bj/car/c2d4fdd2902df36b' response = requests.get(detail_url) if not response or response.status_code != 200: print('人人車詳情頁請求失敗') return # 將響應文本轉爲lxml對象,爲了方便,使用的是scrapy.Selector() html = Selector(text=response.text) # 獲取標題,並規格化標題文本 titles = html.xpath("//div[@class='title']/h1/text()").extract() title = ' '.join(titles).replace('\n', '').strip() # 轉換標題文字,獲取正確文本 true_title = font_transfor(title, html, detail_url) # 打印文本 print(true_title) if __name__ == '__main__': # 請求人人車的二手車詳情頁 get_renrenche_detail()
測試代碼運行結果: