一個(gè)偶然的機(jī)會(huì)襟铭,一個(gè)朋友讓我?guī)退纯茨硞€(gè)網(wǎng)站的反爬機(jī)制犀盟,他弄了大半天都沒(méi)有解決,這個(gè)網(wǎng)站就是奇聞007搪桂,一開(kāi)始我也沒(méi)搞明白它的反爬機(jī)制,后來(lái)經(jīng)過(guò)仔細(xì)分析調(diào)試發(fā)現(xiàn)它的反爬機(jī)制是字體反爬盯滚,一個(gè)簡(jiǎn)單而又特殊的字體反爬踢械,為什么說(shuō)它簡(jiǎn)單而又特殊呢?因?yàn)樗亲煮w反爬但是它的字體文件的編碼不會(huì)隨著頁(yè)面的刷新而刷新淌山,但是它的字有點(diǎn)多278個(gè)字裸燎,特殊在于它不像我們常見(jiàn)的反爬網(wǎng)頁(yè)源代碼顯示的是編碼而是中文漢字,這樣就起到了混淆的作用泼疑。
目標(biāo)分析
我們先打開(kāi)奇聞007的一個(gè)頁(yè)面德绿,檢查一下元素看看它的反爬現(xiàn)象:
信
字,頁(yè)面看到的和渲染的是一樣会油。我們?cè)倏纯淳W(wǎng)頁(yè)原代碼:article
發(fā)現(xiàn):article
相關(guān)的請(qǐng)求雖然有幾個(gè)翻翩,但是仔細(xì)分析都不是我們想要的(不是解密相關(guān))都许。我們?cè)倏纯凑?qǐng)求中的js請(qǐng)求,看看能否找到加密解密的相關(guān)js:Toggle JavaScript
,通過(guò)它我們可以攔截頁(yè)面所有的js請(qǐng)求桨仿,我們測(cè)試一下是不是不需要js這個(gè)頁(yè)面也是能夠正常顯示:hansansjm.ttf
钱雷,難道是字體反爬?不像啊吹零,以前做過(guò)字體反爬罩抗,字體反爬的網(wǎng)頁(yè)原代碼顯示都是
的方式,而瀏覽器Elements里顯示?瘪校,難道是特殊的字體反爬澄暮?抱著嘗試的態(tài)度下載這個(gè)字體文件然后用百度FontEditor打開(kāi)查看:直
字對(duì)應(yīng)頁(yè)面顯示的相
字,我們看看這幾頁(yè)有沒(méi)有相
字:相
字伸辟,它的編碼是$76F4
,我們?cè)倏纯雌渌囊恍┐a中和顯示結(jié)果不一樣的字是否能在字體文件中找到馍刮,結(jié)果發(fā)現(xiàn)都能找到信夫,那就說(shuō)明這個(gè)反爬還真是字體反爬。
按照之前做過(guò)的字體反爬經(jīng)驗(yàn)卡啰,我們每刷新一下字體文件里文字對(duì)應(yīng)的編碼都是會(huì)變的静稻,我們也刷新一下頁(yè)面然后再看看字體文件的字以及它們對(duì)應(yīng)的編碼會(huì)不會(huì)改變,經(jīng)過(guò)多次嘗試發(fā)現(xiàn)它的字體文件里面的字以及字對(duì)應(yīng)的編碼是不會(huì)變的匈辱。
這就簡(jiǎn)單了振湾,我們直接把每個(gè)字的映射關(guān)系弄出來(lái),然后在頁(yè)面替換就能獲取正常的的數(shù)據(jù)了亡脸,可是有這么多字押搪,現(xiàn)在我們只知道部分字的對(duì)應(yīng)關(guān)系,比如直
對(duì)應(yīng)相
浅碾,其他的不知道大州,這樣就很難做映射,而且有個(gè)疑問(wèn)為什么是直
對(duì)應(yīng)相
而不是其他什么字垂谢?它們之間有什么聯(lián)系厦画?
我們可以使用Python的fonttools庫(kù)把字體和編碼的映射弄出來(lái),比如相
字我們可以找出它的編碼0x76f4
滥朱,0x76f4
跟直
有什么關(guān)系根暑,憑借著以前的經(jīng)驗(yàn),我推斷它可能是Unicode編碼轉(zhuǎn)為字符徙邻,我們使用js的String.fromCharCode()
方法或者Python的chr()
方法試一下:
解密
按照上面的分析我們只需要下載字體文件,使用fonttools庫(kù)把字體和編碼的映射弄出來(lái)鹃栽,我們?cè)偈謩?dòng)把這278個(gè)字手動(dòng)敲出來(lái),再用chr()
方法把真正的映射關(guān)系弄出來(lái)躯畴,最后替換文字民鼓,就可以提取數(shù)據(jù)了。
from fontTools.ttLib import TTFont
import requests
from lxml import etree
# 可以是.ttf類型的字體文件也可以是.woff類型的字體文件
font = TTFont('hansansjm.ttf')
# 手動(dòng)把字體文件中的字寫下來(lái)
font_data = ['萬(wàn)', '三', '上', '下', '不', '與', '世', '東', '兩', '個(gè)', '中', '為', '主', '麗', '么', '之', '樂(lè)', '也', '了', '事', '二',
'五', '些', '親', '人', '什', '今', '他', '代', '以', '們', '會(huì)', '傳', '位', '體', '何', '作', '你', '值', '做', '像', '兒',
'元', '光', '入', '全', '公', '關(guān)', '內(nèi)', '寫', '冰', '出', '分', '劉', '到', '前', '劇', '力', '動(dòng)', '十', '千', '卻', '原',
'去', '友', '發(fā)', '變', '古', '只', '可', '吃', '合', '同', '名', '后', '嗎', '吳', '員', '和', '四', '回', '因', '國(guó)', '圖',
'圈', '在', '地', '場(chǎng)', '型', '外', '多', '大', '天', '太', '夫', '頭', '奇', '女', '她', '好', '如', '媽', '妻', '娛', '婚',
'子', '學(xué)', '孩', '寶', '實(shí)', '家', '對(duì)', '將', '小', '少', '就', '山', '歲', '已', '巴', '帥', '年', '底', '度', '開(kāi)', '張',
'當(dāng)', '影', '很', '得', '心', '性', '怪', '情', '驚', '想', '意', '感', '戲', '成', '我', '房', '手', '打', '拍', '排', '新',
'方', '無(wú)', '日', '時(shí)', '明', '星', '是', '曝', '曾', '最', '月', '有', '服', '本', '機(jī)', '李', '來(lái)', '楊', '林', '果', '樣',
'榜', '次', '死', '母', '比', '民', '氣', '水', '沒(méi)', '法', '活', '海', '清', '游', '演', '火', '點(diǎn)', '熱', '然', '照', '愛(ài)',
'片', '物', '特', '狗', '王', '現(xiàn)', '球', '生', '用', '電', '男', '界', '白', '百', '的', '直', '相', '看', '真', '眼', '著',
'知', '神', '種', '秘', '稱', '穿', '竟', '笑', '第', '粉', '紅', '經(jīng)', '結(jié)', '給', '網(wǎng)', '美', '老', '而', '能', '臉', '自',
'色', '藝', '花', '英', '行', '衣', '被', '裝', '西', '要', '見(jiàn)', '視', '認(rèn)', '讓', '說(shuō)', '誰(shuí)', '走', '趙', '起', '超', '路',
'身', '車', '過(guò)', '還', '這', '造', '道', '遭', '部', '都', '里', '重', '金', '長(zhǎng)', '陳', '面', '穎', '顏', '食', '馬', '高',
'魚', '黃', '黑', '龍', '一']
# 編碼和文字之間的映射關(guān)系
name_font = {name:fonts for name, fonts in zip(font.getGlyphOrder()[1:],font_data)}
# 頁(yè)面字和文字的映射
code_font = {chr(code):name_font[name] for code, name in font.getBestCmap().items() if name in name_font }
"""
完整的映射關(guān)系(code_font變量)蓬抄,字典的key是網(wǎng)頁(yè)源代碼中的文字丰嘉,value是頁(yè)面顯示的文字
{'?': '比', '?': '氣', '?': '視', '?': '過(guò)', '?': '陳', '?': '馬', '?': '高', '?': '黃', '?': '黑', '?': '一', '?': '萬(wàn)', '?': '五', '?': '什', '?': '元', '?': '全', '?': '動(dòng)', '?': '千', '?': '天', '?': '她', '?': '學(xué)', '?': '少', '?': '歲', '?': '性', '?': '打', '?': '無(wú)', '?': '日', '?': '時(shí)', '?': '有', '?': '民', '?': '水', '?': '沒(méi)', '?': '點(diǎn)', '?': '物', '?': '用', '?': '電', '?': '百', '?': '美', '?': '而', '?': '能', '?': '色', '?': '藝', '?': '衣', '?': '被', '?': '趙', '?': '車', '?': '重', '?': '長(zhǎng)', '?': '穎', '?': '馬', '?': '魚', '?': '龍', '一': '萬(wàn)', '萬(wàn)': '三', '三': '上', '上': '下', '下': '不', '不': '與', '與': '世', '世': '東', '東': '兩', '兩': '個(gè)', '個(gè)': '中', '中': '為', '為': '主', '主': '麗', '麗': '么', '么': '之', '之': '樂(lè)', '樂(lè)': '也', '也': '了', '了': '事', '事': '二', '二': '五', '五': '些', '些': '親', '親': '人', '人': '什', '什': '今', '今': '他', '他': '代', '代': '以', '以': '們', '們': '會(huì)', '會(huì)': '傳', '傳': '位', '位': '體', '體': '何', '何': '作', '作': '你', '你': '值', '值': '做', '做': '像', '像': '兒', '兒': '元', '元': '光', '光': '入', '入': '全', '全': '公', '公': '關(guān)', '關(guān)': '內(nèi)', '內(nèi)': '寫', '寫': '冰', '冰': '出', '出': '分', '分': '劉', '劉': '到', '到': '前', '前': '劇', '劇': '力', '力': '動(dòng)', '動(dòng)': '十', '十': '千', '千': '卻', '卻': '原', '原': '去', '去': '友', '友': '發(fā)', '發(fā)': '變', '變': '古', '古': '只', '只': '可', '可': '吃', '吃': '合', '合': '同', '同': '名', '名': '后', '后': '嗎', '嗎': '吳', '吳': '員', '員': '和', '和': '四', '四': '回', '回': '因', '因': '國(guó)', '國(guó)': '圖', '圖': '圈', '圈': '在', '在': '地', '地': '場(chǎng)', '場(chǎng)': '型', '型': '外', '外': '多', '多': '大', '大': '天', '天': '太', '太': '夫', '夫': '頭', '頭': '奇', '奇': '女', '女': '她', '她': '好', '好': '如', '如': '媽', '媽': '妻', '妻': '娛', '娛': '婚', '婚': '子', '子': '學(xué)', '學(xué)': '孩', '孩': '寶', '寶': '實(shí)', '實(shí)': '家', '家': '對(duì)', '對(duì)': '將', '將': '小', '小': '少', '少': '就', '就': '山', '山': '歲', '歲': '已', '已': '巴', '巴': '帥', '帥': '年', '年': '底', '底': '度', '度': '開(kāi)', '開(kāi)': '張', '張': '當(dāng)', '當(dāng)': '影', '影': '很', '很': '得', '得': '心', '心': '性', '性': '怪', '怪': '情', '情': '驚', '驚': '想', '想': '意', '意': '感', '感': '戲', '戲': '成', '成': '我', '我': '房', '房': '手', '手': '打', '打': '拍', '拍': '排', '排': '新', '新': '方', '方': '無(wú)', '無(wú)': '日', '日': '時(shí)', '時(shí)': '明', '明': '星', '星': '是', '是': '曝', '曝': '曾', '曾': '最', '最': '月', '月': '有', '有': '服', '服': '本', '本': '機(jī)', '機(jī)': '李', '李': '來(lái)', '來(lái)': '楊', '楊': '林', '林': '果', '果': '樣', '樣': '榜', '榜': '次', '次': '死', '死': '母', '母': '比', '比': '民', '民': '氣', '氣': '水', '水': '沒(méi)', '沒(méi)': '法', '法': '活', '活': '海', '海': '清', '清': '游', '游': '演', '演': '火', '火': '點(diǎn)', '點(diǎn)': '熱', '熱': '然', '然': '照', '照': '愛(ài)', '愛(ài)': '片', '片': '物', '物': '特', '特': '狗', '狗': '王', '王': '現(xiàn)', '現(xiàn)': '球', '球': '生', '生': '用', '用': '電', '電': '男', '男': '界', '界': '白', '白': '百', '百': '的', '的': '直', '直': '相', '相': '看', '看': '真', '真': '眼', '眼': '著', '著': '知', '知': '神', '神': '種', '種': '秘', '秘': '稱', '稱': '穿', '穿': '竟', '竟': '笑', '笑': '第', '第': '粉', '粉': '紅', '紅': '經(jīng)', '經(jīng)': '結(jié)', '結(jié)': '給', '給': '網(wǎng)', '網(wǎng)': '美', '美': '老', '老': '而', '而': '能', '能': '臉', '臉': '自', '自': '色', '色': '藝', '藝': '花', '花': '英', '英': '行', '行': '衣', '衣': '被', '被': '裝', '裝': '西', '西': '要', '要': '見(jiàn)', '見(jiàn)': '視', '視': '認(rèn)', '認(rèn)': '讓', '讓': '說(shuō)', '說(shuō)': '誰(shuí)', '誰(shuí)': '走', '走': '趙', '趙': '起', '起': '超', '超': '路', '路': '身', '身': '車', '車': '過(guò)', '過(guò)': '還', '還': '這', '這': '造', '造': '道', '道': '遭', '遭': '部', '部': '都', '都': '里', '里': '重', '重': '金', '金': '長(zhǎng)', '長(zhǎng)': '陳', '陳': '面', '面': '穎', '穎': '顏', '顏': '食', '食': '馬', '馬': '高', '高': '魚', '魚': '黃', '黃': '黑', '黑': '龍', '龍': '一', '老': '而', '路': '身', '不': '與', '力': '動(dòng)', '年': '底', '里': '重', '林': '果', '什': '今', '行': '衣'}
"""
# 定義一個(gè)文字替換的函數(shù)
def replace_word(row,replace_dict):
result = []
for word in row:
if word in replace_dict.keys():
result.append(replace_dict[word])
else:
result.append(word)
return ''.join(result)
# 請(qǐng)求的url地址
url = "http://www.qiwen007.com/zbwz/366283.html"
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
}
response = requests.get(url, headers=headers)
html = etree.HTML(response.text)
# 源文章標(biāo)題
row_title_list = html.xpath('//div[@class="article_main_right"]/h1/text()')
# 源文章內(nèi)容
row_content_list = html.xpath('//div[@class="article"]//p/text()')
if row_title_list:
row_title = row_title_list[0]
print(replace_word(row_title,code_font ))
else:
print('沒(méi)有提取到數(shù)據(jù)')
if row_content_list:
row_content = '\n'.join(row_content_list)
print(replace_word(row_content ,code_font ))
else:
print('沒(méi)有提取到數(shù)據(jù)')
結(jié)果: