反反爬技术,破解猫眼网加密数字

 

From:https://blog.csdn.net/qq_31032181/article/details/79153578

From:http://www.freebuf.com/news/140965.html

利用自定义web-font实现数据防采集:http://blog.csdn.net/fdipzone/article/details/68166388

利用前端字体文件(.ttf)混淆数字来阻止爬虫爬取数据:https://blog.csdn.net/qq_37540004/article/details/78864713

反击爬虫,前端工程师的脑洞可以有多大?:https://baijiahao.baidu.com/s?id=1572788572555517

反爬虫解析-字体替换(天眼查/猫眼电影):https://www.jianshu.com/p/79c4272c0969

猫_眼_电_影 字体文件 @font-face:https://www.cnblogs.com/my8100/p/js_maoyandianying.html

Github 地址:猫眼电影字体加载还原

Font­Tools 安装与使用简明指南:https://darknode.in/font/font-tools-guide

崔庆才 - 字体反爬:https://cuiqingcai.com/6431.html

 

 

************** 

2019/8/21 猫眼的字体反爬变了,这篇文章中方法现在破解不了了

原来是:   unicode编码   <---------->  字体形状编码(字形编码固定不变)  <---------->  对应字符编码

现在是:   unicode编码   <---------->  字体形状编码(字形编码变化)  <---------->  对应字符编码

可以参考这篇破解猫眼字体反爬:https://blog.csdn.net/weixin_43145520/article/details/89878788

**************

 

 

示例

 

1. 打开 地址:https://maoyan.com/cinema/15280?poi=99389254

发现数字是乱码。

2. 在网页 上  右键 ——> 查看网页源代码 找到 加密数字

 加密数字:&#xf764;&#xed9d;

3. 在网页源码搜索 font-face,然后提取 font 的 url。
此处提取的 font url 是 //vfile.meituan.net/colorstone/44d84ad862bdc7074d0909ee5bf108512088.woff ,然后在加上 http 得到完整的 url 地址:http://vfile.meituan.net/colorstone/44d84ad862bdc7074d0909ee5bf108512088.woff

4. 把提取 的 加密字体 赋值 给 程序中 unicode_1 和 unicode_2 ,字体 url 地址 赋值给 web_font_url, 运行程序

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author      : 
# @File        : font_data.py
# @Software    : PyCharm
# @description : XXXimport os
import base64
from fontTools.ttLib import TTFont
import requestsfile_abs_path = os.path.abspath(__file__)
# file_name = os.path.basename(file_abs_path)
current_dir = os.path.dirname(file_abs_path)
# grader_father = os.path.abspath(os.path.dirname(current_dir) + os.path.sep + "..")
# father_dir = os.path.abspath(os.path.dirname(current_dir))
font_encrypt_data = "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"
font_decrypt_data = base64.b64decode(font_encrypt_data)
file = open(os.path.join(current_dir, 'base.woff'), 'wb')
file.write(font_decrypt_data)
file.close()base_num_dict = {"uniEA4D": "1", "uniE6CD": "2", "uniEF24": "3", "uniE1F5": "4", "uniF807": "5","uniEF10": "6", "uniE118": "7", "uniE4F5": "8", "uniECFD": "9", "uniF38B": "0"
}base_font = TTFont(os.path.join(current_dir, 'base.woff'))
# base_font.saveXML(os.path.join(current_dir, 'base.xml'))class MaoYanDecryptFont(object):def __init__(self, web_font_url):super(MaoYanDecryptFont, self).__init__()self.unicode_to_glyph_dict = self.get_unicode_to_glyph_dict(web_font_url)self.glyph_to_character_dict = self.get_glyph_to_character_dict()pass@staticmethoddef get_unicode_to_glyph_dict(web_font_url=None):"""得到 unicode 到 字形 的映射 字典:param web_font_url:web_font_url = 'http://vfile.meituan.net/colorstone/ef2688be76f0a8f2a810ee89adaab71f2084.woff':return:"""file_name = web_font_url.split('/')[-1].split('.')[0] + '.woff'file_full_path = os.path.join(current_dir, file_name)if not os.path.exists(file_full_path):with open(file_full_path, 'wb') as f:font_content = requests.get(web_font_url).contentf.write(font_content)online_fonts = TTFont(file_full_path)  # 加载字体文件unicode_to_glyph_dict = dict()temp = online_fonts.getGlyphSet()._glyphs.glyphsfor k, v in temp.items():if 'uni' not in k:continueunicode_to_glyph_dict[k] = v.datareturn unicode_to_glyph_dict@staticmethoddef get_glyph_to_character_dict():"""得到 字形  到  字符  的映射 字典:return: python 字典"""glyph_to_character_dict = dict()unicode_to_byte_dict = base_font.getGlyphSet()._glyphs.glyphs# font_glyph_unicode_set = base_font.getGlyphSet().keys()for k, v in base_num_dict.items():# font_dict[base_font.getGlyphSet().get(k)] = vglyph_to_character_dict[unicode_to_byte_dict[k].data] = vreturn glyph_to_character_dictdef get_encrypt_num(self, unicode_str=None):glyph = self.unicode_to_glyph_dict[unicode_str]character = self.glyph_to_character_dict[glyph]return characterif __name__ == "__main__":# 网页上加密的数字# <span class="stonefont">&#xf764;&#xed9d;</span>unicode_1 = 'uni' + 'f764'.upper()unicode_2 = 'uni' + 'ed9d'.upper()print(unicode_1, unicode_2)web_font_url = 'http://vfile.meituan.net/colorstone/44d84ad862bdc7074d0909ee5bf108512088.woff'mdf = MaoYanDecryptFont(web_font_url)print(mdf.get_encrypt_num(unicode_1))print(mdf.get_encrypt_num(unicode_2))pass

