前端反爬蟲策略--font-face 貓眼數據爬取


 
1 .font-face定義了字符集,通過unicode去印射展示。
2 .font-face加載網絡字體,我么可以自己創建一套字體,然后自定義一套字符映射關系表例如設置0xefab是映射字符1,0xeba2是映射字符2,以此類推。當需要顯示字符1時,網頁的源碼只會是0xefab,被采集的也只會是 0xefab,並不是1 3 .但是對於正常的用戶來說則沒有影響,因為瀏覽器會加載css的font字體為我們渲染好,實時顯示在網頁中。 4 .所以我們需要做的是,如何在判斷請求web字體的是機器人或者是真人,也就是說,攔截被收斂到了這一個地方
5 .定期更新一批字體文件和映射表來加大難度
6 .他這個破解也很簡單,需要一下人工,讀出那個請求html文件對應數字的unicode,自己把那個表更新一下,轉換那個部分可以做成自動的,還是可以用的。自己手動看一下1-9對應的unicode

實戰:貓眼

貓眼電影

首先來看一個頁面

https://maoyan.com/films/1212492

  

來分析一下頁面,先來網頁源代碼看看

可以找的到數據 但是數據是原始編碼。

這時候可能想那是不是 &#xe981對應的就是9,而&#xec39對應的就是3呢?

好我們來刷新驗證一下

有不一樣了這是怎么回事呢,我們來看

發現這個url是隨機的,每次訪問的值都不一樣。

emm那如果我們想要拿所有的數據就不適合把編碼寫死了,我們把字體文件下載下來看看里面究竟是怎么回事。它是一個woff的字體文件,我們可以使用python的一個第三方庫fonttools來幫助我們查看字體的信息。

fontTools

安裝很簡單,我的python版本是python3。

pip3 install fonttools

使用起來也很簡單,有一些以前的技術博客寫的這里的fonttools解析下來的結果是有序的,可能是貓眼升級了反爬措施,但是我解析下來的編碼是亂序的,所以只能自己去分析woff文件。

ttf = TTFont('./fonts/' + link)
self.font.saveXML('trans.xml') # 將woff文件的信息儲存為xml格式, 我們可以在xml里查看一些相關內容

trans.xml的輸出信息太長了這里就貼一部分

  <TTGlyph name="uniE89E" xMin="0" yMin="-12" xMax="516" yMax="706">
      <contour>
        <pt x="134" y="195" on="1"/>
        <pt x="144" y="126" on="0"/>
        <pt x="217" y="60" on="0"/>
        <pt x="271" y="60" on="1"/>
        <pt x="335" y="60" on="0"/>
        <pt x="423" y="158" on="0"/>
        <pt x="423" y="311" on="0"/>
        <pt x="337" y="397" on="0"/>
        <pt x="270" y="397" on="1"/>
        <pt x="227" y="397" on="0"/>
        <pt x="160" y="359" on="0"/>
        <pt x="140" y="328" on="1"/>
        <pt x="57" y="338" on="1"/>
        <pt x="126" y="706" on="1"/>
        <pt x="482" y="706" on="1"/>
        <pt x="482" y="622" on="1"/>
        <pt x="197" y="622" on="1"/>
        <pt x="158" y="430" on="1"/>
        <pt x="190" y="452" on="0"/>
        <pt x="258" y="475" on="0"/>
        <pt x="293" y="475" on="1"/>
        <pt x="387" y="475" on="0"/>
        <pt x="516" y="346" on="0"/>
        <pt x="516" y="243" on="1"/>
        <pt x="516" y="147" on="0"/>
        <pt x="459" y="75" on="1"/>
        <pt x="390" y="-12" on="0"/>
        <pt x="271" y="-12" on="1"/>
        <pt x="173" y="-12" on="0"/>
        <pt x="112" y="42" on="1"/>
        <pt x="50" y="98" on="0"/>
        <pt x="42" y="188" on="1"/>
      </contour>
      <instructions/>
    </TTGlyph>

然后我們發現每一個數字編碼都對應一個這樣的信息,每個信息的內容都不相同。經過細心的比對各個woff文件我們發現不同文件之間相同的數字對應的第一行<pt>內容是相同的,所以只要通過解析出一個woff里編碼的數字內容,其他woff的就都可以解析。為此我做了一個解析表

NUM_ATTR = {
    '8': {'x': '177', 'y': '388', 'on': '1'},
    '7': {'x': '47', 'y': '622', 'on': '1'},
    '6': {'x': '410', 'y': '534', 'on': '1'},
    '5': {'x': '134', 'y': '195', 'on': '1'},
    '1': {'x': '373', 'y': '0', 'on': '1'},
    '3': {'x': '130', 'y': '201', 'on': '1'},
    '4': {'x': '323', 'y': '0', 'on': '1'},
    '9': {'x': '139', 'y': '173', 'on': '1'},
    '2': {'x': '503', 'y': '84', 'on': '1'},
    '0': {'x': '42', 'y': '353', 'on': '1'},
    '.': {'x': '20', 'y': '20', 'on': '1'},
}

