字体反爬

字体反爬也就是自定义字体反爬,通过调用自定义的字体文件来渲染网页中的文字,而网页中的文字不再是文字,而是相应的字体编码,通过复制或者简单的采集是无法采集到编码后的文字内容的。

现在貌似不少网站都有采用这种反爬机制,我们通过猫眼的实际情况来解释一下。

下图的是猫眼网页上的显示:

检查元素看一下

这是什么鬼,关键信息全是乱码。

熟悉 CSS 的同学会知道,CSS 中有一个 @font-face,它允许网页开发者为其网页指定在线字体。原本是用来消除对用户电脑字体的依赖,现在有了新作用——反爬。

汉字光常用字就有好几千,如果全部放到自定义的字体中,那么字体文件就会变得很大,必然影响网页的加载速度,因此一般网站会选取关键内容加以保护,如上图,知道了等于不知道。

这里的乱码是由于 unicode 编码导致的,查看源文件可以看到具体的编码信息。

搜索 stonefont,找到 @font-face 的定义:

这里的 .woff 文件就是字体文件,我们将其下载下来,利用 http://fontstore.baidu.com/static/editor/index.html 网页将其打开,显示如下:

网页源码中显示的  跟这里显示的是不是有点像?事实上确实如此,去掉开头的 &#x 和结尾的 ; 后,剩余的4个16进制显示的数字加上 uni 就是字体文件中的编码。所以 &#xea0b 对应的就是数字“9”。

知道了原理,我们来看下如何实现。

处理字体文件,我们需要用到 FontTools 库。

先将字体文件转换为 xml 文件看下:

from fontTools.ttLib import TTFont

font = TTFont('bb70be69aaed960fa6ec3549342b87d82084.woff')
font.saveXML('bb70be69aaed960fa6ec3549342b87d82084.xml')

打开 xml 文件

开头显示的就是全部的编码,这里的 id 仅仅是编号而已,千万别当成是对应的真实值。实际上,整个字体文件中,没有任何地方是说明 EA0B 对应的真实值是啥的。

看到下面

这里就是每个字对应的字体信息,计算机显示的时候,根本不需要知道这个字是啥,只需要知道哪个像素是黑的,哪个像素是白的就可以了。

猫眼的字体文件是动态加载的,每次刷新都会变,虽然字体中定义的只有 0-9 这9个数字,但是编码和顺序都是会变的。就是说,这个字体文件中“EA0B”代表“9”,在别的文件中就不是了。

但是,有一样是不变的,就是这个字的形状,也就是上图中定义的这些点。

我们先随便下载一个字体文件,命名为 base.woff,然后利用 fontstore 网站查看编码和实际值的对应关系,手工做成字典并保存下来。爬虫爬取的时候,下载字体文件,根据网页源码中的编码,在字体文件中找到“字形”,再循环跟 base.woff 文件中的“字形”做比较,“字形”一样那就说明是同一个字了。在 base.woff 中找到“字形”后,获取“字形”的编码,而之前我们已经手工做好了编码跟值的映射表,由此就可以得到我们实际想要的值了。

这里的前提是每个字体文件中所定义的“字形”都是一样的(猫眼目前是这样的,以后也许还会更改策略),如果更复杂一点,每个字体中的“字形”都加一点点的随机形变,那这个方法就没有用了,只能祭出杀手锏“OCR”了。

下面是完整的代码,抓取的是猫眼2018年电影的第一页,由于主要是演示破解字体反爬,所以没有抓取全部的数据。

代码中使用的 base.woff 文件跟上面截图显示的不是同一个,所以会看到编码跟值跟上面是对不上的。

