天天看點

woff字型反爬實戰,10分鐘就能學會

聲明:本文章僅是用于學習用途,請勿與用于惡意破壞别人網站,本人不承擔法律責任。

來繼續學爬蟲呀!

前言

簡單描述一下這種手段,html源碼的數字跟頁面展示的數字是不一緻的!當時就一臉黑人問号,嗯???

經過分析,目前這種字型反爬機制是:通過擷取指定連結的woff字型檔案,然後根據html源碼的數字
去woff字型檔案裡面查找真正的數字,講到底就是一個映射關系/查找字典。如html源碼是123,去woff檔案裡面
查找出來的是:623。好了,看到這裡,你一定想說:廢話講那麼多幹嘛?趕緊上教程啊!!
           
那先來看一下大緻流程呗:
woff字型反爬實戰,10分鐘就能學會

分析目标網站頁面(在這裡我不打算貼出網站位址,請大家自己找網站練習),這裡看到html源碼和頁面展示的數字是不一緻的,如下圖:

woff字型反爬實戰,10分鐘就能學會
tips:
一開始不知道是怎麼下手,隻能谷歌搜尋字型反爬,一搜果然很多說法,有說woff檔案的、有說CSS的、還有說svg曲線啥的,
然後我就去檢視Network裡面的All,就發現關鍵字眼woff,就開始猜測可能是屬于這種類型的反爬手段,接着開始幹活。
           
混淆前字型:
woff字型反爬實戰,10分鐘就能學會
混淆後的字型:
woff字型反爬實戰,10分鐘就能學會

找了一會,發現.woff2檔案和woff檔案前後不一樣,然後開始着手解決

如需下載下傳woff檔案,請點選這裡, 提取碼: ghnx

但是本地打不開woff字型檔案,需要借助的軟體是fontcreator,這個你自己去找一下,很多破解的

woff字型反爬實戰,10分鐘就能學會

但是這好像看不出什麼,然後我們接着需要從另外一方面下手,重點來了》将woff檔案轉換為xml檔案

如下:

import os
import requests
from fontTools.ttLib import TTFont

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

url = "http://xxxxxx.xxx.woff"
    
woff_dir = os.path.join(base_dir, "statics/woffs/")
file_name = url.split("/")[-1]
xml_name = file_name.replace(file_name.split(".")[-1], "xml")
save_woff = os.path.join(woff_dir, file_name)
save_xml = os.path.join(woff_dir, xml_name)

resp = requests.get(url="xxx")
with open(save_woff, "wb") as f:
    f.write(resp.content)
    f.close()
font = TTFont(save_woff)
font.saveXML(save_xml)  # 轉換為xml檔案
           

然後打開xml檔案看,先來檢視一下縮略的内容,紅色圈圈的那兩個是本次重點破解的分析的内容:

woff字型反爬實戰,10分鐘就能學會

然後先檢視cmap,發現線索,裡面注釋的地方有标注了。然後我們大膽猜測:NINE對應的name=cid00018,code=0x39,這翻譯過來就是9對應的name=cid00018,其id标記為0x39:

woff字型反爬實戰,10分鐘就能學會

接着來看一下code=0x39,其對應的name=cid00018,然後我們拿這個cid00018去搜尋,發現在部分裡面看到:

<GlyphID id="3" name="cid00018"/>

,這表明什麼呢?結合前後兩個映射關系,然後連起來再大膽猜測一下,可能是9對應3?

woff字型反爬實戰,10分鐘就能學會

為了驗證這個猜想,繼續再找一下其他例子,我使用已經轉換為如下格式,友善你們對比,你們也可以從三張截圖來對比,哪三張截圖呢?分别是:①是前面包含“code=0x39,name=cid00018”的截圖;②是包含“id=3,name=cid00018”的截圖;③是文章的第二張截圖。

你們可以①②截圖來一個個列出映射關系,建議先列出①的映射關系,再列出②的映射關系,然後再将①、②的映射關系組合起來,得出一個新的映射關系,這個新的映射關系就是我們所需的,下面來給你們看一下我提取的①、②的映射關系:

①的映射關系,在這裡我定義為before_code_id
②的映射關系,在這裡我定義為affter_code_id,結果如下:
before_code_id =  {
    "0": "cid00019",
    "1": "cid00020",
    "2": "cid00017",
    "3": "cid00021",
    "4": "cid00022",
    "5": "cid00024",
    "6": "cid00026",
    "7": "cid00025",
    "8": "cid00023",
    "9": "cid00018"
}
affter_code_id = {
    "cid00017": 2,
    "cid00018": 3,
    "cid00019": 4,
    "cid00020": 5,
    "cid00021": 6,
    "cid00022": 7,
    "cid00023": 8,
    "cid00024": 9,
    "cid00025": 10,
    "cid00026": 11
}

然後從html源碼到before_code_id, affter_code_id應用起來就是如下:
前端數字—中間人code—最終的數字,即:
"0"——"cid00019"——4
"1"——"cid00020"——5
"2"——"cid00017"——2
"3"——"cid00021"——6
"4"——"cid00022"——7
"5"——"cid00024"——9
"6"——"cid00026"——11
"7"——"cid00025"——10
"8"——"cid00023"——8
"9"——"cid00018——3

我們再簡化一步,直接從html源碼數字到最終的數字映射為如下(即直接省去中間的cidxxxxx這串):
"0"——4
"1"——5
"2"——2
"3"——6
"4"——7
"5"——9
"6"——11
"7"——10
"8"——8
"9"——3