运行结果:

打印 4 和 5 ,刚好和网页上显示 票价 对应

 

 

 

破解猫眼加密数字

 

一、背景

 

 字体反爬应用还是很普遍。这两天有朋友咨询如何实现猫眼票房数据的爬取,这里其实与上面的文章核心思想是一致的,但是操作更复杂一些,本文做一个更详细的破解实践。

有对字体反爬还比较陌生的,请参考前文。

 

二、查找字体源

 猫眼电影是美团旗下的一家集媒体内容、在线购票、用户互动社交、电影衍生品销售等服务的一站式电影互联网平台。2015年6月,猫眼电影覆盖影院超过4000家,这些影院的票房贡献占比超过90%。目前,猫眼占网络购票70%的市场份额,每三张电影票就有一张出自猫眼电影,是影迷下载量较多、使用率较高的电影应用软件。同时,猫眼电影为合作影院和电影制片发行方提供覆盖海量电影消费者的精准营销方案,助力影片票房。

我们使用Chrome浏览页面,并查看源码,发现售票中涉及数字的,在页面显示正常,在源码中显示一段span包裹的不可见文本。

 

上面其实就是自定义字体搞的鬼。根据网页源码中,

 

<span class="stonefont">.</span>

使用了自定义的stonefont字体,我们在网页中查找stonefont,很快有了发现,这就是标准的@font-face定义方法。且每次访问,字体文件访问地址都会随机变化。

我们访问其中woff文件的地址,可将woff字体文件下载到本地。前文中fonttools并不能直接解析woff字体,我们需要将woff字体转换成otf字体。百度可以直接转换字体 ,地址:http://fontstore.baidu.com/static/editor/index.html

字体转换 woff :http://www.sfont.cn/tools/font
把下载好的字体通过地址( http://www.sfont.cn/tools/font )转换成 otf 字体,在线转换完成后,下载 转换完成的字体
把转换完成的字体上传到( http://fontstore.baidu.com/static/editor/index.html ) 即可看到结果
 

 

三、字体解析

 otf 就是常用的字体文件,可以使用系统自带的字体查看器查看,但是难以看到更多有效的信息,可以使用一个专用工具Font Creator查看。(Font Creator 汉化破解版下载地址:http://www.downcc.com/soft/242914.html)

可以看到,这个字体里有12个字(含一个空白字),每个字显示其字形和其字形编码。这里比之前字体解析更复杂的是,这里不仅字体编码每次都会变,字体顺序每次也会变,很难直接通过编码和顺序获取实际的数字。因此,我们需要预先下载一个字体文件,人工识别其对应数值和字体,然后针对每次获取的新的字体文件,通过比对字体字形数据,得到其真实的数字值。

下面是使用fontTools.ttLib获取的单个字符的字形数据。

    <TTGlyph name="uniE183" 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>

使用下面语句可以获取顺序的字符编码值,

# 解析字体库font文件
# 用一个base文件提前解析出文件的编码规律##############################################################################
# 访问字体的 url ,下载 字体文件 并 保存,这里保存文件名为 base.woff
base_font = TTFont('base.woff')  # 解析字体库font文件# 使用 "FontCreator字体查看软件" 查看字体的对应关系,然后设置对应关系
base_num_list = ['.', '3', '5', '1', '2', '7', '0', '6', '9', '8', '4']
base_unicode_list = ['x', 'uniE64B', 'uniE183', 'uniED06', 'uniE1AC', 'uniEA2D','uniEBF8', 'uniE831', 'uniF654', 'uniF25B', 'uniE3EB'
]
"""1. 字库对应的字形顺序不变,映射的 unicode 编码改变。 只需要找一次对应关系即可。2. 字库对应的字形顺序改变,映射的 unicode 编码也改变。需要找两次对应关系:第一次可以当基准对应关系,找到 字形 和 unicode 的对应关系第二次时,因为字形的数据都相同,可以找到字形的数据和第一次做基准的做对比,因为字形数据相同,可以找到第一次对应的字形所对应的第二次的 unicode 对应关系 
"""
##############################################################################
# 猫眼 属于 字形 顺序改变,unicode 编码也改变
mao_yan_font = TTFont('maoyan.woff')
mao_yan_unicode_list = mao_yan_font['cmap'].tables[0].ttFont.getGlyphOrder()
mao_yan_num_list = []for i in range(1, 12):mao_yan_glyph = mao_yan_font['glyf'][mao_yan_unicode_list[i]]for j in range(11):base_glyph = base_font['glyf'][base_unicode_list[j]]if mao_yan_glyph == base_glyph:mao_yan_num_list.append(base_num_list[j])break
pass

 

 

四、内容替换

关键点攻破了,整个工作就好做了。先访问需要爬取的页面,获取字体文件的动态访问地址并下载字体,读取用户帖子文本内容,替换其中的自定义字体编码为实际文本编码,就可复原网页为页面所见内容了。

完整代码如下:

# -*- coding:utf-8 -*-
import requests
from lxml import html
import re
import woff2otf
from fontTools.ttLib import TTFont
from bs4 import BeautifulSoup as bs#抓取maoyan票房
class MaoyanSpider:#页面初始化def __init__(self):self.headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","Accept-Encoding": "gzip, deflate, br","Accept-Language": "zh-CN,zh;q=0.8","Cache-Control": "max-age=0","Connection": "keep-alive","Upgrade-Insecure-Requests": "1","Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36"}# 获取票房def getNote(self):url = "http://maoyan.com/cinema/15887?poi=91871213"host = {'host':'maoyan.com','refer':'http://maoyan.com/news',}headers = dict(self.headers.items() + host.items())# 获取页面内容r = requests.get(url, headers=headers)#print r.textresponse = html.fromstring(r.text)u = r.text# 匹配ttf fontcmp = re.compile(",\n           url\('(//.*.woff)'\) format\('woff'\)")rst = cmp.findall(r.text)ttf = requests.get("http:" + rst[0], stream=True)with open("maoyan.woff", "wb") as pdf:for chunk in ttf.iter_content(chunk_size=1024):if chunk:pdf.write(chunk)# 转换woff字体为otf字体woff2otf.convert('maoyan.woff', 'maoyan.otf')# 解析字体库font文件baseFont = TTFont('base.otf')maoyanFont = TTFont('maoyan.otf')uniList = maoyanFont['cmap'].tables[0].ttFont.getGlyphOrder()numList = []baseNumList = ['.', '3', '5', '1', '2', '7', '0', '6', '9', '8', '4']baseUniCode = ['x', 'uniE64B', 'uniE183', 'uniED06', 'uniE1AC', 'uniEA2D', 'uniEBF8','uniE831', 'uniF654', 'uniF25B', 'uniE3EB']for i in range(1, 12):maoyanGlyph = maoyanFont['glyf'][uniList[i]]for j in range(11):baseGlyph = baseFont['glyf'][baseUniCode[j]]if maoyanGlyph == baseGlyph:numList.append(baseNumList[j])breakuniList[1] = 'uni0078'utf8List = [eval("u'\u" + uni[3:] + "'").encode("utf-8") for uni in uniList[1:]]# 获取发帖内容soup = bs(u,"html.parser")index=soup.find_all('div', {'class': 'show-list'})print '---------------Prices-----------------'for n in range(len(index)):mn=soup.find_all('h3', {'class': 'movie-name'})ting=soup.find_all('span', {'class': 'hall'})mt=soup.find_all('span', {'class': 'begin-time'})mw=soup.find_all('span', {'class': 'stonefont'})for i in range(len(mt)):moviename=mn[i].get_text()film_ting = ting[i].get_text()movietime=mt[i].get_text()moviewish=mw[i].get_text().encode('utf-8')for i in range(len(utf8List)):moviewish = moviewish.replace(utf8List[i], numList[i])                    print moviename,film_ting,movietime,moviewish
spider = MaoyanSpider()
spider.getNote()

解析访问,获取数据(最后一列是加密破解后的数据)。

 

 

 

反击“猫眼电影”网站的反爬虫策略

 

0×01 前言

前两天在百家号上看到一篇名为《反击爬虫,前端工程师的脑洞可以有多大?》的文章,文章从多方面结合实际情况列举了包括猫眼电影、美团、去哪儿等大型电商网站的反爬虫机制。的确,如文章所说,对于一张网页,我们往往希望它是结构良好,内容清晰的,这样搜索引擎才能准确地认知它;而反过来,又有一些情景,我们不希望内容能被轻易获取,比方说电商网站的交易额,高等学校网站的题目等。因为这些内容,往往是一个产品的生命线,必须做到有效地保护。这就是爬虫与反爬虫这一话题的由来。本文就以做的较好的“猫眼电影”网站为例,搞定他的反爬虫机制,轻松爬去我们想要的数据!

 

0×02 常见反爬虫

从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。而作为程序员的我们只关心数据采集部分,处理什么的还是交给那些数据分析师去搞吧。

一般来说,大多数网站会从三个方面反爬虫:用户请求的Headers,用户行为,网站目录和数据加载方式。前两种比较容易遇到,大多数网站都从这些角度来反爬虫,而第三种则相对比较特殊,一些应用ajax的网站会采用,这样无疑会增大了爬虫爬取的难度。

然而,这三种反爬虫策略则早已有应对的方法和策略。如果遇到了从用户请求的Headers反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。对于基于用户行为的反爬虫其实就是通过限制同一IP短时间内多次访问同一页面,应对策略也是很粗暴——使用IP代理,可以专门写一个爬虫,爬取网上公开的代理ip,检测后全部保存起来。有了大量代理ip后可以每请求几次更换一个ip,即可绕过这种反爬虫机制。对于最后一种动态页面反爬虫机制来讲, selenium+phantomJS框架能够让你在无界面的浏览器中模拟加载网页的动态请求,毕竟 selenium 可是自动化渗透的神器。

 

 

0×03 猫眼反爬虫介绍

 

介绍完常见的反爬虫机制,我们回过头看看我们今天的主角

 

 

先来个简单点的:天眼查

 

1. 打开天眼查

找到阿里巴巴的页面 https://www.tianyancha.com/company/59837300,可以看到勇哥帅气的照片。。。
(注意:直接打开需要登陆的话通过百度搜索 “天眼查 阿里” 再点击访问,如果是用python请求,headers 带上 "Referer": "https://www.baidu.com/",伪装成百度跳转过来的)

 

 

2. 查看源代码

查看源代码后发现,注册资本和注册时间是无法直接在源代码中获取正确的值。

 

3. 搜索 类 样式

这时需要思考原因,应该是 js 修改过或者替换过 字体文件,发现两个值的类都是 ”tyc-num“ ,可以尝试查看一下 类 的样式。
经过搜索,找到样式如下:

@font-face {font-family: "tyc-num";src: url("https://static.tianyancha.com/web-require-js/public/fonts/tyc-num-ad584829a0.eot");/* IE9*/src: url("https://static.tianyancha.com/web-require-js/public/fonts/tyc-num-ad584829a0.eot#iefix") format("embedded-opentype"), url("https://static.tianyancha.com/web-require-js/public/fonts/tyc-num-832854095c.woff") format("woff"), url("https://static.tianyancha.com/web-require-js/public/fonts/tyc-num-7f971a8be7.ttf") format("truetype"), url("https://static.tianyancha.com/web-require-js/public/fonts/tyc-num-67f91eabd9.svg#tic") format("svg");/* iOS 4.1- */
}.tyc-num {font-family: "tyc-num" !important;font-style: normal;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}

可以看出,这是用 css 更改了加载的字体文件,自定义了字体。

 

4. 使用开发者工具

在开发者工具中找到这个这个文件,看一下做了什么改变。

字体文件

 

显然是数字这个地方做了手脚,对比一下正常的字体。

正常的字体


好像比正常的还少了一个 "4",多了一个 "." ,猜想 "4" 对应的就是 "." ,所以这样可以得出数字的对应关系。

对应关系

  1. 把网页中的数据进行对应一下,果然没错。

423176999999万美元 ---> 15298.000000万美元
3995-90-38 ---> 2007-03-26

  1. 之后在提取数据时做一个逻辑判断替换即可得到真实数据了。

 

 

猫眼电影

 

1. 打开网页

打开猫眼电影详情页 http://maoyan.com/films/1198214,查看用户评分和累计票房,发现源码中是乱码。

 

猫眼电影的反爬虫是什么样的。

猫眼反爬虫

对于每日的电影院票价这一重要数据,源代码中展示的并不是纯粹的数字。而是在页面使用了font-face定义了字符集,并通过unicode去映射展示。简单介绍下这种新型的web-fongt反爬虫机制:使用web-font可以从网络加载字体,因此我们可以自己创建一套字体,设置自定义的字符映射关系表。例如设置0xefab是映射字符1,0xeba2是映射字符2,以此类推。当需要显示字符1时,网页的源码只会是0xefab,被采集的也只会是 0xefab,并不是1:

网页源码

因此采集者采集不到正确的票价数据:

票价数据不显示

采集者只能获取到类似&#xebc4;的数据,并不能知道”&#xebc4;”映射的字符是什么,实现了数据防采集。而对于正常访问的用户则没有影响,因为浏览器会加载css中的font字体为我们渲染好,实时显示在网页中。也就是说,除去图像识别,必须同时爬取字符集,才能识别出数字。 

查看猫眼的网站源文件正是如此:

网站源文件

 

2. 提取字体

提取加密的字体

所有的票价信息都是由动态font字体“加密”后得到的。既然知道了原理,我们就继续发掘,通过分析网站HTML结构,我们发现网站每次渲染票价的font字体都可以在网页的script标签中被找到:

网站 HTML 结构

字体是由base64加密后存储在网页中的,于是乎,上python:

# 将base64 加密的 font 文件解密转存本地
import requests
import base64
import re
from fontTools.ttLib import TTFontcustom_headers = {'Host': 'piaofang.maoyan.com','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) ''Gecko/20100101 Firefox/61.0','Accept': '*/*','Accept-Encoding': 'gzip, deflate','Referer': 'https://piaofang.maoyan.com/?ver=normal','Connection': 'keep-alive'
}
url = 'https://piaofang.maoyan.com/?ver=normal 'r = requests.get(url, headers=custom_headers)
font = re.findall(r"src:\(data:application/font-woff;charset=utf-8;base64,(.*?)\) format", r.text)[0]
font_data = base64.b64decode(font)
file = open('/home/jason/workspace/1.ttf', 'wb')
file.write(font_data)
file.close()

以上就是把 加密后的 字体 解密然后存储在本地为 文件名 是 1.ttf。我们在爬取时将font文件解密后存储在本地存储为ttf文件,留做备用。前文提到过这种web-font定义了字符集,要通过unicode去映射展示,所以,我们要构建ttf字体文件中unicode映射出来的字符字典:

构建字符字典

python代码:

import fontforge
def tff2Unicode():#将字体映射为unicode列表filename = '/home/jason/workspace/1.ttf'fnt = fontforge.open(filename)for i in fnt.glyphs():print i.unicode

我们猜测映射关系如下:

映射关系

还记得嘛,第三张图我们爬取到的数据是“绣春刀·修罗战场 341189 2017-07-20 6号厅 2D 国语 11:10 &#xebc4;&#xe1e7;”,我们将“&#”替换成“0”后对应上表得出的票价不是刚好是“29”嘛!

python代码:

tmp_dic={}
ttf_list = []
def creatTmpDic():  # 创建映射字典tmp_dic={}ttf_list = []num_list = [-1,-1,0,1,2,3,4,5,6,7,8,9]filename = '/home/jason/workspace/1.ttf'fnt = fontforge.open(filename)ttf_list = []for i in fnt.glyphs():ttf_list.append(i.unicode)tmp_dic = dict(zip(ttf_list,num_list))  # 构建字典return tmp_dic,ttf_list
def tff2price(para = ";",tmp_dic={},ttf_list = []):  # 将爬取的字符映射为字典中的数字tmp_return = ""for j in para.split(";"):if j != "":ss = j.replace("&#","0")for g in ttf_list:if (hex(g) == ss):tmp_return+=str(tmp_dic[g])return tmp_return

到此,我们已经可以说已经完成了对票价“加密”数据的破解啦~还是有点小小的成就感呢!但是,这里面还是有个很坑的地方:开发者已经想到采集者可以通过分析,知道每一个映射代表的意思,从而进行采集后转换处理,所以我们每次访问都是随机得到一种字体,而且开发者还定期更新一批字体文件和映射表用来加大采集的难度,所以我们在采集的过程中不得不每采集一个页面就更新一次本地的该网页的web-font字体,无疑会大大增加爬虫的爬取成本和爬取效率,所以从一定意义上确实实现了反爬虫。

实现爬取

 

提取没有加密的字体

如果没有加密,他们的 class 都是 长 这样子的。可以直接在 url 里面看到 字体的 url 地址,可以直接提取。

    @font-face {font-family: stonefont;src: url('//vfile.meituan.net/colorstone/1881db7c788dfdf9d2d00a926734d0973168.eot');src: url('//vfile.meituan.net/colorstone/1881db7c788dfdf9d2d00a926734d0973168.eot?#iefix') format('embedded-opentype'),url('//vfile.meituan.net/colorstone/632958fd02509dc28d915375c3a835e02088.woff') format('woff');}.stonefont {font-family: stonefont;}

    重点:    多刷新几次,看到每次 url 是变化的,确定是动态生成字体,不要紧,生成哪个下载哪个。

加载一个页面,把这个字体文件下载下来拿去分析,可能是在文件中数字的地方进行了替换。在网上正好有一片是防止爬虫采集的文章: 利用自定义web-font实现数据防采集,看过后恍然大悟,这不正是我们要的吗,嘿嘿,爬虫是防不住的!

用 python 的 fonttools 库提取字体,fonttools 的用法可以网上查找一下。

pip3 install fonttools        # 安装 

 

把提取的字体转换成人可以识别的 xml 格式

把 woff 的文件转换成我们熟悉的 xml 格式

from fontTools.ttLib import TTFont     # 导包font = TTFont('./632958fd02509dc28d915375c3a835e02088.woff')    # 打开文件
font.saveXML('./6329.xml')     # 转换成 xml 文件并保存

 

3. 查找字体对应关系

打开 xml 文件,红框内即为我们要的,略微不同,把 uni 改成 &#x ,后面再加一个分号 。

 

 

4. 在网页上验证对应关系是否正确

跟源代码中对应验证一下可以得出他们的对应关系如下。

 

5. 使用程序得到对应关系映射

用 fonttools 可以直接从文件得到这些值

from fontTools.ttLib import TTFont     # 导包font = TTFont('./632958fd02509dc28d915375c3a835e02088.woff')   # 打开文件
gly_list = font.getGlyphOrder()     # 获取 GlyphOrder 字段的值
for gly in gly_list[2:]:    # 前两个值不是我们要的,切片去掉print(gly)                 # 打印

 

最后补充完整代码

思路:
    前面知道字体库是随机的,可以提前把一些能刷新到的字体库下载到本地。
    抓取页面时,如果已经在本地,直接使用,不在的话再下载下来。

本地目录:字体存在 fonts 目录下

 

完整代码

import requests
import re
import os
from fontTools.ttLib import TTFontclass MaoYan(object):def __init__(self):self.url = 'http://maoyan.com/films/1198214'self.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 get_html(self, url):response = requests.get(url, headers=self.headers)return response.content# 创建 self.font 属性def create_font(self, font_file):# 列出已下载文件file_list = os.listdir('./fonts')# 判断是否已下载if font_file not in file_list:# 未下载则下载新库print('不在字体库中, 下载:', font_file)url = 'http://vfile.meituan.net/colorstone/' + font_filenew_file = self.get_html(url)with open('./fonts/' + font_file, 'wb') as f:f.write(new_file)# 打开字体文件,创建 self.font属性self.font = TTFont('./fonts/' + font_file)# 把获取到的数据用字体对应起来,得到真实数据def modify_data(self, data):# 获取 GlyphOrder 节点gly_list = self.font.getGlyphOrder()# 前两个不是需要的值,截掉gly_list = gly_list[2:]# 枚举, number是下标,正好对应真实的数字,gly是乱码for number, gly in enumerate(gly_list):# 把 gly 改成网页中的格式gly = gly.replace('uni', '&#x').lower() + ';'# 如果 gly 在字符串中,用对应数字替换if gly in data:data = data.replace(gly, str(number))# 返回替换后的字符串return datadef start_crawl(self):html = self.get_html(self.url).decode('utf-8')# 正则匹配字体文件font_file = re.findall(r'vfile\.meituan\.net\/colorstone\/(\w+\.woff)', html)[0]self.create_font(font_file)# 正则匹配星级star = re.findall(r'<span class="index-left info-num ">\s+<span class="stonefont">(.*?)</span>\s+</span>', html)[0]star = self.modify_data(star)# 正则匹配评论的人数people = ''.join(re.findall(r'''<span class='score-num'><span class="stonefont">(.*?万)</span>(人评分)</span>''', html)[0])people = self.modify_data(people)# 正则匹配累计票房ticket_number = ''.join(re.findall(r'''<span class="stonefont">(.*?)</span><span class="unit">(亿)</span>''', html)[0])ticket_number = self.modify_data(ticket_number)print('用户评分: %s 星' % star)print('评分人数: %s' % people)print('累计票房: %s' % ticket_number)if __name__ == '__main__':maoyan = MaoYan()maoyan.start_crawl()作者:谦面客
链接:https://www.jianshu.com/p/79c4272c0969
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

效果图

网页浏览效果

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/256373.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

《这是全网最硬核redis总结,谁赞成,谁反对?》六万字大合集

我摊牌了&#xff0c;这篇文章&#xff0c;值得99%的人收藏 此文后续会改为粉丝可见&#xff0c;所以喜欢的请提前关注和收藏&#xff0c;不迷路。 最近有五本我喜欢的redis实体新书&#xff0c;想要的去评论&#xff0c;我写个随机数抽奖包邮送给你。 那么&#xff0c;准备好…

如何实现第一个单片机裸机程序(附汇编指令)

一、(8-2) 一个芯片上面有片内SRAM内存(4K)&#xff0c;NOR Flash(2M) , Nand控制器(256M)&#xff0c;GPIO控制器 启动过程&#xff1a;(大多数ARM芯片从0地址启动) 1、NOR 启动&#xff0c; NOR Flash基址为0 CPU读取NOR上第一个指令(前4字节)执行&#xff0c;CPU继续读取其…

程序是怎样跑起来的

1. 对程序来说CPU是什么 问题&#xff1a; 程序是什么&#xff1f; 指示计算机每一步动作的一组指令程序是由什么组成的&#xff1f; 指令和数据什么是机器语言&#xff1f; CPU可以直接识别并使用的语言正在运行的程序存储在什么位置&#xff1f; 内存什么是内存地址&#x…

深入理解《hello world》是如何实现的

函数栈桢的创建和销毁 前言C语言是由函数构成的栈帧概念寄存器hello world是如何实现我们转到汇编代码&#x1f333; main函数栈帧的创建(开始调用main函数)&#x1f333; main函数栈帧的初始化&#x1f333;函数栈帧的销毁printf函数栈帧的销毁main函数的栈帧销毁 总结 ❤️ &…

【供应链架构day8】履约系统的架构长什么样子:从需求开始讲起

很多公司&#xff0c;除了自营商城以外&#xff0c;还有其它渠道&#xff08;如天猫、京东等&#xff09;&#xff0c;多个渠道的订单该如何集中履约&#xff1f;订单履约全流程是怎样的&#xff1f;接着小Q的故事&#xff0c;为您揭晓多平台订单履约系统的系统设计思路。 由于…

金融反欺诈-交易基础介绍

一、简介 如今&#xff0c;互联网金融比较火热&#xff0c;金融欺诈也变得非常普遍&#xff0c;金融反欺诈也应运而生。本文将主要介绍下金融交易中的一些基本内容&#xff0c;并简单介绍下历史悠久的并且还未淘汰的磁条卡的风险&#xff0c;这些也是了解金融欺诈需要的基本知识…

详解反调试技术

反调试技术&#xff0c;恶意代码用它识别是否被调试&#xff0c;或者让调试器失效。恶意代码编写者意识到分析人员经常使用调试器来观察恶意代码的操作&#xff0c;因此他们使用反调试技术尽可能地延长恶意代码的分析时间。为了阻止调试器的分析&#xff0c;当恶意代码意识到自…

stl文件的解析和在线3d打印

什么是stl文件 STL(Stereolithography)文件&#xff0c;由3D Systems于1987年创建&#xff0c;并且已被广泛用作全行业3D打印机模型的标准文件。它有一些别的首字母缩写词如“标准三角语言(Standard Triangle Language)”&#xff0c;“标准曲面细分语言(Standard Tessellatio…

\r \r\n \t的区别,是什么意思

分享一下我老师大神的人工智能教程吧。零基础&#xff0c;通俗易懂&#xff01;风趣幽默&#xff01;http://www.captainbed.net/ 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; \n 软回车&#xff1a; 在Windo…

著名的 P=NP 问题到底是什么?

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 大家好&#xff0c;我是猫哥。我最近在追一部热播的电视剧《天才基本法》&#xff0c;它反复提到了“PNP”问题。这可是一个天大的难题&#xff0c;在 2000 年克雷数学研究所公布的千禧年七大数学难题中&#xff0…

PCIe 是什么 ? -- 基本知识

1. 概述 1&#xff09;PCIe(Peripheral Component Interconnect Express)是继ISA和PCI总线之后的第三代I/O总线。一般翻译为周边设备高速连接标准。 2&#xff09;PCIe协议是一种端对端的互连协议&#xff0c;提供了高速传输带宽的解决方案。目前PCIe已经发展到第四代PCIe4.0, …

反欺诈概念库-信用卡反欺诈管理

原文&#xff1a;http://www.cnki.com.cn/Article/CJFDTotal-XYKZ200508004.htm 2005年6月&#xff0c;美国爆出4000万张信用卡资料外泄的特大新闻。消息传来&#xff0c;舆论哗然。尽管我国只有数千个信用卡账户数据资料受波及&#xff0c;但一石激起千层浪&#xff0c;国内金…

打印DPI如何与计算机DPI一致,打印效果失真 你了解DPI与照片关系么?

今天我们来聊一下关于分辨率的问题,分辨率这个词看似遥远,但是却和你的照片息息相关,文章开始之前我先向大家抛出一个问题,“我们要打印A4尺寸的照片,照片的像素至少要多大?”如果您能轻松的回答上来这个问题,那么这篇文章对于您的意义就不大了。 其实关于打印照片尺寸的…

常说的监听某个端口,是什么意思?怎么理解?

▲ 点击上方“分布式实验室”关注公众号 回复“1”抽取纸质技术书 今天这篇文章&#xff0c;想用一个故事和你讲讲端口监听是怎么回事。耐心往下看。 在学生会大楼的角落里&#xff0c;有一家咖啡店&#xff0c;在咖啡店的角落里有两个学生。利兹敲打着她哥哥在她搬到大学时给她…

RWKV – transformer 与 RNN 的强强联合

在 NLP (Natural Language Processing, 自然语言处理) 领域&#xff0c;ChatGPT 和其他的聊天机器人应用引起了极大的关注。每个社区为构建自己的应用&#xff0c;也都在持续地寻求强大、可靠的开源模型。自 Vaswani 等人于 2017 年首次提出 Attention Is All You Need 之后&am…

chatgpt赋能python:Python中的或运算:学习这个重要概念

Python中的或运算&#xff1a;学习这个重要概念 或运算是Python编程语言中一个重要的概念。了解如何使用或运算可以帮助程序员编写更有效和有意义的代码。在此文章中&#xff0c;我们将介绍Python中或运算的基础知识以及如何使用它来编写各种类型的代码。 什么是或运算&#…

智慧工厂主题 Meetup 线下报名+福利开启!IoTDB X EMQ 构建数据平台赋能智能制造...

随着全球制造业的竞争日益激烈&#xff0c;智慧工厂成为当今制造业的重要趋势之一。智慧工厂采用了先进的物联网、大数据等科技手段&#xff0c;以期通过智能化、数字化管理和生产&#xff0c;实现高度自动化和高效生产。因此&#xff0c;如何通过计算分析挖掘生产数据价值&…

《计算机组成原理》唐朔飞 第8章 CPU的结构和功能 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习计算机组成原理时的个人笔记&#xff0c;分享出来与大家学习交流。使用教材为唐朔飞第3版&#xff0c;笔记目录大体与教材相同。 网课 计算机组成原理&#xff08;哈工大刘宏伟&#xff09;135讲&#xff08;全&#xff09;高清_…

常用方法——7.JS 给数组排序 es6

let arrObj[{"name": "银行转账","value": 2}, {"name": "支付宝支付","value": 1}, {"name": "微信支付","value": 0} ] arrObj.sort((a,b)>{ return a.value-b.value})//升序…

JS数组对象排序(es6)

效果&#xff1a;升序&#xff1a; 降序&#xff1a; 升序是&#xff1a;a.value-b.value 降序是&#xff1a;b.value-a.value 代码&#xff1a; let arrObj[{"name": "银行转账","value": 2},{"name": "支付宝支付","…