然后可以根據這個表的內容來解析每一次爬下來的woff文件內容,搞成一個轉換表。

def parse_transform(self):
self.font.saveXML('trans.xml')
tree = etree.parse("trans.xml")
TTGlyph = tree.xpath(".//TTGlyph")
translate_form = {}
for ttg in TTGlyph[1:11]:
ttg_dic = dict(ttg.attrib)
attr_dic = dict(ttg.xpath('./contour/pt')[0].attrib)
name = chr(int(ttg_dic['name'][3:7], 16)) # 字符串轉 16進制數字 再轉unicode
ttg_dic.pop('name')
for num, dic in NUM_ATTR.items():
if dic == attr_dic:
translate_form[name] = num
return translate_form

最好把這表保存一下,這樣以后遇到重復的字體就不用重復解析了。

貼一下完整代碼,使用的是scrapy框架,沒有用其他組件,而且只爬了一個頁面,所以只貼爬蟲的內容了

# -*- coding: utf-8 -*-
import re
import os
import json
import scrapy
import requests
from fontTools.ttLib import TTFont
from lxml import etree

NUM_ATTR = {
'8': {'x': '177', 'y': '388', 'on': '1'},
'7': {'x': '47', 'y': '622', 'on': '1'},
'6': {'x': '410', 'y': '534', 'on': '1'},
'5': {'x': '134', 'y': '195', 'on': '1'},
'1': {'x': '373', 'y': '0', 'on': '1'},
'3': {'x': '130', 'y': '201', 'on': '1'},
'4': {'x': '323', 'y': '0', 'on': '1'},
'9': {'x': '139', 'y': '173', 'on': '1'},
'2': {'x': '503', 'y': '84', 'on': '1'},
'0': {'x': '42', 'y': '353', 'on': '1'},
'.': {'x': '20', 'y': '20', 'on': '1'},
}


class MaoyanspSpider(scrapy.Spider):
name = 'maoyansp'
start_urls = ['https://maoyan.com/films/1212492']
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
}

def parse(self, response):
font_link = re.findall(r'vfile.meituan.net/colorstone/(\w+\.woff)',
response.text)[0]
self.get_font(font_link)
data = self.parse_item(response)
print(data)

def parse_item(self, response):
html = etree.HTML(response.body.decode('utf-8'))
name = html.xpath('.//div[@class="movie-brief-container"]/h3/text()')[0]
movie_content = html.xpath('.//div[@class="movie-stats-container"]')[0]
content = movie_content[0].xpath('.//span[@class="stonefont"]/text()')
score = content[0]
comment_count = content[1]
box = movie_content[1].xpath('.//span[@class="stonefont"]/text()')[0]
box_unit = movie_content[1].xpath('.//span[@class="unit"]/text()')[0]
score = self.modify_data(score)
comment_count = self.modify_data(comment_count)
box = self.modify_data(box)
data = {
"name": name,
"score": score,
"comment_count": comment_count,
"box": box,
"box_unit": box_unit
}
return data

def download_font(self, link):
download_link = 'http://vfile.meituan.net/colorstone/' + link
woff = requests.get(download_link)
with open(r'./fonts/' + link, 'wb') as f:
f.write(woff.content)

def get_font(self, link):
file_list = os.listdir(r'.\fonts')
if link not in file_list:
self.download_font(link)
print("字體不在庫中:", link)
else:
print("字體在庫中:", link)
self.font = TTFont('./fonts/' + link)
self.transform = './transform/' + link.replace('.woff', '.json')

def modify_data(self, data):
print(data)
trans_form = self.get_transform()
for name, num in trans_form.items():
if name in data:
data = data.replace(name, num)
return data

def get_transform(self):
file_list = os.listdir(r'.\transform')
if self.transform in file_list:
with open(self.transform, 'r') as f:
file = f.read()
return json.loads(file)
else:
translate_form = self.parse_transform()
with open(self.transform, 'w') as f:
f.write(json.dumps(translate_form))
return translate_form

def parse_transform(self):
self.font.saveXML('trans.xml')
tree = etree.parse("trans.xml")
TTGlyph = tree.xpath(".//TTGlyph")
translate_form = {}
for ttg in TTGlyph[1:11]:
ttg_dic = dict(ttg.attrib)
attr_dic = dict(ttg.xpath('./contour/pt')[0].attrib)
hexstr = ttg_dic['name'][3:7]
name = chr(int(hexstr, 16)) # 字符串轉 16進制數字 再轉unicode
ttg_dic.pop('name')
for num, dic in NUM_ATTR.items():
if dic == attr_dic:
translate_form[name] = num
return translate_form

由於使用的python3,scrapy的response.body是Unicode,所以我只能直接通過這個body來修改內容.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM