記一次css字型反爬

李帥將發表於2020-07-26

前段時間在看css反爬的時候,發現很多網站都做了css反爬,比如,設定字型反爬的(58同城租房版塊,實習僧招聘https://www.shixiseng.com/等)設定雪碧圖反爬的(自如租房http://gz.ziroom.com/)。

還有一個網站本身是沒有其他反爬措施的,只是設定了字型反爬,但是這個網站的反爬就有些扯淡,http://www.qiwen007.com/,我們隨便點開一個文章,並開啟開發者工具

 

 

 其中的文字並不是像其他字型反爬一樣,是將某些文字轉為了Unicode顯示在原始碼中的

首先來看一下破解流程及思路:

"""
流程及思路:
1. 通過requests請求獲取響應資料,得到的就是原始碼中加密資料(雜亂的文章)
2. 將加密資料的每一個字元轉成Unicode編碼,得到字元的Unicode列表
3. 通過搜尋原始碼中font-face關鍵字,找到字型庫檔案(ttf/woff檔案)將當前域名拼接上/hansansjm.ttf,即 http://www.qiwen007.com/hansansjm.ttf,開啟後即下載
4. 使用FontCreator或者百度字型編輯器開啟ttf檔案,會看到裡面每個字元
5. 將ttf轉為xml檔案
6. 同時使用pycharm開啟xml檔案,找到 glyf 標籤下面的 TTGlyph 標籤,裡面的name 即是xml檔案中的Unicode編碼,
裡面contour下面的pt的x,y即是每個字元的座標,計算座標差(計算座標差的時候,可以直接取第一個contour的前兩組pt即可)
(但是 TTGlyph name的順序 有可能和ttf檔案裡面的字元的Unicode順序對應不上,此時就要從GlyphOrder裡面看是否對應,
如果對應,就從GlyphOrder中獲取每個xml檔案中的每個字元的Unicode列表,然後遍歷列表,從 glyf 中找到對應的座標)
7. 手動去組成漢字字元的列表,然後使用對映(dict(zip()))得到漢字和座標差的對映
8. 在第6步中我們可以獲取Unicode和座標差的對映
9. 然後回到第2步,拿到加密資料的Unicode編碼列表後,去Unicode和座標差的對映中找到對應的座標差,拿到座標差後找到對應的漢字
至此,破解過程結束
"""

這裡說明一下,為什麼加密內容轉Unicode之後不能直接用的原因,因為加密內容的Unicode和ttf檔案中的Unicode不對應

 

 可以看到在ttf檔案中萬字的編碼為uni2F00,而在Unicode線上編碼中 為\u4e07,uni 和 \u 可忽略,直接看後四位,後面程式碼中有做轉換

獲取文章加密內容,及將每個字元轉為Unicode編碼
def get_source_article():
    """獲取加密文章內容"""
    url = 'http://www.qiwen007.com/mb-db/pc-sg/zbwz/363609.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36',
    }
    resp = requests.get(url=url, headers=headers)

    html = etree.HTML(resp.text)

    p_list = html.xpath('//div[@class="article"]/p')
    for p in p_list:
        temp_article = p.xpath('./text()')
        if len(temp_article) > 0:
            print('temp_article', temp_article)
            result = to_unicode(temp_article[0])
            print('result', result)
            break


def to_unicode(temp_article):
    """將漢字全部轉為Unicode編碼"""
    bytes_article = temp_article.encode(encoding='unicode-escape')
    str_article_li = str(bytes_article)[2:-1].split('\\')
    uni_article_li = ['uni' + uni[1:].upper() for uni in str_article_li if uni != '']
print(uni_article_li)

 

將ttf檔案或者woff檔案轉為xml檔案

# 將ttf/woff檔案轉換為xml檔案(ttf/woff檔案pycharm開啟是亂碼,xml可以開啟)
from fontTools.ttLib import TTFont
font = TTFont('hansansjm.ttf')
# 此時就將ttf/woff檔案轉換為了xml檔案
font.saveXML('hansansjm.xml')

 

