整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          如何破解字體反爬機(jī)制

          幾天爬取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)載請附上博文鏈接!

          • Weex代碼中加載自定義字體

          beforeCreate中加載字體文件:

          beforeCreate: function () {
          	const domModule = weex.requireModule('dom')
          	domModule.addRule('fontFace', {
              'fontFamily': "myfont",
              'src': "url('http://xxx.ttf')"
          	});
          }


          addRule(type, contentObject)

          • @fontFace 協(xié)議名稱,不可修改。
          • @fontFamily font-family的名稱。
          • @src 字體地址,url('') 是保留字段,可接受的參數(shù)如下:

          1. http, url('http://xxx.ttf')

          2.httpsurl('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 字體


          主站蜘蛛池模板: 亚洲一区精品无码| 人妻无码一区二区三区四区| 无码一区18禁3D| 日韩av无码一区二区三区| 91一区二区视频| 国产一区在线电影| 色噜噜狠狠一区二区| 中文字幕一区日韩在线视频| 亚洲人成人一区二区三区| 一区二区三区四区精品视频 | 无码人妻精品一区二区三区99不卡| 青青青国产精品一区二区| 影院成人区精品一区二区婷婷丽春院影视 | 国产高清在线精品一区二区| 国产亚洲日韩一区二区三区 | 激情内射亚州一区二区三区爱妻| 国产日韩一区二区三区在线观看| 性盈盈影院免费视频观看在线一区| 亚洲午夜福利AV一区二区无码| 中文精品一区二区三区四区| 无码精品人妻一区二区三区免费| 国产精品视频分类一区| 午夜视频久久久久一区| 亚洲综合一区国产精品| 亚洲AV无码一区二区三区性色 | 日本精品一区二区在线播放| 久久久av波多野一区二区| 亚洲一区精品无码| 无码人妻AV免费一区二区三区| 日本成人一区二区| 亚洲日本精品一区二区| 国产福利一区二区三区视频在线| 精品一区二区三区在线视频| 美女视频在线一区二区三区| 亚洲一区二区三区影院| 亚洲午夜精品一区二区公牛电影院 | 一区二区免费电影| 国产精品第一区第27页| 一区二区在线观看视频| 亚洲色一区二区三区四区| 波多野结衣精品一区二区三区|