但是你們發現這映射後的數字很奇怪嗎,比如"6"、"7"映射之後分别為11和10,
但是在我們的正常邏輯之中不對呀,要不我們再列一下html源碼跟前端的肉眼看到的數字的映射關系呗:
"0"——2
"1"——3
"2"——0
"3"——4
"4"——5
"5"——7
"6"——9
"7"——8
"8"——6
"9"——1
哇,這列出來之後不是很相似嗎,跟前面的結果,要不我再放在一起給你們好對比一下呗:
xml提取的映射     html源碼跟網頁展示的,提取的映射
"0"——4				"0"——2
"1"——5				"1"——3
"2"——2				"2"——0
"3"——6				"3"——4
"4"——7				"4"——5
"5"——9				"5"——7
"6"——11				"6"——9
"7"——10				"7"——8
"8"——8				"8"——6
"9"——3				"9"——1
           

到此,我們發現從xml提取的映射跟html源碼跟網頁展示的提取的映射數值都是相差2,是以我們大膽猜測:網頁上看到的數值是可以從xml提取的映射關系裡面每個數字減去2所得的,即:

"0"——4-2=2
"1"——5--2=3
"2"——2-2=0
"3"——6-2=4
"4"——7-2=5
"5"——9-2=7
"6"——11-2=9
"7"——10-2=8
"8"——8-2=6
"9"——3-2=1
           

是以這就是破解了嘛,到此,這個教程總可以了解吧,寫得辣麼辛苦、改的辣麼辛苦,趕快評論點贊收藏一套走起來

好了,别嗨了,實操才是王道,下面來看一下核心代碼,如下:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019/8/19 13:08
# @Author  : qizai
# @File    : crawl_woff.py
# @Software: PyCharm

# 先安裝:pip3 install fontTools
import os
import requests
from fake_useragent import UserAgent
from fontTools.ttLib import TTFont  # 對字型檔案進行格式轉換

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ua = UserAgent()
header = {
    "user-agent": ua.chrome,
}


def parse_woff(url=""):
    """這裡是下載下傳字型并且解析對應的值"""
    global cookie
    global header
    
    woff_dir = os.path.join(base_dir, "statics/woffs/")
    file_name = url.split("/")[-1]
    xml_name = file_name.replace(file_name.split(".")[-1], "xml")
    save_woff = os.path.join(woff_dir, file_name)
    save_xml = os.path.join(woff_dir, xml_name)

    if os.path.exists(save_woff):  # 存在本地的話直接提取本地的檔案去解析即可省去下載下傳,避免浪費資源
        font = TTFont(save_woff)
    else:
        resp = requests.get(url=url, cookies=cookie, headers=header)
        with open(save_woff, "wb") as f:
            f.write(resp.content)
            f.close()
        font = TTFont(save_woff)
        font.saveXML(save_xml)  # 轉換為xml檔案

    cmap = font.getBestCmap()  # 這個是xml源碼裡面的【數值-中間人code】映射,數值還不一定是html源碼裡面的數值,而是每位數經過加上一定的數值之後的
    tmp = {  # 這個是對應的才是我們需要的值,或者你也可以在每次擷取的時候,将這個值對應減去48即可,就可以省去這這個映射
        48: 0,  # html源碼裡面的0對應xml源碼裡面的48
        49: 1,  # html源碼裡面的1對應xml源碼裡面的49
        50: 2,  # html源碼裡面的2對應xml源碼裡面的50
        51: 3,  # html源碼裡面的3對應xml源碼裡面的51
        52: 4,  # html源碼裡面的4對應xml源碼裡面的52
        53: 5,  # html源碼裡面的5對應xml源碼裡面的53
        54: 6,  # html源碼裡面的6對應xml源碼裡面的54
        55: 7,  # html源碼裡面的7對應xml源碼裡面的55
        56: 8,  # html源碼裡面的8對應xml源碼裡面的56
        57: 9,  # html源碼裡面的9對應xml源碼裡面的57
    }    # 注意:個人猜測以上這個tmp字典,xml源碼的數字跟html源碼數字的映射關系可能會定期改變的

    before_code_id = {}  # 轉換之後before_code_id為:1:cid00019  key就是html源碼數字,value就是用來查詢的中間人code
    for k, v in cmap.items():
        if k not in set(range(48, 58)):
            continue
        before_code_id[tmp.get(k)] = v  # 這一步其實是将49:cid00019的映射格式轉換為好了解的1:cid00019映射關系

    code_id_list = font.getGlyphOrder()[2:]  # 這個傳回的值有11個,但是我這裡隻是取了第三個到最後一個,是用來取計算前端看到的真正的數值
    affter_code_id = {k:v for k,v in zip(code_id_list, range(2, 12))}  # 将每一個按照順序映射為cid00562:2這種

    return before_code_id, affter_code_id


if __name__ == '__main__':
    """使用如下"""
    before_code_id, affter_code_id = parse_woff(url="xxxx")
    
    # html源碼數字:假設為0
    html_number = 0
    tmp_code = before_code_id.get(html_number)  # 先比對中間人code
    real_number = affter_code_id.get(tmp_code) - 2  # 再提取中間人code對應的真正的數字,記得要減去2,因為本來是每位數字已經多了2
    print("目前html源碼數字html_number:{} 真正的數字為real_number:{}".format(html_number, real_number))
           

目前的woff字型反爬已經破解了,如果有不妥的地方請指出,大家一起學習。

至此本文教程寫完了,希望能夠幫助到各位在爬蟲路上的小夥伴們,覺得不錯點個贊呗
感謝認真讀完這篇教程的您

先别走呗,這裡有可能有你需要的文章:

CSS字型反爬實戰,10分鐘就能學會;

爬蟲:js逆向目前遇到的知識點集合

python最好用的第三方庫資源下載下傳網址

詳細講解aiohttp異步請求及使用,高效率