坑:TTGlyph name的順序 有可能和ttf檔案裡面的字元的Unicode順序對應不上

 

 

 

 

此時就要找 GlyphOrder 中的name 

 

 

組成 所有漢字和座標差的對映,和 Unicode和座標差的對映

# 此漢字列表,需要通過百度字型編輯器(線上)開啟ttf/woff檔案,或者FontCreator,將全部漢字按照順序寫到列表中
hans_list = ['', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '穿', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '西', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '',
             ]


def get_coordinate_hans_and__unicode_coordinate_map():
    """
    獲取所有漢字和座標差的對映,和 Unicode和座標差的對映,
    接下來便可以通過對映找到Unicode所對應的座標差,
    拿到座標差之後,就可以再通過coordinate_hans_map,找到具體的漢字
    """
    # 儲存該hansansjm.xml檔案中所有的字元座標差
    coordinate_diff_list = []

    # Unicode和座標差的對映
    _unicode_coordinate_map = {}

    print('>>> 開始計算xml檔案中所有字元的座標差')
    content = parse('hansansjm.xml')
    # 此處有個坑,應當先獲取GlyphOrder中的GlyphID對應的name即Unicode,這裡的Unicode是和ttf檔案中一一對應的
    # 而glyf中的TTGlyph對應的name(Unicode)不是和ttf檔案中一一對應的
    # 應該拿到GlyphOrder 下面所有的Unicode編碼後,再去 glyf中根據Unicode找對應的座標
    GlyphID_list = content.getElementsByTagName('GlyphID')
    TTGlyph_list = content.getElementsByTagName('TTGlyph')
    # 由於xml檔案中glyf下面的第一個TTGlyph 所對應的Unicode為.notdef ,要剔除掉,因此TTGlyph_list不能包含第一個元素
    # 注意:如果最後一個元素也不是Unicode編碼的,應當也要剔除掉

    # 獲取GlyphID下面所有的Unicode編碼
    GlyphID_uni_list = []
    for GlyphID in GlyphID_list[1:]:
        # 獲取Unicode
        _unicode = GlyphID.getAttribute('name')
        GlyphID_uni_list.append(_unicode)
    for uni in GlyphID_uni_list:
        for TTGlyph in TTGlyph_list[1:]:
            if uni == TTGlyph.getAttribute('name'):
                # 獲取第一個contour
                # print(TTGlyph.getElementsByTagName('contour')[0])
                first_contour = TTGlyph.getElementsByTagName('contour')[0]

                # 獲取第一個contour中的前兩個pt元素,進一步獲取這兩個元素的x,y屬性,便於計算座標差
                first_pt = first_contour.getElementsByTagName('pt')[0]
                first_pt_x = int(first_pt.getAttribute('x'))
                first_pt_y = int(first_pt.getAttribute('y'))
                # print(first_pt_x, first_pt_y)

                second_pt = first_contour.getElementsByTagName('pt')[1]
                second_pt_x = int(second_pt.getAttribute('x'))
                second_pt_y = int(second_pt.getAttribute('y'))
                # print(second_pt_x, second_pt_y)

                # 計算座標差
                coordinate_diff = (second_pt_x - first_pt_x, second_pt_y - first_pt_y)
                # print(coordinate_diff)
                coordinate_diff_list.append(coordinate_diff)
                # break
                _unicode_coordinate_map[uni] = coordinate_diff

            # print(coordinate_diff_list)

    # 將座標差和漢字組成字典,完成對映
    coordinate_hans_map = dict(zip(coordinate_diff_list, hans_list))
    print(coordinate_hans_map)
    print(_unicode_coordinate_map)
    return coordinate_hans_map, _unicode_coordinate_map

 

完整程式碼如下:

import requests
from lxml import etree
from fontTools.ttLib import TTFont
from xml.dom.minidom import parse

