字体反爬

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

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

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

检查元素看一下

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

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

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

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

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

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

网页源码中显示的  跟这里显示的是不是有点像?事实上确实如此,去掉开头的 和结尾的 ; 后,剩余的4个16进制显示的数字加上 uni 就是字体文件中的编码。所以  对应的就是数字“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('(?<=).*?(?=)')

regex_font = re.compile('(?<=).{4}(?=;)')

basefont = TTFont('base.woff')

fontdict = {'uniF30D': '0', 'uniE6A2': '8', 'uniEA94': '9', 'uniE9B1': '2', 'uniF620': '6',

'uniEA56': '3', 'uniEF24': '1', 'uniF53E': '4', 'uniF170': '5', 'uniEE37': '7'}

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'] = '0'

msg['score-num'] = '0'

msg['box-office'] = '0'

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'{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。

相关博文推荐:

原文地址:https://www.cnblogs.com/gl1573/p/9994286.html

python爬取b站搜索结果_Python爬虫实例:爬取猫眼电影——破解字体反爬,Python爬虫实例:爬取B站《工作细胞》短评——异步加载信息的爬取,Python爬虫实例:爬取豆瓣Top250...相关推荐

  1. Python爬虫实例:爬取猫眼电影——破解字体反爬

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

  2. AsyncTask异步加载的源码分析与实现实例

    一 . AsyncTask Android的Lazy Load主要体现在网络数据(图片)异步加载.数据库查询.复杂业务逻辑处理以及费时任务操作导致的异步处理等方面.在介绍Android开发过程中,异步 ...

  3. 《封号码罗》python爬虫之抖音分享页破解字体反爬虫进阶实战(六)

    无敌免责声明:本文主要用于学习技术,切不可用于非法盈利目的,不可用于商业,不可攻击该服务器 先放出结果镇楼: {'nickname': 'Dear-迪丽热巴', '抖音ID': '274110380' ...

  4. python 异步加载图片_Python 爬取拉钩网异步加载页面

    如下是我简单的获取拉钩网异步加载页面信息的过程 获取的是深圳 Python 岗位的所有信息,并保存在Mongo中 (对于异步加载,有的人说是把你要爬页面的信息整个页面先爬下来,保存本地,然后再看有没有 ...

  5. 从入门到入土:Python爬虫学习|实例练手|详细讲解|爬取腾讯招聘网|一步一步分析|异步加载|初级难度反扒处理|寻找消失的API来找工作吧

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  6. Python爬虫 解决异步加载问题--以爬取PEXELS图片为例

    第一次尝试爬取->[Python爬虫]爬虫实例:三种方式爬取PEXELS图片 在爬取PEXELS时,遇到了这样问题: 页面使用Ajax的异步加载技术来实现分页,所以通过request.text无 ...

  7. Python爬取京东iphone8的异步加载评论

    运行环境 Python 3.X 寻找评论信息地址 我们打开京东商城,搜索iphone8 iphone8虽然买不起,但是看看也欢迎 我们点击进去查看详情 往下翻,找到商品评价,点击 不仅有评论,而且还有 ...

  8. python提取ajax异步加载数据_python爬取豆瓣电影分类排行榜引出的异步加载(AJAX)问题...

    1.背景 之前的文章中已经介绍过猫眼TOP100的电影信息爬取案例,网页每页有10条电影信息,通过翻页发现URL变化规律构造循环爬取10页100条全部电影信息.但是豆瓣电影分类排行榜的网页情况就所不同 ...

  9. python 异步加载_Python学习笔记4——爬取异步加载数据

    一.什么是异步加载? 在之前的学习笔记中,爬取的网页是需要手动翻页的网址,但是一些网站是通过自动加载翻页的,如knewone网页.浏览knewone的官网就能发现,当下拉到网页最下端时,网站会自动加载 ...

最新文章

  1. 近期活动盘点:大咖云集,中国AI创新者论坛(3.21)
  2. UVa10763 交换学生
  3. 14行代码AC——1017 A除以B (20分)(大数运算+讲解)
  4. 设计类的五个原则_内容设计的5个原则
  5. 数论六之计算几何——An Easy Problem,Ancient Berland Circus,Open-air shopping malls
  6. LeetCode 541. 反转字符串 II
  7. 计算机创新课,计算机教学课程模式与创新论文
  8. 如何快速下载CentOS7镜像
  9. 5个数中取三个数组合 不重复 php,PHP产生不重复随机数的5个方法总结
  10. java中位操作_Java中使用位操作的几个小技巧
  11. marshaller java 乱码_Marshaller根据对象生成xml文件
  12. node.js常见问题之:解决npm install出错(Cannot find module ‘internal/util/types‘)
  13. SQL Server 游标运用:查看所有数据库所有表大小信息(Sizes of All Tables in All Database)...
  14. 【C/C++】C++重复率最高、最经典面试题/笔试题【持续更新】
  15. 每天一个算法(简单)
  16. Qt+OpenCV配置教程(图解亲测)
  17. 世界500强面试题(趣味智力测试题)
  18. python输入直角三角形两条直角边、输出斜边长_用C++输入直角三角形的两条直角边长,调用平方根函数sqrt来求斜边的长度。...
  19. Linux C编程 —— 通过文件描述符获得文件路径
  20. 重装系统时将mbr分区改为gpt分区

热门文章

  1. Button设置elevation阴影
  2. iOS音频播放 (三):AudioFileStream 转
  3. Jira-使用docker安装jira(支持最新版本)
  4. 《炬丰科技-半导体工艺》不破坏MEMS结构的颗粒去除方法
  5. 追觅、戴森、石头扫地机器人对比测评,哪个性价比更高
  6. 轻松洁净地面,还能自动清洗烘干拖布,只需一台追觅扫拖机器人
  7. Intent之对象传递(Parcelable传递对象和对象集合)
  8. HDOJ2955 Robberies(01背包,概率)
  9. istio安全之Citadel
  10. (十五):常用的十种算法(下)