import os
import time
import re
import requests
from fontTools.ttLib import TTFont
from fake_useragent import UserAgent
from bs4 import BeautifulSoup host = 'http://maoyan.com' def main():
url = 'http://maoyan.com/films?yearId=13&offset=0'
get_moviescore(url) os.makedirs('font', exist_ok=True)
regex_woff = re.compile("(?<=url\(').*\.woff(?='\))")
regex_text = re.compile('(?<=<span class="stonefont">).*?(?=</span>)')
regex_font = re.compile('(?<=&#x).{4}(?=;)') basefont = TTFont('base.woff')
fontdict = {'uniF30D': '', 'uniE6A2': '', 'uniEA94': '', 'uniE9B1': '', 'uniF620': '',
'uniEA56': '', 'uniEF24': '', 'uniF53E': '', 'uniF170': '', 'uniEE37': ''} def get_moviescore(url):
# headers = {"User-Agent": UserAgent(verify_ssl=False).random}
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/68.0.3440.106 Safari/537.36'}
html = requests.get(url, headers=headers).text
soup = BeautifulSoup(html, 'lxml')
ddlist = soup.find_all('dd')
for dd in ddlist:
a = dd.find('a')
if a is not None:
link = host + a['href']
time.sleep(5)
dhtml = requests.get(link, headers=headers).text
msg = {} dsoup = BeautifulSoup(dhtml, 'lxml')
msg['name'] = dsoup.find(class_='name').text
ell = dsoup.find_all('li', {'class': 'ellipsis'})
msg['type'] = ell[0].text
msg['country'] = ell[1].text.split('/')[0].strip()
msg['length'] = ell[1].text.split('/')[1].strip()
msg['release-time'] = ell[2].text[:10] # 下载字体文件
woff = regex_woff.search(dhtml).group()
wofflink = 'http:' + woff
localname = 'font\\' + os.path.basename(wofflink)
if not os.path.exists(localname):
downloads(wofflink, localname)
font = TTFont(localname) # 其中含有 unicode 字符,BeautifulSoup 无法正常显示,只能用原始文本通过正则获取
ms = regex_text.findall(dhtml)
if len(ms) < 3:
msg['score'] = ''
msg['score-num'] = ''
msg['box-office'] = ''
else:
msg['score'] = get_fontnumber(font, ms[0])
msg['score-num'] = get_fontnumber(font, ms[1])
msg['box-office'] = get_fontnumber(font, ms[2]) + dsoup.find('span', class_='unit').text
print(msg) def get_fontnumber(newfont, text):
ms = regex_font.findall(text)
for m in ms:
text = text.replace(f'&#x{m};', get_num(newfont, f'uni{m.upper()}'))
return text def get_num(newfont, name):
uni = newfont['glyf'][name]
for k, v in fontdict.items():
if uni == basefont['glyf'][k]:
return v def downloads(url, localfn):
with open(localfn, 'wb+') as sw:
sw.write(requests.get(url).content) if __name__ == '__main__':
main()

也可以扫码关注我的个人公众号,后台回复 “猫眼”获取源码,及代码中我使用的 basefont。


相关博文推荐:

Python爬虫实例:爬取B站《工作细胞》短评——异步加载信息的爬取

Python爬虫实例:爬取豆瓣Top250

