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

 

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://www.mzph.cn/news/496122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字签名,数字证书,证书链原理

来源&#xff1a;数字签名&#xff0c;数字证书&#xff0c;证书链原理&#xff08;图文详解&#xff09;_Ruby丶彬的博客-CSDN博客_证书链验证原理 数字签名&#xff0c;数字证书&#xff0c;加密简述 数字签名&#xff1a;谈及数字签名&#xff0c;就如小时候老师叫把卷子或…

成都信息工程c语言题库,成都信息工程学院C语言考试题及答案

}in 0;while (sum < im) { }sum - in; in--;printf(\sum%d\\n\in , sum); return 0;in; sum in;编写一程序P215.C实现以下功能求S1/1&#xff01;1/2&#xff01;1/3&#xff01;…1/N&#xff01;并输出结果(显示时小数部分占16位&#xff0c;计算时要求从第1项开始往后累…

转--javascript 数组

较为上篇更为深刻 原文&#xff1a;http://blog.csdn.net/jaylongli/archive/2009/03/20/4007823.aspx 数组有四种定义的方式 使用构造函数&#xff1a; vara newArray(); varb newArray(8); varc newArray("first", "second", "third"); 或者数…

猫眼 — 破解数字反爬获取实时票房

From&#xff1a;https://zhuanlan.zhihu.com/p/33112359 js分析 猫_眼_电_影 字体文件 font-face&#xff1a;https://www.cnblogs.com/my8100/p/js_maoyandianying.html 解析某电影和某招聘网站的web-font自定义字体&#xff1a;https://www.jianshu.com/p/5400bbc8b634 Fo…

c语言中单精度的有效位数是,C语言中的单精度双精度数的有效位数

#include "stdio.h"#include "conio.h"void main(){printf("%.20f\n",0.1234567890123456789f);getch();}观察在VC6&#xff0c;WinTC中的运行结果&#xff0c;可能是0.1234567891043281560000.12345678901234567700而一般在C语言教材上说&#…

Yoshua Bengio团队通过在网络「隐藏空间」中使用降噪器以提高深度神经网络的「鲁棒性」

原文来源&#xff1a;arXiv 作者&#xff1a;Alex Lamb、Jonathan Binas、Anirudh Goyal、Dmitriy Serdyuk、Sandeep Subramanian、Ioannis Mitliagkas、Yoshua Bengio「雷克世界」编译&#xff1a;嗯~是阿童木呀、KABUDA、EVA导语&#xff1a;深度神经网络在各种各样的重要任…

PDF签名系列(1):PDF签名机制的漏洞分析

来源&#xff1a;PDF签名系列(1):PDF签名机制的漏洞分析 - 知乎 研究PDF文件的签名机制有一段时间了&#xff0c;刚开始学习的时候就看到有提到说&#xff0c;被签名的PDF内容的Range gap&#xff0c;会成为这个机制的漏洞&#xff0c;但是一直不能完全参透。直到昨天看到一篇…

linux date命令显示毫秒,解决MacOS系统中date命令没有毫秒和sha512sum、sha256sum、md5sum等命令的问题...

安装brew/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"使用brew安装coreutilsbrew install coreutils设置系统新的命令软连接sudo ln -sv /usr/local/bin/gsha256sum /usr/local/bin/sha256sumsudo ln -sv /…

PDF签名系列(2):PDF的签名值到底存在哪里?

来源&#xff1a;PDF签名系列(2):PDF的签名值到底存在哪里? - 知乎 研究过PDF签名的同学应该见过下面这张图, 来自ADOBE的文档Acrobat_DigitalSignatures_in_PDF.pdf PDF被签名的内容是整个文档除去signature dictionary里/Contents下面的内容. 签过名的哈希值再连同其他一些…

android zip文件读写,如何直接从.zip文件中读取文件而不在android中提取它

我过去几个月一直在研究android,现在我的问题是读取放在SD卡上的.zip文件.我已经成功完成了在SD卡上下载.zip文件的编码.我有img.zip文件下载到SD卡上.这个img.zip包含5个图像文件.现在我可以直接阅读其内容而不是解压缩img.zip …… ???如果是的话请帮忙.我在互联网上看到了…

