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