猫眼字体反爬

一.背景

字体反爬是什么鬼?我才听说的时候一脸懵逼.最后发现是字体编码方式.简单解释就是一种映射关系,网页的特定编码方式.说再多也不如来一图.

看图,无名之辈的票房是不是大家都能看懂,4029.11万.接下来showtime到了.

4029.11是不是很爽快的变成一堆堆框框了.老实交代有木有打开猫眼观察了.是不是惊悚的发现数字基本都变了.这是什么鬼,其实就是一种自定义字体.不对不对,也不一定是一种,因为亲爱的猫眼就是很多种自定义字体.所以你还会绝望的发现你每次打开网页看到的票房显示的是一样的,但是当你打开开发者工具却发现自定对应的映射每次都变了.待会再介绍详情.接下来我们在网页代码里面搜索font.对,就是很多个框框前面那个stonefont.为什么不搜stonefont呢,因为你会搜出40个结果来.然后你搜一下font就能得到字体映射对应的网址.

url()里面的就是字体映射所在的网页.其实这也解释了为什么只有数字才反爬.不对应该说少量的字体.因为汽车之家里面有一些中文也是这样的.理论上来说如果你对自己服务器的性能非常自信,你也可以把所有的字体都映射一遍.那基本就不用爬了.

我们把url括号里的网址提取出来.然后打开这网页.下载得到字体.然后用百度的工具http://fontstore.baidu.com/static/editor/index.html打开看看这个字体映射里面到底包括了多少字符.

里面一个有12个字符.第一个是空格,有用的是后面的.是不是看到每个数字下面都有一个uni开头的字符?这个就是对应的关键.(这里我不太完全理解.我感觉应该是unicode表的字符,而不是utf-8编码所以在开发者工具里并不能显示出来)那么我们再来详细看看这个字符里面的构成.

这一步需要一些辅助,先安装这个库.

pip install fonttools

然后利用python将字体文件转化成能观察的,如下

from fontTools.ttLib import TTFont

font=TTFont('base.otf')   
font.saveXML('base.xml')

这样用sublime打开base.xml就可以看到完整的字体文档了,也就跟网页结构差不多.

各位可爱的帅哥美女有没有发现这个跟刚刚百度工具打开的uni字符很像啊,准确的说不是像而是直接就下.但是我们不可能每次都打开看啊,他们到底怎么对应的,不急请看下图是不是发现有个name="uniE183".是个这个结构里面就是uniE183的字符形状.

这就解释了为什么是半自动了.我们首先下载一套字体,然后查看一套字体对应的数字关系,然后爬虫运行的时候把字体下载下来,通过每一个字符对应的图形对比,如果字体图形一样,那么就代表了是指同一个数字,这也是为什么我一直称它为映射的原因.

 

二.编程实现

接下来就撸起袖子开始干啦.

 

1.需要的库

import requests
from fake_useragent import UserAgent
from pyquery import PyQuery as pq
import re
from lxml import html
import woff2otf
from fontTools.ttLib import TTFont

这些库都可以通过pip安装.唯一需要注意的是woff2otf这个是github上大神写的,必须将附带的两个文档也下了.然后把这两个文档放在和你程序同目录下才能导入这个woff2otf库.

 

2.返回网页源码

def get_page():
    url = 'http://maoyan.com/'
    ua = UserAgent()
    headers = {'User-Agent': ua.random}
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
    except requests.ConnectionError as e:
        print(e.args)

这一步是有两用处,一是得到字体编码的网址.就是url后面括号里面的网址,用来下载字体文件,二是得到你要爬的信息.

 

3.下载字体

def down_ziti(html):
    ua = UserAgent()
    headers = {'User-Agent': ua.random}
    cmp = re.compile(",\n.*?url\('(.*?.woff)'\)")
    url = cmp.findall(html)
    url = 'http:' + url[0]
    response = requests.get(url, headers=headers, stream=True)
    with open('maoyan.woff', 'wb') as f:
        for chunk in response.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)
    woff2otf.convert('maoyan.woff', 'maoyan.otf')

这里是下载字体文件,requests里的stream是用来分段下载,默认是False,改成True之后几乎是分段下载.

 

4.解析字体

def parse_ziti():
    baseFont = TTFont('base.otf')
    maoyanFont = TTFont('maoyan.otf')
    baseNumberList = ['.', '3', '5', '1', '2', '7', '0', '6', '9', '8', '4']
    baseUnicode = ['x', 'uniE64B', 'uniE183', 'uniED06', 'uniE1AC', 'uniEA2D', 'uniEBF8',
        'uniE831', 'uniF654', 'uniF25B', 'uniE3EB']
    maoyanNumberList = []
    maoyanUnicode = maoyanFont.getGlyphOrder()
    for i in range(1, 12):
        maoyanGlyph = maoyanFont['glyf'][maoyanUnicode[i]]
        for j in range(11):
            baseGlyph = baseFont['glyf'][baseUnicode[j]]
            if maoyanGlyph == baseGlyph:
                maoyanNumberList.append(baseNumberList[j])
                break
    maoyanUnicode[1] = 'uni0078'
    utf8List = [eval("u'\\u" + uni[3:] + "'").encode('utf-8') for uni in maoyanUnicode[1:]]
    return (utf8List, maoyanUnicode, maoyanNumberList)

如上所说,解析字体对比字体符号图形得出对应的数字

 

5.解析要爬的数据

def parse_page(html):
    doc = pq(html)
    data = {}
    if doc:
        items = doc('.content .aside .ranking-box-wrapper .panel .ranking-wrapper li')
        for k,item in enumerate(items.items()):
            if k == 0:
                data['name'] = item.find('.ranking-top-moive-name').text()
            else:
                data['name'] = item.find('.ranking-movie-name').text()
            data['score'] = item.find('.stonefont').text()
            yield data

得到今日票房信息.我这里使用今日票房信息做实例.

 

6.schedule

html = get_page()
down_ziti(html)
utf8List, maoyanUnicode, maoyanNumberList = parse_ziti()
maoyanUnicode = [item.encode('utf-8') for item in maoyanUnicode]
maoyanNumberList = [item.encode('utf-8') for item in maoyanNumberList]
items = parse_page(html)
for item in items:
    item['score'] = item['score'].encode('utf-8')
    for k in range(len(utf8List)):
        item['score'] = item['score'].replace(utf8List[k], maoyanNumberList[k])
    print(item['name'], item['score'].decode() + '万')

 

7.结果

 

三.总结

在程序里面我是将字体转化为utf-8编码然后在用replace()得到对应数字的.其实之间也有操作过不转化为utf-8,直接用unicode编码.然后有个问题是unicode在python里面显示的是一堆堆框框,转化失败.发现根本没法.然后就只能老老实实的按大神的思路照着转化成utf-8.

在这里提一下,字体编码方式一定要统一,毕竟计算机不知道一和1是不是一样的.所以得编码统一,等以后研究透了为什么要转换成utf-8.我再来补充.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值