前段時間在看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()
最后說明:在我測試的時候,發現最終結果還是有一些問題,通過解密,數據還是和頁面上顯示的不太一樣,個別字符還是不對,搞不懂問題出在哪里,歡迎大佬們指正