"""
此案例是破解css反爬 網站:http://www.qiwen007.com
經過檢視網站頁面原始碼後發現,tff文字型檔是 '/hansansjm.ttf',因此判定該網站的文字型檔不會自動變換

woff/ttf檔案樣式檢視(線上) http://fontstore.baidu.com/static/editor/index.html
也可以使用FontCreator(下載地址) https://www.onlinedown.net/soft/88758.htm
"""

"""
流程及思路:
1. 通過requests請求獲取響應資料,得到的就是原始碼中加密資料(雜亂的文章)
2. 將加密資料的每一個字元轉成Unicode編碼,得到字元的Unicode列表
3. 通過搜尋原始碼中font-face關鍵字,找到字型庫檔案(ttf/woff檔案)將當前域名拼接上/hansansjm.ttf,即 http://www.qiwen007.com/hansansjm.ttf,開啟後即下載
4. 使用FontCreator或者百度字型編輯器開啟ttf檔案,會看到裡面每個字元
5. 將ttf轉為xml檔案
6. 同時使用pycharm開啟xml檔案,找到 glyf 標籤下面的 TTGlyph 標籤,裡面的name 即是xml檔案中的Unicode編碼,
   裡面contour下面的pt的x,y即是每個字元的座標,計算座標差(計算座標差的時候,可以直接取第一個contour的前兩組pt即可)
 (但是 TTGlyph name的順序 有可能和ttf檔案裡面的字元的Unicode順序對應不上,此時就要從GlyphOrder裡面看是否對應,
  如果對應,就從GlyphOrder中獲取每個xml檔案中的每個字元的Unicode列表,然後遍歷列表,從 glyf 中找到對應的座標)
7. 手動去組成漢字字元的列表,然後使用對映(dict(zip()))得到漢字和座標差的對映
8. 在第6步中我們可以獲取Unicode和座標差的對映
9. 然後回到第2步,拿到加密資料的Unicode編碼列表後,去Unicode和座標差的對映中找到對應的座標差,拿到座標差後找到對應的漢字
至此,破解過程結束
"""

# 此漢字列表,需要通過百度字型編輯器(線上)開啟ttf/woff檔案,或者FontCreator,將全部漢字按照順序寫到列表中
hans_list = ['', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '穿', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '西', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '', '',
             '', '', '', '', '', '', '', '', '', '', '', '', '',
             ]


def get_result_article():
    """獲取加密文章內容"""
    url = 'http://www.qiwen007.com/mb-db/pc-sg/zbwz/363609.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36',
    }
    resp = requests.get(url=url, headers=headers)

    html = etree.HTML(resp.text)

    p_list = html.xpath('//div[@class="article"]/p')
    for p in p_list:
        temp_article = p.xpath('./text()')
        if len(temp_article) > 0:
            print('temp_article', temp_article)
            result = to_unicode(temp_article[0])
            print('result', result)
            break


def to_unicode(temp_article):
    """將漢字全部轉為Unicode編碼"""
    bytes_article = temp_article.encode(encoding='unicode-escape')
    str_article_li = str(bytes_article)[2:-1].split('\\')
    uni_article_li = ['uni' + uni[1:].upper() for uni in str_article_li if uni != '']
    # print(uni_article_li)
    return parse_uni(uni_article_li)


def parse_uni(uni_article_li):
    # 獲取座標差漢字對映,和 Unicode 座標差的對映
    get_map = get_coordinate_hans_and__unicode_coordinate_map
    coordinate_hans_map, _unicode_coordinate_map = get_map()
    result_article_content = ''
    for uni in uni_article_li:
        if uni in list(_unicode_coordinate_map.keys()):
            coordinate = _unicode_coordinate_map.get(uni)
            hans = coordinate_hans_map.get(coordinate)
            # print('1', hans)
            result_article_content += hans
        else:
            hans = chr(int(uni[3:], 16))
            # print('2', hans)
            result_article_content += hans

    return result_article_content


