字体反爬之实习僧

字体反爬是爬虫不可避免的一道关卡,因为这是成本比较低,而且效果还不错的一种方式。

今天我们先看看实习僧的字体爬虫怎么破解。首先我们先随便搜索一个职业,https://www.shixiseng.com/interns?k=数据库&p=1。F12查看源码发现,职业的某些汉字字母和所有数字都是框框,这基本可以确定使用了自定义字体。
在这里插入图片描述
这里可以看到li标签有一个font属性,点击一下这个标签,右边就会出现详细的css属性。我们只看.font,发现有font-family: myFont这个信息。我们先找到这个字体文件,这个可以去原网页或者加载的js里搜索,可以找到这段代码是包含在原网页的。而字体文件是以base64字符串的格式传输的,先用python自带的base64库解码一下字符串,然后另存为ttf文件。

import base64

s = '加密字符串'
with open('a.ttf', 'wb') as f:
	f.write(base64.b64decode(s))

然后用FontCreato这个工具打开字体文件:
在这里插入图片描述
这就是被重新编码的文字,他们的编码和utf-8是不一样的,所以浏览器会显示为框框。但浏览器在文字渲染(浏览器通过绘制相应像素点来达到显示整个汉字)的时候被显示成想要的汉字,因为它们的渲染代码在字体文件中被更改。

到了这里,我想很多人肯定是去百度找别人怎么解决这种问题的。当然,我也是,我看了很多关于字体反爬的文章,基本上是使用fonttools来寻找渲染字体代码是否相同,只要渲染代码相同,则判定为同一个字。为了方便人理解,我们先使用fonttools库将ttf文件转化为xml文件。

from  fontTools.ttLib  import  TTFont

font = TTFont('a.ttf')
font.saveXML('a.xml')

使用文本编辑器打开xml文件,这里我使用的是editplus。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
初看文件是看不懂文件表达的含义的,这里我粗略的说明我看懂的部分,也是爬虫要用到的那部分,至于文件的格式就不深究了。首先第一张图有id和name,每个ID会对应一个name,这两个值暂时是没有用的。我们再看第二张图,包含code和name,code代表这个字的编码的16进制,将0x改成\u就是网页源码中的框框的字符了,name会在第三张图中用到,第三种图表示将name所表示的code渲染成某种形状(即相应文字),只要对照每一个文字的这一段代码就可以判断是不是同一文字了,既然要对照,首先我们手里肯定要有一份已经知道渲染是什么文字的字体了。所以我们必须手工解码一份字体。

到这里后面我就不多说了,因为如果使用这种方法,那么爬虫的效率就有点低了,这样解码一套字体会耗费一定时间,虽然网站一般是每天或者每几天更新一次字体文件的,但工作量也不小。这样大量的工作也会长时间占用电脑大量的CPU。而且字体渲染不同其实浏览器也有可能显示为同一汉字,稍微改变一下字体形状就行(参考一下不同字体为什么不一样,你也能看成同一个字)。这样爬虫就不是要判断相等,而是判断一个范围,效率就更低了。于是我就秉承着程序员的核心思想继续思考:不会偷懒的程序员不是好程序员

看着别人的博客,发现别人用fontcreator打开的字体文件和我打开的文件不仅字是一样的,而且数量和顺序都是一样的(博客时间是2018年了),也就是说实习僧至少有一年没有更新网页代码了。虽然这个字体文件变化很频繁,但有没有可能所有的字体文件都有一个特定的顺序排序这些文字。这个猜想是很合理的,每个程序员都会有一个归一化的思想,比如代码结构,代码排版等。

我们看第一张图的ID就知道,文字顺序应该是ID来决定的,而右边的name只是文字的一个别名,这个别名不确定会不会变化,我们就当他会变化吧,那么我只需要获取ID和name对应的字典,还有code和name对应的字典,组合成ID和code对应的字典,再将fontcreator里面显示出来的文字按顺序放在一个列表,再使用ID作为索引取出列表对应的值,不就做成了一个code和文字的密码表了。操作一番后发现,猜想完全是正确的,当然我只是实验了一次,后面还需要 靠时间来验证。希望不要被打脸。

还有一种思路:只获取当然页面每个职业的具体url,然后访问子页面,子页面是只对十个数字重新编码的,那么我们只要手工获取这十个数字的编码表给爬虫就行。这是一种思路,但一般情况下不可取,因为它给爬虫增加了相当多的工作量,有多少数据就需要多访问多少个网页,这并不程序员。如果只是少量数据,可以这么操作,但如果需要大量数据的时候就显得很不合理了。

代码如下:

# -*- coding: utf-8 -*-
import base64
import re
import pyquery
import requests
from  fontTools.ttLib  import  TTFont


def get():
    url = 'https://www.shixiseng.com/interns?k=Python&p=31'
    headers = {'Host': 'www.shixiseng.com',
               'Referer': 'https://www.shixiseng.com/',
               'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
    }
    resp = requests.get(url, headers=headers)
    if resp.status_code == 200:
        return resp.text


class UniDecrypt(object):
    def __init__(self, ciphertext):
        self.ciphertext = ciphertext
        self.decrypt()
        self.analysis()
    
    def __call__(self, word):
        return word.translate(self.tab)
    
    def decrypt(self):
        s = base64.b64decode(self.ciphertext)
        with open('temp.ttf', 'wb') as f:
            f.write(s)
        font = TTFont('temp.ttf')
        font.saveXML('temp.xml')
    
    def analysis(self):
        words = '  0123456789一师X会四计财场DHLPT聘招工d周l端p年hx设程二五天tCG前KO网SWcgkosw广市月个BF告NRVZ作bfjnrvz三互生人政AJEI件M行QUYaeim软qu银y联'
        with open('temp.xml') as f:
            xml = f.read()
        temp1 = re.findall(r'<GlyphID id="(\d+)" name="(.*?)"/>',xml)
        temp2 = list(set(re.findall(r'<map code="(.*?)" name="(.*?)"/>',xml)))
        d2 = {x[1]:x[0] for x in temp2}
        #print(d2)
        wordtab = {chr(int(d2[x[1]], 16)):words[int(x[0])] for x in temp1 if not (x[0] == '0' or x[0] == '1')}
        self.tab = str.maketrans(wordtab)
        
    
if __name__ == '__main__':
#    with open('a.html') as f:
#        html = f.read()
    html = get()
    ciphertext = re.search(r'base64,(.*?)"', html).group(1)
    uni = UniDecrypt(ciphertext)
    
    doc = pyquery.PyQuery(html)
    position_list = doc('.position-list .position-item.clearfix.font').items()
    for position in position_list:
        job_name = position('.position-name').text()
        url = position('.position-name').attr('href')
        salary = position('.position-salary').text()
        place = position('.info2.clearfix span:first-of-type').text()
        work_day = position('.info2.clearfix span:nth-child(2)').text()
        least_month = position('.info2.clearfix span:last-of-type').text()
        company = position('.company-name').text()
        category = position('.company-more-info.clearfix span:first-of-type').text()
        scale = position('.company-more-info.clearfix span:last-of-type').text()
        d = {'job_name':uni(job_name),'url':uni(url),'salary':uni(salary),
             'place':uni(place),'work_day':uni(work_day),'least_month':uni(least_month),
             'company':uni(company),'category':uni(category),'scale':uni(scale)}
        print(d)
     

这样拿到密码表所花费的时间是非常短的(一两秒就行),基本拿到一次就可以在整个爬虫周期使用,如果哪天失效,只需要在获取一次就行。就算他每个网页都返回一个不同的字体文件,我们所花费的时间也不会太多,效率会远远高于对比字体。当然,这只是针对个例,而开始介绍的方法是比较通用的,另外,如果连fonttools都解决不了了,就只能使用OCR识别了。OCR的效率很低很低,不到万不得已,不要使用。

既然拿到了密码表,那么该如何快速替换爬虫中的字符呢?而且拿到的也只是0x一样的字符串,怎么变成\u一样的字符编码呢(replace(‘0x’, ‘\u’)或者replace(‘0x’, ‘\u’)就不用想了)。首先回答第一个问题,0x的字符变成\u的字符编码只需要使用int将0x字符串变成十六进制的数字,然后使用chr(数字)变成\u形式的字符编码了。替换文本中的一些字符,写100个replace当然可行,但是不是有点太不程序员了。其实python提供了内置的方法,请百度str.maketrans和str.translate。

下一篇博客:scrapy爬取实习僧所有数据

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
【为什么学爬虫?】        1、爬虫入手容易,但是深入较难,如何写出高效率的爬虫,如何写出灵活性高可扩展的爬虫都是一项技术活。另外在爬虫过程中,经常容易遇到被反爬虫,比如字体反爬、IP识别、验证码等,如何层层攻克难点拿到想要的数据,这门课程,你都能学到!        2、如果是作为一个其他行业的开发者,比如app开发,web开发,学习爬虫能让你加强对技术的认知,能够开发出更加安全的软件和网站 【课程设计】 一个完整的爬虫程序,无论大小,总体来说可以分成三个步骤,分别是:网络请求:模拟浏览器的行为从网上抓取数据。数据解析:将请求下来的数据进行过滤,提取我们想要的数据。数据存储:将提取到的数据存储到硬盘或者内存中。比如用mysql数据库或者redis等。那么本课程也是按照这几个步骤循序渐进的进行讲解,带领学生完整的掌握每个步骤的技术。另外,因为爬虫的多样性,在爬取的过程中可能会发生被反爬、效率低下等。因此我们又增加了两个章节用来提高爬虫程序的灵活性,分别是:爬虫进阶:包括IP代理,多线程爬虫,图形验证码识别、JS加密解密、动态网页爬虫、字体反爬识别等。Scrapy和分布式爬虫:Scrapy框架、Scrapy-redis组件、分布式爬虫等。通过爬虫进阶的知识点我们能应付大量的反爬网站,而Scrapy框架作为一个专业的爬虫框架,使用他可以快速提高我们编写爬虫程序的效率和速度。另外如果一台机器不能满足你的需求,我们可以用分布式爬虫让多台机器帮助你快速爬取数据。 从基础爬虫到商业化应用爬虫,本套课程满足您的所有需求!【课程服务】 专属付费社群+定期答疑

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值