Python反反爬系列(一)----K近邻算法与CSS动态字体加密
?聲明:文章僅源自個人興趣愛好,不涉及他用,侵權(quán)聯(lián)系刪。
網(wǎng)站不好直接給出,給出論壇無法過審,觀看破解過程即可。
1.字體反爬
? ? ? ? 字體反爬也就是自定義字體加密映射,通過調(diào)用自定義的字體文件來渲染網(wǎng)頁中的文字,而網(wǎng)頁中的文字不再是文字,而是相應(yīng)的字體編碼,通過復(fù)制或者簡單的采集是無法采集到編碼后的文字內(nèi)容的。
2.查看字體軟件font creator?點(diǎn)我下載,也可不下載,借助網(wǎng)頁版工具
3.CSS處理前后的字體
我們看到的網(wǎng)頁上的數(shù)據(jù)是正常的
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
但是當(dāng)我們打開開發(fā)者工具檢查字體時 ,金額和票房數(shù)據(jù)卻變成了類似亂碼的字符
? ? ? ? ? ? ? ? ? ? ?
我們再檢查網(wǎng)頁源碼,發(fā)現(xiàn)數(shù)據(jù)和上面的都不一樣,而且每次請求金額還被加密成不同的密文
? ? ? ? ? ??
? ?多次請求,發(fā)現(xiàn)返回的字體文件重復(fù)概率太低(仔細(xì)觀察是有的,就是少)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
4.解決思路
? ? ? ?了解CSS 的應(yīng)該會知道(我不知道),CSS 中有一個 @font-face,它允許網(wǎng)頁開發(fā)者為其網(wǎng)頁指定在線字體。原本是用來消除對用戶電腦字體的依賴,現(xiàn)在有了新作用——字體反爬。具體的請查看??https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face?再觀察源碼中的數(shù)據(jù),像是編碼過后的數(shù)據(jù)。
? ? ? 仔細(xì)觀察發(fā)現(xiàn)是一些特定span中的數(shù)據(jù)經(jīng)過處理,如下圖
? ? ? ? ? ? ? ? ?
? ? ? 所以我們就查找該class名,找到了其字體樣式
? ? ??
其中的woff就是字體文件,還有其他的,比如ttf字體,woff2,svg等,這里僅有woff?,可在font欄查看
? ? ? ? ? ? ? ?
將該字體下載過來,在json字體編輯器中打開,https://font.qqe2.com/,可看到字體,多次刷新的話同樣的數(shù)字還不一樣
我們再次拿處理前后的部分?jǐn)?shù)字拿來進(jìn)行對比:
最初數(shù)字 2 4 0 1 . 3 加密后     .  字體中 $E290 $ED17 $F1A7 $EFBD $EFBDuniE290 uniED17 uniF1A7 uniEFBD uniEFBD發(fā)現(xiàn)規(guī)律了吧,但是我們知道每次數(shù)字位置都是動態(tài)...?
5.用TTfont把woff文件轉(zhuǎn)化成xml文件
先將字體轉(zhuǎn)化成xml文件。
import requests from fontTools.ttLib import TTFont def woff_xml():url = "https://vfile.meituan.net/colorstone/167b59ea53b59e17be72018703b759c32284.woff"woff_dir = r"./colorstone/"file_name = url.split("/")[-1]xml_name = file_name.replace(file_name.split(".")[-1], "xml")save_woff = file_namesave_xml = xml_nameresp = requests.get(url=url)with open(woff_dir+save_woff, "wb") as f:f.write(resp.content)f.close()font = TTFont(woff_dir+save_woff)font.saveXML(woff_dir+save_xml)轉(zhuǎn)換成的數(shù)據(jù)如圖:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ??
仔細(xì)查看后,確定和我們字體相關(guān)的標(biāo)簽:<GlyphOrder></GlyphOrder> 和 <glyf><TTGlyph/></glyf>,其中<GlyphOrder></GlyphOrder>標(biāo)簽中的數(shù)據(jù)在上圖,我們對<TTGlyph>進(jìn)行查看:
? ? ? ? ? ? ? ? ? ? ? ?
其中有x,y,Xmin,Ymin,Xmax,Ymax等值,很明顯是一些坐標(biāo)點(diǎn)的信息,其實他就是確定字體形狀的坐標(biāo),不信我們可以畫一下:
import matplotlib.pyplot as plt import re str = """" <contour>相應(yīng)內(nèi)容復(fù)制上來 """ x = [int(i) for i in re.findall(r'<pt x="(.*?)" y=', str)] y = [int(i) for i in re.findall(r'y="(.*?)" on=', str)] plt.plot(x, y) plt.show()我們還可以多測試幾個XML文件的相同數(shù)字的坐標(biāo)
? ?
對比? ? ? ??? 和6稍微有點(diǎn)差異,可Ta就是6。
網(wǎng)上最初的方法到此都是先抓取一份字體,構(gòu)建基準(zhǔn)字體庫,來進(jìn)行編碼對比,相同的則為同一個,但顯然現(xiàn)在是不行的。
其實借助坐標(biāo)圖,再仔細(xì)觀察相同數(shù)字的坐標(biāo),可以看出,相同數(shù)字的坐標(biāo)x,y的差異并不大,根據(jù)同一個字體的坐標(biāo)差異,其實就可以確定這個數(shù)字了。因為存在負(fù)數(shù),所以通過abs函數(shù)取絕對值,偽代碼
#對比兩個坐標(biāo)的差異 def compare(AA, BB):for i in range(5):if abs(AA[i][0] - BB[i][0]) < 80 and abs(AA[i][1] - BB[i][1]) < 80:passelse:return Falsereturn True #True則可視為是同一個字最后發(fā)現(xiàn)也是不行的,直到看到一篇博客說可以用K近鄰算法,突然恍然大悟,確實很適合這個問題,關(guān)于K近鄰,參考文章
K-近鄰算法算法及其實戰(zhàn)
6.Python代碼解決:
? ? 6.1.先獲取10套字體作為樣本
def save_ten_woff(self):'''獲取10套基準(zhǔn)字體,最初是存到XML中的,后面發(fā)現(xiàn)沒必要:return: None'''for i in range(0,10):#獲取10套字體作為基準(zhǔn)字體time.sleep(1)res = requests.get(url=self.start_url,headers=self.headers,proxies=self.proxies)res.encoding = "utf-8"part_font_url = re.findall(r"url\('(.{,100}?\.woff)",res.text,re.S)#請求一次獲得部分urlif part_font_url:font_url = "https:" + part_font_url[0]file_name = str(i+1)+".woff" #字體文件1.woffsave_woff = file_nameresp = requests.get(url=font_url,proxies=self.proxies)try:with open(r"./colorstone/" + save_woff, "wb") as f:#將woff文件保存f.write(resp.content)f.close()# font = TTFont(r"./colorstone/" + save_woff)# font.saveXML(r"./colorstone/base" + str(i+1)+ ".xml") #保存為base1.xml這樣的文件名print("第{}套基準(zhǔn)字體保存完畢!".format((i+1)))except Exception as e:print(e)else:print("第{}次請求失敗,請檢查網(wǎng)站是否禁止訪問等".format((i+1)))?6.2.提取樣本字體中的數(shù)字 + 坐標(biāo):
def base_font(self):'''獲取10套基準(zhǔn)字體中數(shù)字對應(yīng)的x,y值:return: None'''# 查看10套基準(zhǔn)字體, 獲取數(shù)字順序# base_num1 = [3,8,9,2,0,1,7,5,4,6]# base_num2 = [3,6,5,2,4,8,9,1,7,0]# base_num3 = [6,0,4,8,1,9,5,2,3,7]# base_num4 = [1,8,2,5,7,9,4,6,3,0]# base_num5 = [0,9,8,6,1,4,7,3,2,5]# base_num6 = [9,7,5,8,3,4,6,1,2,0]# base_num7 = [6,5,9,4,0,2,8,3,1,7]# base_num8 = [6,5,1,0,4,7,8,2,9,3]# base_num9 = [0,6,9,5,3,8,4,1,2,7]# base_num10 = [0,6,2,8,5,9,5,3,1,7]base_num = [[3,8,9,2,0,1,7,5,4,6],[3,6,5,2,4,8,9,1,7,0],[6,0,4,8,1,9,5,2,3,7],[1,8,2,5,7,9,4,6,3,0],[0,9,8,6,1,4,7,3,2,5],[9,7,5,8,3,4,6,1,2,0],[6,5,9,4,0,2,8,3,1,7],[6,5,1,0,4,7,8,2,9,3],[0,6,9,5,3,8,4,1,2,7],[0,6,2,8,5,9,5,3,1,7]]num_coordinate = []for i in range(0,10):woff_path = "./colorstone/"+str(i+1)+".woff"font = TTFont(woff_path)obj1 = font.getGlyphOrder()[2:] #過濾到前兩個不需要的for j, g in enumerate(obj1):coors = font['glyf'][g].coordinatescoors = [_ for c in coors for _ in c]coors.insert(0, base_num[i][j])num_coordinate.append(coors)return num_coordinate6.3. 在函數(shù)knn(self)中:
? ? 6.3.1? 獲取特征值,目標(biāo)值
num_coordinate = self.base_font() data = pd.DataFrame(num_coordinate)data = data.fillna(value=0) x = data.drop([0],axis=1)y = data[0]? ? 6.3.2 進(jìn)行數(shù)據(jù)的分割:訓(xùn)練集和測試集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)? ? 6.3.3 調(diào)用KNN算法(這里n的參數(shù)由網(wǎng)格驗證得出,最優(yōu)參數(shù)為1):
knn = KNeighborsClassifier(n_neighbors=1) knn.fit(x_train, y_train)?
? 6.4.建立映射,將數(shù)字和對應(yīng)的編碼建成字典形式:
def get_map(self):font = TTFont("./colorstone/target.woff")glyf_order = font.getGlyphOrder()[2:]info = []for g in glyf_order:coors = font['glyf'][g].coordinatescoors = [_ for c in coors for _ in c]info.append(coors)print(info)knn,length = self.knn()df = pd.DataFrame(info)data = pd.concat([df, pd.DataFrame(np.zeros((df.shape[0], length - df.shape[1])), columns=range(df.shape[1], length))])data = data.fillna(value=0)y_predict = knn.predict(data)num_uni_dict = {}for i, uni in enumerate(glyf_order):num_uni_dict[uni.lower().replace('uni', '&#x') + ';'] = str(y_predict[i])return num_uni_dict? 6.5.采集數(shù)據(jù)并替換,獲取正確數(shù)據(jù):
根據(jù)網(wǎng)頁結(jié)構(gòu),提取數(shù)據(jù):
def get_info(self):res = requests.get(url=self.start_url, headers=self.headers)res.encoding = "utf-8"part_font_url = re.findall(r"url\('(.{,100}?\.woff)", res.text, re.S)# 請求一次獲得部分urlif part_font_url:font_url = "https:" + part_font_url[0]resp = requests.get(url=font_url,proxies=self.proxies)with open(r"./colorstone/target.woff", "wb") as f: # 保存需要分析的字體文件f.write(resp.content)f.close()html = res.textmap_dict = self.get_map()for uni in map_dict.keys():html = html.replace(uni, map_dict[uni])parse_html = etree.HTML(html)for i in range(0,11):name = parse_html.xpath('//dd[{}]//p[@class="name"]/a/@title'.format(i))star = parse_html.xpath('//dd[{}]//p[@class="star"]/text()'.format(i))releasetime = parse_html.xpath('//dd[{}]//p[@class="releasetime"]/text()'.format(i))realtime_amount= parse_html.xpath('//dd[{}]//p[@class="realtime"]//text()'.format(i))total_amount = parse_html.xpath('//dd[{}]//p[@class="total-boxoffice"]//text()'.format(i))print("".join(name)," ","".join(star)," ","".join(releasetime),"".join(realtime_amount).replace(" ","").replace("\n",""),"".join(total_amount).replace(" ",""))打印結(jié)果
對比原網(wǎng)頁
? ? ? ? ??
數(shù)據(jù)完全是一樣的,此次動態(tài)字體反爬到此就結(jié)束了。
參考:
https://www.cnblogs.com/shenyiyangle/p/10711065.html
https://cloud.tencent.com/developer/article/1525768
https://cloud.tencent.com/developer/article/1553787
?
總結(jié)
以上是生活随笔為你收集整理的Python反反爬系列(一)----K近邻算法与CSS动态字体加密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven 依赖树
- 下一篇: Python——使用OGR操作矢量数据