# 將ttf/woff檔案轉換為xml檔案(ttf/woff檔案pycharm開啟是亂碼,xml可以開啟)
def ttf_to_xml():
    font = TTFont('hansansjm.ttf')
    # 此時就將ttf/woff檔案轉換為了xml檔案
    font.saveXML('hansansjm.xml')


def get_coordinate_hans_and__unicode_coordinate_map():
    """
    獲取所有漢字和座標差的對映,和 Unicode和座標差的對映,
    接下來便可以通過對映找到Unicode所對應的座標差,
    拿到座標差之後,就可以再通過coordinate_hans_map,找到具體的漢字
    """
    # 儲存該hansansjm.xml檔案中所有的字元座標差
    coordinate_diff_list = []

    # Unicode和座標差的對映
    _unicode_coordinate_map = {}

    print('>>> 開始計算xml檔案中所有字元的座標差')
    content = parse('hansansjm.xml')
    # 此處有個坑,應當先獲取GlyphOrder中的GlyphID對應的name即Unicode,這裡的Unicode是和ttf檔案中一一對應的
    # 而glyf中的TTGlyph對應的name(Unicode)不是和ttf檔案中一一對應的
    # 應該拿到GlyphOrder 下面所有的Unicode編碼後,再去 glyf中根據Unicode找對應的座標
    GlyphID_list = content.getElementsByTagName('GlyphID')
    TTGlyph_list = content.getElementsByTagName('TTGlyph')
    # 由於xml檔案中glyf下面的第一個TTGlyph 所對應的Unicode為.notdef ,要剔除掉,因此TTGlyph_list不能包含第一個元素
    # 注意:如果最後一個元素也不是Unicode編碼的,應當也要剔除掉

    # 獲取GlyphID下面所有的Unicode編碼
    GlyphID_uni_list = []
    for GlyphID in GlyphID_list[1:]:
        # 獲取Unicode
        _unicode = GlyphID.getAttribute('name')
        GlyphID_uni_list.append(_unicode)
    for uni in GlyphID_uni_list:
        for TTGlyph in TTGlyph_list[1:]:
            if uni == TTGlyph.getAttribute('name'):
                # 獲取第一個contour
                # print(TTGlyph.getElementsByTagName('contour')[0])
                first_contour = TTGlyph.getElementsByTagName('contour')[0]

                # 獲取第一個contour中的前兩個pt元素,進一步獲取這兩個元素的x,y屬性,便於計算座標差
                first_pt = first_contour.getElementsByTagName('pt')[0]
                first_pt_x = int(first_pt.getAttribute('x'))
                first_pt_y = int(first_pt.getAttribute('y'))
                # print(first_pt_x, first_pt_y)

                second_pt = first_contour.getElementsByTagName('pt')[1]
                second_pt_x = int(second_pt.getAttribute('x'))
                second_pt_y = int(second_pt.getAttribute('y'))
                # print(second_pt_x, second_pt_y)

                # 計算座標差
                coordinate_diff = (second_pt_x - first_pt_x, second_pt_y - first_pt_y)
                # print(coordinate_diff)
                coordinate_diff_list.append(coordinate_diff)
                # break
                _unicode_coordinate_map[uni] = coordinate_diff

            # print(coordinate_diff_list)

    # 將座標差和漢字組成字典,完成對映
    coordinate_hans_map = dict(zip(coordinate_diff_list, hans_list))
    print(coordinate_hans_map)
    print(_unicode_coordinate_map)
    return coordinate_hans_map, _unicode_coordinate_map


# ttf_to_xml()
get_result_article()

 

最後說明:在我測試的時候,發現最終結果還是有一些問題,通過解密,資料還是和頁面上顯示的不太一樣,個別字元還是不對,搞不懂問題出在哪裡,歡迎大佬們指正

 

相關文章