jQuery的radio,checkbox,select操作

获取一组radio被选中项的值 var item $(input[nameitems][checked]).val(); 获取select被选中项的文本 var item $("select[nameitems] option[selected]").text(); select下拉框的第二个元素为当前选中值 $(#select_id)[0].selectedIndex 1; radio单选组的第二个…

数字签名时间戳服务器的原理

时间戳服务工作流程&#xff1a; ◆ 用户对文件数据进行Hash摘要处理&#xff1b; ◆ 用户提出时间戳的请求&#xff0c;Hash值被传递给时间戳服务器&#xff1b; ◆ 时间戳服务器对哈希值和一个日期/时间记录进行签名&#xff0c;生成时间戳&#xff1b; ◆ 时间戳数据和文…

android x86 笔记本卡屏,笔记本死机卡屏怎么办

笔记本死机卡屏的解决方法&#xff1a;首先按住键盘上ctrlaltdel&#xff0c;弹出任务管理器&#xff1b;然后在弹出任务管理器中选择explorer.exe&#xff1b;接着右键点击explorer.exe&#xff0c;在弹出菜单中选择“结束进程”&#xff1b;最后输如explorer.exe即可。首先&a…

scrapy 伪装代理和 fake_userAgent 的使用

From&#xff1a;https://www.cnblogs.com/hellangels333/p/8918938.html fake-useragent 下载 和 使用方法&#xff1a;https://pypi.org/project/fake-useragent 伪装浏览器代理 在爬取网页是有些服务器对请求过滤的不是很高可以不用ip来伪装请求直接将自己的浏览器信息给伪…

readyState的五种状态详解【转】

在《Pragmatic Ajax A Web 2.0 Primer 》中偶然看到对readyStae状态的介绍&#xff0c;感觉这个介绍很实在&#xff0c;摘译如下&#xff1a; 0: (Uninitialized) the send( ) method has not yet been invoked. 1: (Loading) the send( ) method has been invoked, request i…

【读书笔记】《框架设计(第2版)CLR Via C#》中两个比较有趣的知识点

本季度公司要求阅读《框架设计&#xff08;第2版&#xff09;CLR Via C#》这本书&#xff0c;由于前两个月一直很忙&#xff0c;也没有时间阅读&#xff0c;偶尔阅读也是很晚回家以后临睡前拿起这经典之作读那么一个小节。最近利用周末可以说一鼓作气的看了大半本&#xff0c;感…

Python 使用 Scrapy 发送 post 请求的坑

From&#xff1a;https://www.jb51.net/article/146769.htm 使用 requests 发送 post 请求 先来看看使用requests来发送post请求是多少好用&#xff0c;发送请求 Requests 简便的 API 意味着所有 HTTP 请求类型都是显而易见的。 例如&#xff0c;你可以这样发送一个 HTTP POST …

Effective Java~46. 优先选择Stream 中无副作用的函数

纯函数&#xff08;pure function&#xff09;的结果仅取决于其输入&#xff1a;它不依赖于任何可变状态&#xff0c;也不更新任何状态。 坏味道 // Uses the streams API but not the paradigm--Dont do this! Map<String, Long> freq new HashMap<>(); try (S…

android applybatch,android – 使用applyBatch插入成千上万的联系人条目很慢

我正在开发一个应用程序&#xff0c;我需要插入大量的联系人条目。在当前时间约600个联系人&#xff0c;共有6000个电话号码。最大的联系人有1800个电话号码。截至今天的状态是&#xff0c;我创建了一个自定义帐户来保存联系人&#xff0c;因此用户可以选择在联系人视图中查看联…

[译]How to make searching faster

Here are some things to try to speed up the seaching speed of your Lucene application. Please see ImproveIndexingSpeed for how to speed up indexing. 以下是一些尝试提高lucene程序检索速度的方法. 如果需要提高索引速度,请看提高索引速度. Be sure you really need …