前一段时间,爬取了58同城,发现当时的网页对数字有字体反爬虫,然后废了九牛二虎之力找到了规律,终于**了反爬虫,后来发现猫眼的这个网页虽然使用了字体反爬,但是和原来的58同城还是有很大的差别,后来了解到这个新的字体反爬虫属于动态字体加载。
首先我们登陆上猫眼的首页,然后可以看到
本来应该正常显示数字的部分,变成了一组我们看不懂的文本格式,然后我们去查看源码
发现在源码中是以html编码编写的,同时我们也可以在网页中找到
这个就是整个猫眼网页中使用的字体文本,所以我们需要花费很大的精力在这一部分,这个也是**这个动态字体反爬的关键必要操作。
我们需要使用到一个叫做fontcreator的应用,可以自行在网上下载一下。
我们将这个应用下载下来,然后将上图中的
//vfile.meituan.net/colorstone/726c02e5f18a0632020d0d5c82ec81b72088.woff
这一段代码提取出来,然后组成一个url,用Python将网页中的信息提取出来,以为二进制的形式保存在.woff文件中,将其作为基础字体模板,用来比较后面的字体。
def write_file(self,response_woff):
#存储woff字体文件
with open('get_fonts.woff','wb')as f:
f.write(response_woff)
然后用刚刚下载的应用将这个woff文件打开。
我们将这个.woff文件命名为basefonts.woff,其目地就是为了对比以后获取到的字体woff文件。
baseFonts = TTFont('basefonts.woff')
base_nums = ['1', '6', '5', '0', '7', '9', '8', '4', '2', '3']
base_fonts = ['uniF2DD', 'uniF747', 'uniF01C', 'uniEBD0', 'uniEA1B', 'uniE897', 'uniE477', 'uniF53B', 'uniF1DF','uniE38B']
以上我么就完成了将字体模板解析和缓存在本地。
接下来,我们刷新网页,可以发现woff字体发生了改变,
我们对比两组字体发现对应的Unicode编码也并不相同,但是我们将.woff文件转换成xml文件,可以找到一定的规律。
font = TTFont("basefonts.woff")
font.saveXML('basefonts.xml')
我们将两个woff文件 都转换成xml格式,打开以后可以看到其中,对于字体笔画顺序的编写是相同的,
所以我们可以找出相应的规律,将先后得到的代码进行一一对应,就可以找出每一次加载出来的字体有什么变化规律。
base_nums = ['1', '6', '5', '0', '7', '9', '8', '4', '2', '3']
base_fonts = ['uniF2DD', 'uniF747', 'uniF01C', 'uniEBD0', 'uniEA1B', 'uniE897', 'uniE477', 'uniF53B', 'uniF1DF','uniE38B']
onlineFonts = TTFont('get_fonts.woff')
#经过观察字体本身可以发现规律,第一个为None,最后一个也为None值,所以需要处理掉
uni_list = onlineFonts.getGlyphNames()[1:-1]
temp = {}
#因为字体是动态加载出来的,所以需要实现字体的一一对应:
for i in range(10):
onlineGlyph = onlineFonts['glyf'][uni_list[i]]
for j in range(10):
baseGlyph = baseFonts['glyf'][base_fonts[j]]
if onlineGlyph == baseGlyph:
temp["&#x" + uni_list[i][3:].lower() + ';'] = base_nums[j]
最后,我们就可以分析出整个动态字体反爬的心路历程了。
# -*- coding: utf-8 -*-
# Created : 2019/4/1
import re
import requests
from fontTools.ttLib import TTFont
from lxml import etree
class Maoyanspider:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36",
}
def parse_fonts(self,url):
#获取网页源码
response = requests.get(url,headers = self.headers).text
#获取网页动态加载的字体模板
self.get_font(response)
#字体手动获取到的网页字体模板
baseFonts = TTFont('basefonts.woff')
base_nums = ['1', '6', '5', '0', '7', '9', '8', '4', '2', '3']
base_fonts = ['uniF2DD', 'uniF747', 'uniF01C', 'uniEBD0', 'uniEA1B', 'uniE897', 'uniE477', 'uniF53B', 'uniF1DF','uniE38B']
onlineFonts = TTFont('get_fonts.woff')
#经过观察字体本身可以发现规律,第一个为None,最后一个也为None值,所以需要处理掉
uni_list = onlineFonts.getGlyphNames()[1:-1]
temp = {}
#因为字体是动态加载出来的,所以需要实现字体的一一对应:
for i in range(10):
onlineGlyph = onlineFonts['glyf'][uni_list[i]]
for j in range(10):
baseGlyph = baseFonts['glyf'][base_fonts[j]]
if onlineGlyph == baseGlyph:
temp["&#x" + uni_list[i][3:].lower() + ';'] = base_nums[j]
#将被处理过的字体进行替换
pat = '(' + '|'.join(temp.keys()) + ')'
response_index = re.sub(pat, lambda x: temp[x.group()], response)
#最终返回的是一个处理过(信息正常)的网页源码
return response_index
def get_font(self,response):
#提取出woff文件,存储在本地
woff = re.search(r"url\('(.*\.woff)'\)", response).group(1)
woff_url = 'http:' + woff
response_woff = requests.get(woff_url, headers=self.headers).content
self.write_file(response_woff)
def write_file(self,response_woff):
#存储woff字体文件
with open('get_fonts.woff','wb')as f:
f.write(response_woff)
font = TTFont("basefonts.woff")
font.saveXML('basefonts.xml')
def parse_url(self,response):
#该response是已经处理过的网页源码,可以直接操作
html = etree.HTML(response)
ul_list = html.xpath('//ul[@class="ranking-wrapper ranking-box"]/li')
content_list = []
for ul in ul_list:
item = {}
#获取影片票房
movie_price = ul.xpath(".//span[@class='stonefont']/text()")[0]
#获取影片名称
movie_name = ul.xpath(".//span[@class='ranking-top-moive-name']/text()|.//span[@class='ranking-movie-name']/text()")[0]
movie_price_unit ="".join(ul.xpath(".//p[@class='ranking-top-wish']/text()|.//span[@class='ranking-num-info']/text()")).replace("\n","").replace(" ","")
item['movie_name'] = movie_name
item['movie_price'] = movie_price + movie_price_unit
content_list.append(item)
return content_list
#将信息打印出来,也可以选择存储在数据库中
def print_movie_info(self,content_list):
for content in content_list:
print(content)
def run(self):
#输入需要获取的网页
url = 'https://maoyan.com/'
#将网页信息提取出来,将被处理过的字体进行反处理后解析出来
response = self.parse_fonts(url)
#获取需要的信息
content_list = self.parse_url(response)
self.print_movie_info(content_list)
if __name__ == '__main__':
maoyan = Maoyanspider()
maoyan.run()
如有问题,欢迎斧正!
联系qq:986361369