幾天爬取58租房信息的時候意外發(fā)現(xiàn)了它是一個字體反爬的網(wǎng)站,所謂的字體反爬就是網(wǎng)站將一些關(guān)鍵字替換為網(wǎng)站自己的字體,這樣在網(wǎng)頁上字體會正常顯示,但是當(dāng)爬取下來的時候,經(jīng)過字體加密的字符都是亂碼的,根本無法查看
如圖所示:
可以看到,2390元/月在頁面上是正常顯示的,但是,當(dāng)我們打開查看器查看的時候......
好端端的2390就變成了不知道什么字符.......
這就是網(wǎng)站使用了字體反爬機(jī)制,網(wǎng)站有自己的一套字體,只有在它的頁面上才會正常顯示,否則就是一串亂碼,毫無價值。那么遇到這種問題該怎么解決,在經(jīng)歷幾天的摸索之后終于將正確的信息抓取了下來。
首先,我們查看網(wǎng)頁的源碼,就是下面這樣的
這是網(wǎng)頁源碼中一串base64的字符串,它就是網(wǎng)站的字體文件,很難想象一串base64的字符串就是它的字體文件。
我們將這一串base64的字符串復(fù)制下來,將它解碼并保存成一個字體文件
import base64
font_face = "AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzL4XQjtAAABjAAAAFZjbWFwq8N/ZAAAAhAAAAIuZ2x5ZuWIN0cAAARYAAADdGhlYWQTcnjtAAAA4AAAADZoaGVhCtADIwAAALwAAAAkaG10eC7qAAAAAAHkAAAALGxvY2ED7gSyAAAEQAAAABhtYXhwARgANgAAARgAAAAgbmFtZTd6VP8AAAfMAAACanBvc3QFRAYqAAAKOAAAAEUAAQAABmb+ZgAABLEAAAAABGgAAQAAAAAAAAAAAAAAAAAAAAsAAQAAAAEAAOv6p7JfDzz1AAsIAAAAAADX9ZbuAAAAANf1lu4AAP/mBGgGLgAAAAgAAgAAAAAAAAABAAAACwAqAAMAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEERAGQAAUAAAUTBZkAAAEeBRMFmQAAA9cAZAIQAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQJR2n6UGZv5mALgGZgGaAAAAAQAAAAAAAAAAAAAEsQAABLEAAASxAAAEsQAABLEAAASxAAAEsQAABLEAAASxAAAEsQA"
# 太長沒復(fù)制完......
b = base64.b64decode(font_face)
with open('58.ttf','wb') as f:
f.write(b)
這里我們將它保存成一個ttf的字體文件,然后使用fontCreator將這個字體文件打開(fontCreator需要自行下載,直接百度就能下載類,非常簡單)
打開之后的效果
我們再來看看網(wǎng)頁上的源碼......
小伙伴們是不是會驚奇的發(fā)現(xiàn),網(wǎng)頁源碼上被替換掉的數(shù)字,使用fontCreator都能找到與之對應(yīng)的原本的數(shù)字,那么立刻就能想到的一個方法就是將這些網(wǎng)頁中的編碼與原本的值對應(yīng)成一個字典,只要抓取到了字典中存在的值就將其替換成本來的值,但是(注意我這里使用了但是)......
同樣的,這也是58的一個字體文件,但是解析出來編碼與對應(yīng)的數(shù)字與上一次解析的完全不一樣,這倒不是因?yàn)閒ontCreator解析出錯,而是因?yàn)?8這個網(wǎng)站友好幾套字體文件,它的每一頁的數(shù)據(jù)使用的都是隨機(jī)的字體文件,當(dāng)你解析了第一頁字體的對應(yīng)關(guān)系,拿小本本美滋滋的將對應(yīng)關(guān)系記下來,但是點(diǎn)擊到第二頁的時候,發(fā)現(xiàn)關(guān)系又完全對不上,是不是很氣。而且又不可能將每一頁的對應(yīng)關(guān)系都用本子記錄下來。這時候就需要另一個工具,python的第三方庫fontTools,直接pip安裝就行。
在進(jìn)行解密之前,先將原先的字體文件保存成一個xml文件。
from fontTools.ttLib import TTFont
font = TTFont('58.ttf')
font.saveXML('test.xml')
打開這個xml看看到底是啥
看不懂......
這個就好像有點(diǎn)懂了,code的值不是網(wǎng)頁上的數(shù)字被替換的字符串嗎!!!
這里的name屬性的值實(shí)際對應(yīng)的就是網(wǎng)頁上的數(shù)字,下面的代碼可以幫助我們查看對應(yīng)關(guān)系
from fontTools.ttLib import TTFont
font = TTFont('58.ttf')
# font.saveXML('test.xml')
print(font.keys())
a = font['cmap'].tables[2].ttFont.getGlyphOrder()
b = font['cmap'].tables[2].ttFont.getReverseGlyphMap()
c = font['cmap'].tables[2].ttFont.tables['cmap'].tables[1].cmap
print("ppp ::::: ",a)
print("ddd ::::: ",b)
print("ddd ::::: ",c)
輸出的結(jié)果
這里最后一行輸出的就是網(wǎng)頁上顯示的字符串與camp標(biāo)簽中的name值的對應(yīng)關(guān)系。那么網(wǎng)頁上顯示的0x958f這種字符串究竟是什么意思呢,它其實(shí)是一個十六進(jìn)制的數(shù)字,將這個十六進(jìn)制的數(shù)字轉(zhuǎn)換成十進(jìn)制(int("0x985f",16)),得到的值就是最后一行輸出的鍵,那么這個鍵對應(yīng)的值就是第二行輸出的鍵,第二行的值就是本來的數(shù)據(jù),這樣一來,在抓取每一頁之前,先抓取到它這一頁的字體文件,進(jìn)行分析,得到對應(yīng)關(guān)系,不就能獲取到原始的數(shù)據(jù)了嘛。
ps:需要注意的是,使用fontCreator解析時發(fā)現(xiàn)數(shù)字對應(yīng)關(guān)系前面有一個空值,所以實(shí)際解析到的對應(yīng)的數(shù)字需要減1才能得到正確的值。
代碼思路:1.在爬取每一頁數(shù)據(jù)之前,先獲取到源碼中的base64的字符串,解碼,保存成字體文件2.生成對應(yīng)關(guān)系的字典3.抓取到頁面的亂碼文字,解碼成16進(jìn)制的數(shù)字3.將十六進(jìn)制的數(shù)字轉(zhuǎn)為十進(jìn)制,判斷字典中是否有這個鍵,如果有,則解析為原本的數(shù)字,如果沒有,則說明這個文字沒有進(jìn)行機(jī)密處理,保存原來的文字即可。4.完成全部加密文字的替換得到正確的數(shù)據(jù)。
實(shí)際代碼:
import re
import lxml.html
import base64
from fontTools.ttLib import TTFont
import requests
import random
import sqlite3
db = sqlite3.connect("58.db")
cursor = db.cursor()
UA = [
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
]
headers = {
"User-Agent":random.choice(UA)
}
def resp(i):
base_url = "https://tj.58.com/pinpaigongyu/pn/{}/"
response = requests.get(base_url.format(i), headers=headers)
print("正在下載:",response.url)
return response
def get_base64_str(response):
base_font = re.compile("base64,(.*?)\'")
base64_str = re.search(base_font, response.text).group().split(',')[1].split('\'')[0]
return base64_str
def make_font_file(base64_str):
b = base64.b64decode(base64_str)
with open("58.ttf","wb") as f:
f.write(b)
def make_dict():
font = TTFont('58.ttf')
b = font['cmap'].tables[2].ttFont.getReverseGlyphMap() # 編碼對應(yīng)的數(shù)字
c = font['cmap'].tables[2].ttFont.tables['cmap'].tables[1].cmap # 頁面的十六進(jìn)制數(shù)對應(yīng)的編碼
return b, c
def parse_title(text):
s = ""
title_re = re.compile("\s")
html = lxml.html.fromstring(text)
title = html.xpath('//div[@class="des strongbox"]/h2/text()')[0]
title = re.sub(title_re,'',title)
for i in title:
encode_str = str(i.encode("unicode-escape")).split(r'\u')[-1].replace('\'','').replace(r'b(','').strip()
num, code = make_dict()
if len(encode_str) != 4:
i = i
elif int(encode_str,16) not in code:
i = i
else:
i = str(num[code[int(encode_str,16)]] - 1)
s += i
return s
def parse_price(text):
s = ""
html = lxml.html.fromstring(text)
price_code = html.xpath('//span[@class="strongbox"]/b/text()')[0]
price_code = price_code.strip().replace('\r\n','').replace(' ','')
price_encode_str = str(price_code.encode("unicode-escape")).split('\'')[1].split('-')
if len(price_encode_str) > 1:
s1 = ""
s2 = ""
encode_list1 = price_encode_str[0].split(r"\u")[1:]
encode_list2 = price_encode_str[1].split(r"\u")[1:]
for i in encode_list1:
price = int(i,16)
num, code = make_dict()
s1 += str(num[code[price]] - 1)
for i in encode_list2:
price = int(i,16)
num, code = make_dict()
s2 += str(num[code[price]] - 1)
s = s1 + '-' + s2
else:
str_list = price_encode_str[0].split(r'\u')[1:]
for i in str_list:
price = int(i,16)
num, code = make_dict()
s += str(num[code[price]]-1)
return s
def parse_room(text):
s = ""
html = lxml.html.fromstring(text)
p_rooms = html.xpath('//p[@class="room"]/text()')[0]
room_re = re.compile('[\s]')
room_re1 = re.compile(r'[m2]')
room_re2 = re.compile(r'/')
rooms = re.sub(room_re,'',p_rooms)
rooms = re.sub(room_re1,"平米",rooms)
rooms = re.sub(room_re2,"至",rooms)
for i in rooms:
encode_str = str(i.encode("unicode-escape")).split(r'\u')[-1].replace('\'', '').replace(r'b/','').strip()
# print(encode_str)
num, code = make_dict()
if len(encode_str) != 4:
i = i
elif int(encode_str,16) not in code:
i = i
else:
i = str(num[code[int(encode_str,16)]] - 1)
s += i
return s
def parse_dist(text):
s = ""
html = lxml.html.fromstring(text)
p_dist_re = re.compile('\skm')
try:
p_dist = html.xpath('//p[@class="dist"]//text()')[1]
p_dist = ''.join(p_dist).replace(' ','')
p_dist = re.sub(p_dist_re,'千米',p_dist)
for i in p_dist:
encode_str = str(i.encode("unicode-escape")).split(r'\u')[-1].replace('\'', '').replace(r'\r','').replace(r'\n','').replace(r'b.','').strip()
num, code = make_dict()
if len(encode_str) != 4:
i = i
elif int(encode_str, 16) not in code:
i = i
else:
i = str(num[code[int(encode_str, 16)]] - 1)
s += i
dist = s
except:
dist = "暫無"
return dist
def short_rent(text):
html = lxml.html.fromstring(text)
try:
rent = html.xpath('//p[@class="room"]/b/text()')[0]
except:
rent = "不可短租"
return rent
def parse_li(response):
li_re = re.compile('<li logr([\s\S]*?)</li>')
li_list = re.findall(li_re,response.text)
return li_list
def parse_target(text):
html = lxml.html.fromstring(text)
try:
target = html.xpath('//p[@class="spec"]/span/text()')
target = ','.join(target)
except:
target = "暫無"
return target
if __name__ == '__main__':
for i in range(1,171):
response = resp(i)
base64_str = get_base64_str(response)
make_font_file(base64_str)
make_dict()
li_list = parse_li(response)
for i in li_list:
title = parse_title(i)
price = parse_price(i)
room = parse_room(i)
dist = parse_dist(i)
rent = short_rent(i)
target = parse_target(i)
city = "天津"
cursor.execute("insert into home(title, price, room, dist, rent,target, city) values (?,?,?,?,?,?,?)",[title,price,room,dist,rent,target,city])
db.commit()
由于我的正則表達(dá)式功底有點(diǎn)差,所以這里的正則表達(dá)式都用的是比較low的..........
但是數(shù)據(jù)爬下來了,而且沒有問題
一次愉快的破解字體反爬機(jī)制就到此結(jié)束了
---------------------
作者:在杭州
來源:CSDN
原文:https://blog.csdn.net/WanYu_Lss/article/details/83447174
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!
在beforeCreate中加載字體文件:
beforeCreate: function () {
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "myfont",
'src': "url('http://xxx.ttf')"
});
}
1. http, url('http://xxx.ttf')
2.https,url('https://xxx.ttf')
3.local,url('local:///foo.ttf'),Android 從assets目錄讀取;官方文檔這里貌似寫錯了,必須有三條'/'才可以。
4.file,從本地文件讀取, url('file://xxx.ttf')
5.data. 從base64讀取, url('data:font/truetype;charset=utf-8;base64,xxx....')。
以上基本為官方文檔已有說明,需要注意??:
1.使用local加載工程中的ttf時,iOS沒有目錄限制,只要ttf在工程中存在,在plist中加上一個Array類型的UIAppFonts屬性,新增一條xxx.ttf的item即可;
2.css中,font-family的值不能添加引號,否則不會生效,筆者因?yàn)檫@個問題各種嘗試才發(fā)現(xiàn)。(若使用font-family:"Lato-Bold";則不會生效)
前,瀏覽器只能展示本地安裝的字體。如果字體未安裝,網(wǎng)頁顯示效果會大打折扣。
為了解決這個問題,CSS 引入 web 字體,允許瀏覽器從服務(wù)器下載字體,下載完成后再重新渲染字體。
使用 web 字體前,需要了解常用的字體文件格式。
TTF 字體文件,即 TrueType 字體,是由蘋果和微軟在 20 世紀(jì) 80 年代末開發(fā)的字體標(biāo)準(zhǔn)。它是 macOS 和 Windows 操作系統(tǒng)使用最廣泛的字體格式。
OTF 字體文件,即 OpenType 字體,是一種可縮放的計算機(jī)字體格式。它建立在 TrueType 基礎(chǔ)上,是微軟的注冊商標(biāo)。OpenType 字體目前在主要的計算機(jī)平臺上廣泛使用。
WOFF 字體文件,即 The Web Open Font Format 字體,是一種用于網(wǎng)頁的字體格式,2009 年開發(fā),如今是 W3C(萬維網(wǎng)聯(lián)盟)的推薦標(biāo)準(zhǔn)。WOFF 本質(zhì)是 OpenType 或 TrueType 字體,但是經(jīng)過壓縮并附加額外的元數(shù)據(jù)。在帶寬受限的網(wǎng)絡(luò)中,WOFF 能更好的支持從服務(wù)器到客戶端的字體傳輸。
WOFF 2.0 字體文件,相比于 WOFF,提供了更高的壓縮效率。
SVG 字體,將 SVG 用作顯示文本時的字形。SVG 1.1 規(guī)范定義了一個字體規(guī)范,允許在 SVG 文檔中創(chuàng)建字體。
EOT 字體文件,即 Embedded OpenType Fonts 文件,是微軟設(shè)計的一種用于網(wǎng)頁的嵌入式字體,它是 OpenType 字體的緊湊形式。
不同字體格式的瀏覽器兼容性下圖所示:
不同字體格式的瀏覽器兼容性,截圖數(shù)據(jù)來自 w3schools.com
使用 @font-face CSS 指令定義自定義字體。使用前需要把字體文件放在服務(wù)器目錄,然后定義新的字體名稱,并指向字體所在位置。
以京華老宋體為例,這是一款可以免費(fèi)商用的中文字體。下載字體文件后,放到和 index.html 同級的目錄,重命名為 jh-song.ttf。
下載字體文件
在 @font-face 指令內(nèi),使用 font-family 定義字體名稱,src 屬性定義字體文件路徑。
定義 web 字體
然后,像使用普通字體一樣,使用自定義字體樣式:
使用 web 字體
完
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。