這兩天一直在看字體反爬方面的文章,現在難一點的還沒摸清怎麼搞,但是58的品牌公寓的字體反爬相對簡單一些,已經自己做出來了,特此記下來,也可以幫剛在這方面入門的小夥伴更快熟悉起來。整體代碼我會在文末發出來。
話不多說,開始正題
這篇文章用到的python模塊有這些
import requests
import re
import base64
from bs4 import BeautifulSoup
from fontTools.ttLib import TTFont
from xml.dom.minidom import parse
import xml.dom.minidom
打開58公寓的頁面,鏈接點這裏
頁面是這樣的
打開調試
可以看到源碼是亂碼的
用代碼跑出來之後,是這樣的
很明顯的,出現了字體反爬。一般這種反爬,網頁文件裏是會有他們自定義的字體文件的。
這種字體一般是ttf後綴或者woff後綴的。我們在網頁源碼中嘗試搜索ttf或者woff
這裏的一大堆藍色的字體就是他們的自定義字體文件了,我們可以通過正則,把base64,後面的和format前面的這些東西取出來,比如:
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
取出來之後,我們之前看到了base64,所以應該是需要base64解碼的,解碼之後其實還是亂碼,但是無所謂,我們不看,交給工具看。把解碼後的這些東西存入文件中,比如:
font_data_after_decode = base64.b64decode(ttf_text)
with open('./world.ttf', 'wb') as f:
f.write(font_data_after_decode)
存入文件之後,我們需要一個工具軟件來打開這個文件,看看是什麼。可以百度下載安裝 FontCreator。
我們打開這個文件之後,看到是這樣的
有10個數字,以及他們的對應編碼。
我們在網頁源碼中搜索編碼會發現這些編碼所對應的數字都對應網頁上已經渲染出來的正確的數字。也就是說,我們把這些編碼換成數字就是我們要的數據了。但是很遺憾,我們每次請求,所獲得的編碼與數字的對應關係是會改變的。
比如這次的9EA3對應着數字1,但是下次就是別的編碼對應的數字1.所以我們需要每次請求,都把字體文件存下來,然後尋找對應關係。但是現在這種尋找對應關係的方法,是我們用工具軟件打開,手動識別的。我們應該交給代碼去完成。所以,我們首先要用代碼找到每次請求所產生的編碼與數字的對應關係。
說了這麼多,那這個編碼該怎麼用程序找到呢。此時我們可以使用python的一個模塊,把咱們剛纔存的.ttf文件轉換成xml文件。
font = TTFont('./world.ttf')
font.saveXML('my.xml')
打開這個生成的xml文件,上面我們通過FontCreator軟件發現數字0對應的編碼是9A4B,我們在xml文件中搜9A4B。
對應的name值是glyph00001,我們在xml文件中繼續搜glyph00001
找到這裏就對了,有很多x,y,on。我們待會兒就靠這個找對應關係。那是後話,我們一步一步來。
此時要做的就是建立一個字典,把name值和數字的對應關係建立起來,我們剛纔看的,數字0對應的name值應該是glyph00001,依次類推,建立字典對應關係,像這樣:
dict={'glyph00001':'0','glyph00002':'1','glyph00003':'2','glyph00004':'3','glyph00005':'4','glyph00006':'5','glyph00007':'6','glyph00008':'7','glyph00009':'8','glyph00010':'9'}
這時,我們再一次請求網頁,把字體文件存到一個新文件裏,看看這次數字和name值之間的關係。
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
font_data_after_decode = base64.b64decode(ttf_text)
with open('./newworld.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./newworld.ttf')
font.saveXML('newmy.xml')
打開新的xml文件,並且用FontCreator軟件打開新的ttf文件。
可以看到,編碼和數字的關係已經跟第一次不一樣了。我們還是去xml文件裏找0對應的編碼9F92
對應name值是glyph00001,根據name值繼續搜
有沒有發現,x,y,on這些跟咱們第一次找到的一樣。也就是說,雖然每次請求網頁,編碼跟數字的對應關係會變,但是我們通過這個編碼找到的name值所對應的x,y,on這些是不會變的。0這個數字對應着兩個文件裏相同的x,y,on.
那麼想一下接下來要怎麼做了
我這裏有個辦法,我們接下來可以兵分兩路
第一路是不是可以拿着這一塊x,y,on去第一次的文件裏尋找到它對應的name值,這個name值對應的數字是不是已經在字典裏定義好了,就此我們找到了這塊x,y,on對應的數字。
第二路可以拿着這塊x,y,on去新文件裏找到它對應的name值,再通過這個name值就找到了它對應的編碼。
此時兩路匯合,一路拿着數字,一路拿着編碼,編碼和數字的對應關係是不是搞定了。
此時,把網頁裏的編碼換成數字就完成了此次的字體反爬。
具體代碼我發在這裏,可以結合着理解一下:
# -*- coding: utf-8 -*-
# @Author : LMD
# @FILE : 58品牌公寓.py
# @Time : 2019/12/25 10:32
# @Software : PyCharm
import requests
import re
import base64
from bs4 import BeautifulSoup
from fontTools.ttLib import TTFont
from xml.dom.minidom import parse
import xml.dom.minidom
def pc():
headers={
'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
url='https://cd.58.com/pinpaigongyu/?from=58_pc_zonghe_home_ppgy_wzl_ppgy&utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d100000-0006-6bfd-576d-2eb43f45cb17&ClickID=2'
rq=requests.get(url,headers=headers)
rq.encoding='utf8'
data=rq.text
# print(data)
return data
#第一次運行,先存一次字體文件
def base_xml(data):
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
#base64解碼
font_data_after_decode = base64.b64decode(ttf_text)
with open('./world.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./world.ttf')
font.saveXML('my.xml')
def get_ttf(data):
#正則匹配處理字體文件
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
#base64解碼
font_data_after_decode = base64.b64decode(ttf_text)
with open('./newworld.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./newworld.ttf')
font.saveXML('newmy.xml')
#打開xml文檔
dom = xml.dom.minidom.parse('newmy.xml')
#dom對象轉字符串
collection = dom.documentElement.toxml()
soup=BeautifulSoup(collection,'lxml')
cmap_format_4=soup.find('cmap_format_4')
# print(cmap_format_4)
dict={'glyph00010':'9','glyph00001':'0','glyph00002':'1','glyph00003':'2','glyph00004':'3','glyph00005':'4','glyph00006':'5','glyph00007':'6','glyph00008':'7','glyph00009':'8'}
font1= TTFont('./world.ttf')
uni_list1=font1.getGlyphOrder()[1:]
print(uni_list1)
font2= TTFont('./newworld.ttf')
uni_list2=font2.getGlyphOrder()[1:]
print(uni_list2)
unicode_news=[]
nums=[]
for uni2 in uni_list2:
obj2=font2['glyf'][uni2]
for uni1 in uni_list1:
obj1=font1['glyf'][uni1]
if obj1==obj2:
print(uni2,dict[uni1])
unicode_div=soup.find('map',attrs={'name':uni2})
print(unicode_div)
unicode_new=unicode_div['code'].replace('0x','')
print(unicode_new,dict[uni1])
unicode_news.append(unicode_new)
nums.append(dict[uni1])
#替換編碼爲數字
new_data=data.replace(unicode_news[0],nums[0]).replace(unicode_news[1],nums[1]).replace(unicode_news[2],nums[2]).replace(unicode_news[3],nums[3]).replace(unicode_news[4],nums[4]).replace(unicode_news[5],nums[5]).replace(unicode_news[6],nums[6]).replace(unicode_news[7],nums[7]).replace(unicode_news[8],nums[8]).replace(unicode_news[9],nums[9]).replace('&#x','').replace(';','')
# print(new_data)
soup=BeautifulSoup(new_data,'lxml')
print(soup)
if __name__=='__main__':
data=pc()
#第一次運行,先運行 base_xml(data),註釋掉get_ttf(data)。此時是爲了調試,查看
base_xml(data)
#以後運行,註釋掉 base_xml(data),運行get_ttf(data)即可
# get_ttf(data)
老規矩,看下效果: