小編已加密:aHR0cHM6Ly93d3cuc2hpeGlzZW5nLmNvbS8= 出於安全原因,我們把網址通過base64編碼了,大家可以透過base64解碼把網址取得下來。
字體反爬:一種常見的反爬技術,是網頁與前端字體檔案配合完成的反爬策略,最早使用字體反爬技術的有58同城、汽車之家等等,現在很多主流的網站或APP也使用字體反爬技術為自身的網站或APP增加一種反爬措施。
字體反爬原理:透過自訂的字體來取代頁面中某些數據,當我們不使用正確的解碼方式就無法取得正確的數據內容。
在HTML中透過@font-face來使用自訂字體,如下圖所示:
其語法格式為:
@font-face{ font-family:"名字"; src:url('字体文件链接'); url('字体文件链接')format('文件类型') }
字體檔案一般是ttf類型、eot類型、woff類型,woff類型的檔案運用比較廣泛,所以大家一般碰到的都是woff類型的文件。
以woff類型檔案為例,其內容是怎樣的呢,又是以什麼編碼方式使得資料與程式碼一一對應的呢?
我們以某招募網站的字體文件為例,進入百度字體編譯器並開啟字體文件,如下圖所示:
隨機開啟一個字體,如下圖:
可以發現字體6放在一個平面座標裡面,根據平面座標的每個點來得出字體6的編碼,這裡就不解釋如何得出字體6的編碼了。
如何解決字體反爬呢?
首先映射關係可以看作為字典,大致上有兩種常用的方法:
第一種:手動把一組編碼和字符的對應關係提取出來並用字典的形式展示,程式碼如下所示:
replace_dict={ '0xf7ce':'1', '0xf324':'2', '0xf23e':'3', ....... '0xfe43':'n', } for key in replace_dict: 数据=数据.replace(key,replace_dict[key])
先定義字體與其對應的程式碼一一對應的字典,再透過for迴圈把資料一一替換。
注意:這種方法主要適用於字體映射較少的資料。
第二種:首先下載網站的字體文件,再把字體文件轉換為XML文件,找到裡面的字體映射關係的程式碼,透過decode函數解碼,然後將解碼的程式碼組合成一個字典,再根據字典內容將資料一一替換,由於程式碼比較長,這裡就不寫範例程式碼了,待會在實戰演練中會展示這種方法的程式碼。
好了,字體反爬就簡單講到這裡,接下來我們正式爬取某招聘網站。
先進入某位招募網並開啟開發者模式,如下圖所示:
這裡我們看到程式碼中只有生字不能正常函數,而是用來程式碼來替代,初步判定為使用了自訂的字體文件,這時就要找到字體文件了,那麼字型檔在哪裡找呢,先開啟開發者模式,並點選Network選項,如下圖:
一般情況下,字型檔放在Font選卡中,我們發現這裡一共有5個條目,那麼哪個是自訂字體檔案的條目呢,當我們每次點擊下一頁的時候,自訂字體檔案就會執行一次,這時我們只需要點擊網頁中的下一頁即可,如下圖:
可以看到多了一個以file開頭的條目,這時可以初步判定該文件為自定義字體文件,現在我們把它下載下來,下載方式很簡單,只需要把file開頭的條目的URL複製並在網頁上打開即可,下載下來後在百度字體編譯器打開,如下圖所示:
這時發現打開不了,是不是找錯了字體文件,網站提示說不支持這種文件類型,那麼我們把下載的文件後綴改為.woff在開啟試試,如下圖:
這時就成功開啟了。
找到自訂字體檔案了,那我們該怎麼利用呢?這時我們先自訂方法get_fontfile()來處理自訂字體文件,然後在透過兩個步驟來把字體文件中的映射關係透過字典的方式展示出來。
#首先自訂字體檔案更新頻率是很高的,這時我們可以即時取得網頁的自訂字體檔案來防止利用了先前的自訂字體檔案從而導致獲取資料不準確。首先觀察自訂字體檔案的url連結:
https://www.xxxxxx.com/interns/iconfonts/file?rand=0.2254193167485603 https://www.xxxxxx.com/interns/iconfonts/file?rand=0.4313944100724574 https://www.xxxxxx.com/interns/iconfonts/file?rand=0.3615862774301839
可以发现自定义字体文件的URL只有rand这个参数发生变化,而且是随机的十六位小于1的浮点数,那么我们只需要构造rand参数即可,主要代码如下所示:
def get_fontfile(): rand=round(random.uniform(0,1),17) url=f'https://www.xxxxxx.com/interns/iconfonts/file?rand={rand}' response=requests.get(url,headers=headers).content with open('file.woff','wb')as f: f.write(response) font = TTFont('file.woff') font.saveXML('file.xml')
首先通过random.uniform()方法来控制随机数的大小,再通过round()方法控制随机数的位数,这样就可以得到rand的值,再通过.content把URL响应内容转换为二进制并写入file.woff文件中,在通过TTFont()方法获取文件内容,通过saveXML方法把内容保存为xml文件。xml文件内容如下图所示:
该字体.xml文件一共有4589行那么多,哪个部分才是字体映射关系的代码部分呢?
首先我们看回在百度字体编码器的内容,如下图所示:
汉字人对应的代码为f0e2,那么我们就在字体.xml文件中查询人的代码,如下图所示:
可以发现一共有4个结果,但仔细观察每个结果都相同,这时我们可以根据它们代码规律来获取映射关系,再通过解码来获取对应的数据值,最后以字典的形式展示,主要代码如下所示:
with open('file.xml') as f: xml = f.read() keys = re.findall('<map code="(0x.*?)" name="uni.*?"/>', xml) values = re.findall('<map code="0x.*?" name="uni(.*?)"/>', xml) for i in range(len(values)): if len(values[i]) < 4: values[i] = ('\\u00' + values[i]).encode('utf-8').decode('unicode_escape') else: values[i] = ('\\u' + values[i]).encode('utf-8').decode('unicode_escape') word_dict = dict(zip(keys, values))
首先读取file.xml文件内容,找出把代码中的code、name的值并分别设置为keys键,values值,再通过for循环把values的值解码为我们想要的数据,最后通过zip()方法合并为一个元组并通过dict()方法转换为字典数据,运行结果如图所示:
在上一步中,我们成功把字体映射关系转换为字典数据了,接下来开始发出网络请求来获取数据,主要代码如下所示:
def get_data(dict,url): response=requests.get(url,headers=headers).text.replace('&#','0') for key in dict: response=response.replace(key,dict[key]) XPATH=parsel.Selector(response) datas=XPATH.xpath('//*[@id="__layout"]/div/div[2]/div[2]/div[1]/div[1]/div[1]/div') for i in datas: data={ 'workname':i.xpath('./div[1]/div[1]/p[1]/a/text()').extract_first(), 'link':i.xpath('./div[1]/div[1]/p[1]/a/@href').extract_first(), 'salary':i.xpath('./div[1]/div[1]/p[1]/span/text()').extract_first(), 'place':i.xpath('./div[1]/div[1]/p[2]/span[1]/text()').extract_first(), 'work_time':i.xpath('./div[1]/div[1]/p[2]/span[3]/text()').extract_first()+i.xpath('./div[1]/div[1]/p[2]/span[5]/text()').extract_first(), 'company_name':i.xpath('./div[1]/div[2]/p[1]/a/text()').extract_first(), 'Field_scale':i.xpath('./div[1]/div[2]/p[2]/span[1]/text()').extract_first()+i.xpath('./div[1]/div[2]/p[2]/span[3]/text()').extract_first(), 'advantage': ','.join(i.xpath('./div[2]/div[1]/span/text()').extract()), 'welfare':','.join(i.xpath('./div[2]/div[2]/span/text()').extract()) } saving_data(list(data.values()))
首先自定义方法get_data()并接收字体映射关系的字典数据,再通过for循环将字典内容与数据一一替换,最后通过xpath()来提取我们想要的数据,最后把数据传入我们自定义方法saving_data()中。
数据已经获取下来了,接下来将保存数据,主要代码如下所示:
def saving_data(data): db = pymysql.connect(host=host, user=user, password=passwd, port=port, db='recruit') cursor = db.cursor() sql = 'insert into recruit_data(work_name, link, salary, place, work_time,company_name,Field_scale,advantage,welfare) values(%s,%s,%s,%s,%s,%s,%s,%s,%s)' try: cursor.execute(sql,data) db.commit() except: db.rollback() db.close()
好了,程序已经写得差不多了,接下来将编写代码运行程序,主要代码如下所示:
if __name__ == '__main__': create_db() get_fontfile() for i in range(1,3): url=f'https://www.xxxxxx.com/interns?page={i}&type=intern&salary=-0&city=%E5%85%A8%E5%9B%BD' get_data(get_dict(),url)
以上是手把手教你JS逆向搞定字體反爬並獲取某招聘網站信息的詳細內容。更多資訊請關注PHP中文網其他相關文章!