https://blog.csdn.net/qq_31032181/article/details/79153578
一、背景
字体反爬应用还是很普遍。这两天有朋友咨询如何实现猫眼票房数据的爬取,这里其实与上面的文章核心思想是一致的,但是操作更复杂一些,本文做一个更详细的破解实践。
有对字体反爬还比较陌生的,请参考前文。
二、查找字体源
猫眼电影是美团旗下的一家集媒体内容、在线购票、用户互动社交、电影衍生品销售等服务的一站式电影互联网平台。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
三、字体解析
otf就是我们常用的字体文件,可以使用系统自带的字体查看器查看,但是难以看到更多有效的信息,我们使用一个专用工具Font Creator查看。
可以看到,这个字体里有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文件
-
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])
-
break
四、内容替换
关键点攻破了,整个工作就好做了。先访问需要爬取的页面,获取字体文件的动态访问地址并下载字体,读取用户帖子文本内容,替换其中的自定义字体编码为实际文本编码,就可复原网页为页面所见内容了。
完整代码如下:
-
# -*- 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.text
-
response = html.fromstring(r.text)
-
u = r.text
-
# 匹配ttf font
-
cmp = 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])
-
break
-
uniList[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()
解析访问,获取数据(最后一列是加密破解后的数据)。
Python爬虫杂记 - 字体文件反爬(二)
https://www.jianshu.com/p/0e2e1aa6d270
在线字体转化工具