Python爬虫实例:爬取猫眼电影——破解字体反爬的更多相关文章

  1. 爬虫基本库request使用—爬取猫眼电影信息

    使用request库和正则表达式爬取猫眼电影信息. 1.爬取目标 猫眼电影TOP100的电影名称,时间,评分,等信息,将结果以文件存储. 2.准备工作 安装request库. 3.代码实现 impor ...

  2. 爬虫系列(1)-----python爬取猫眼电影top100榜

    对于Python初学者来说,爬虫技能是应该是最好入门,也是最能够有让自己有成就感的,今天在整理代码时,整理了一下之前自己学习爬虫的一些代码,今天先上一个简单的例子,手把手教你入门Python爬虫,爬取 ...

  3. PYTHON 爬虫笔记八:利用Requests+正则表达式爬取猫眼电影top100(实战项目一)

    利用Requests+正则表达式爬取猫眼电影top100 目标站点分析 流程框架 爬虫实战 使用requests库获取top100首页: import requests def get_one_pag ...

  4. Python使用asyncio+aiohttp异步爬取猫眼电影专业版

    asyncio是从pytohn3.4开始添加到标准库中的一个强大的异步并发库,可以很好地解决python中高并发的问题,入门学习可以参考官方文档 并发访问能极大的提高爬虫的性能,但是requests访 ...

  5. python 爬取猫眼电影top100数据

    最近有爬虫相关的需求,所以上B站找了个视频(链接在文末)看了一下,做了一个小程序出来,大体上没有修改,只是在最后的存储上,由txt换成了excel. 简要需求:爬虫爬取 猫眼电影TOP100榜单 数据 ...

  6. # [爬虫Demo] pyquery+csv爬取猫眼电影top100

    目录 [爬虫Demo] pyquery+csv爬取猫眼电影top100 站点分析 代码君 [爬虫Demo] pyquery+csv爬取猫眼电影top100 站点分析 https://maoyan.co ...

  7. # 爬虫连载系列(1)--爬取猫眼电影Top100

    前言 学习python有一段时间了,之前一直忙于学习数据分析,耽搁了原本计划的博客更新.趁着这段空闲时间,打算开始更新一个爬虫系列.内容大致包括:使用正则表达式.xpath.BeautifulSoup ...

  8. 爬虫--requests爬取猫眼电影排行榜

    '''目标:使用requests分页爬取猫眼电影中榜单栏目中TOP100榜的所有电影信息,并将信息写入文件URL地址:http://maoyan.com/board/4 其中参数offset表示其实条 ...

  9. Python 爬取猫眼电影最受期待榜

     主要爬取猫眼电影最受期待榜的电影排名.图片链接.名称.主演.上映时间. 思路:1.定义一个获取网页源代码的函数: 2.定义一个解析网页源代码的函数: 3.定义一个将解析的数据保存为本地文件的函数: ...

随机推荐

  1. 【莫比乌斯反演】BZOJ1101 [POI2007]zap

    Description 回答T组询问,有多少组gcd(x,y)=d,x<=a, y<=b.T, a, b<=4e5. Solution 显然对于gcd=d的,应该把a/d b/d,然 ...

  2. 集成支付宝SDK流程

    5.2 SDK集成流程 5.2.1 iOS 解压接口压缩文件(文件名是 WS_MOBILE_PAY_SDK_BASE.zip),找到iOS的压缩文件(文件名是支付宝移动支付SDK 标准版(iOS).z ...

  3. NavigationView头部设置监听事件

    直接写解决方法吧: 1.将XML里的静态引入删除: <android.support.design.widget.NavigationView android:id="@+id/nav ...

  4. Java基础-工厂设计模式(三锅的肥鸡)

    ---恢复内容开始---   1)还没有工厂时代:假如还没有工业革命,如果一个你要一架飞机,一般的做法是自己去建造一架飞机,然后拿来开 通常的结果就是 有些时候 要么专科螺钉 没打好  要么就是 那个 ...

  5. mybatis 异常Result Maps collection does not contain value for java.lang.String

    Result Maps collection does not contain value for java.lang.String 以上是我报的错. 只要报Result Maps collectio ...

  6. r.js合并实践 --项目中用到require.js做生产时模块开发 r.js build.js配置详解

    本文所用源代码已上传,需要的朋友自行下载:点我下载 第一步: 全局安装  npm install -g requirejs 第二步: 1.以下例子主要实现功能, 1)引用jq库获取dom中元素文本, ...

  7. vue中的 ref 和 $refs

    如图,ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例: ...

  8. java游戏开发杂谈 - 事件处理

    大家都知道,游戏需要跟玩家交互,需要接收玩家的鼠标.键盘发出的命令,比如在地图上点击一下,人物就自动寻路走过去:键盘上按下某个键,就弹出一个背包界面. 这些逻辑是怎么处理的呢? 大家先不用深究太详细的 ...

  9. SpringBoot进阶教程(二十四)整合Redis

    缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力.Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非 ...

  10. Python进阶:设计模式之迭代器模式

    在软件开发领域中,人们经常会用到这一个概念——“设计模式”(design pattern),它是一种针对软件设计的共性问题而提出的解决方案.在一本圣经级的书籍<设计模式:可复用面向对象软件的基础 ...