这两天一直在看字体反爬方面的文章,现在难一点的还没摸清怎么搞,但是58的品牌公寓的字体反爬相对简单一些,已经自己做出来了,特此记下来,也可以帮刚在这方面入门的小伙伴更快熟悉起来。整体代码我会在文末发出来。
话不多说,开始正题
这篇文章用到的python模块有这些
import requests
import re
import base64
from bs4 import BeautifulSoup
from fontTools.ttLib import TTFont
from xml.dom.minidom import parse
import xml.dom.minidom
打开58公寓的页面,链接点这里
页面是这样的
打开调试
可以看到源码是乱码的
用代码跑出来之后,是这样的
很明显的,出现了字体反爬。一般这种反爬,网页文件里是会有他们自定义的字体文件的。
这种字体一般是ttf后缀或者woff后缀的。我们在网页源码中尝试搜索ttf或者woff
这里的一大堆蓝色的字体就是他们的自定义字体文件了,我们可以通过正则,把base64,后面的和format前面的这些东西取出来,比如:
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
取出来之后,我们之前看到了base64,所以应该是需要base64解码的,解码之后其实还是乱码,但是无所谓,我们不看,交给工具看。把解码后的这些东西存入文件中,比如:
font_data_after_decode = base64.b64decode(ttf_text)
with open('./world.ttf', 'wb') as f:
f.write(font_data_after_decode)
存入文件之后,我们需要一个工具软件来打开这个文件,看看是什么。可以百度下载安装 FontCreator。
我们打开这个文件之后,看到是这样的
有10个数字,以及他们的对应编码。
我们在网页源码中搜索编码会发现这些编码所对应的数字都对应网页上已经渲染出来的正确的数字。也就是说,我们把这些编码换成数字就是我们要的数据了。但是很遗憾,我们每次请求,所获得的编码与数字的对应关系是会改变的。
比如这次的9EA3对应着数字1,但是下次就是别的编码对应的数字1.所以我们需要每次请求,都把字体文件存下来,然后寻找对应关系。但是现在这种寻找对应关系的方法,是我们用工具软件打开,手动识别的。我们应该交给代码去完成。所以,我们首先要用代码找到每次请求所产生的编码与数字的对应关系。
说了这么多,那这个编码该怎么用程序找到呢。此时我们可以使用python的一个模块,把咱们刚才存的.ttf文件转换成xml文件。
font = TTFont('./world.ttf')
font.saveXML('my.xml')
打开这个生成的xml文件,上面我们通过FontCreator软件发现数字0对应的编码是9A4B,我们在xml文件中搜9A4B。
对应的name值是glyph00001,我们在xml文件中继续搜glyph00001
找到这里就对了,有很多x,y,on。我们待会儿就靠这个找对应关系。那是后话,我们一步一步来。
此时要做的就是建立一个字典,把name值和数字的对应关系建立起来,我们刚才看的,数字0对应的name值应该是glyph00001,依次类推,建立字典对应关系,像这样:
dict={'glyph00001':'0','glyph00002':'1','glyph00003':'2','glyph00004':'3','glyph00005':'4','glyph00006':'5','glyph00007':'6','glyph00008':'7','glyph00009':'8','glyph00010':'9'}
这时,我们再一次请求网页,把字体文件存到一个新文件里,看看这次数字和name值之间的关系。
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
font_data_after_decode = base64.b64decode(ttf_text)
with open('./newworld.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./newworld.ttf')
font.saveXML('newmy.xml')
打开新的xml文件,并且用FontCreator软件打开新的ttf文件。
可以看到,编码和数字的关系已经跟第一次不一样了。我们还是去xml文件里找0对应的编码9F92
对应name值是glyph00001,根据name值继续搜
有没有发现,x,y,on这些跟咱们第一次找到的一样。也就是说,虽然每次请求网页,编码跟数字的对应关系会变,但是我们通过这个编码找到的name值所对应的x,y,on这些是不会变的。0这个数字对应着两个文件里相同的x,y,on.
那么想一下接下来要怎么做了
我这里有个办法,我们接下来可以兵分两路
第一路是不是可以拿着这一块x,y,on去第一次的文件里寻找到它对应的name值,这个name值对应的数字是不是已经在字典里定义好了,就此我们找到了这块x,y,on对应的数字。
第二路可以拿着这块x,y,on去新文件里找到它对应的name值,再通过这个name值就找到了它对应的编码。
此时两路汇合,一路拿着数字,一路拿着编码,编码和数字的对应关系是不是搞定了。
此时,把网页里的编码换成数字就完成了此次的字体反爬。
具体代码我发在这里,可以结合着理解一下:
# -*- coding: utf-8 -*-
# @Author : LMD
# @FILE : 58品牌公寓.py
# @Time : 2019/12/25 10:32
# @Software : PyCharm
import requests
import re
import base64
from bs4 import BeautifulSoup
from fontTools.ttLib import TTFont
from xml.dom.minidom import parse
import xml.dom.minidom
def pc():
headers={
'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
url='https://cd.58.com/pinpaigongyu/?from=58_pc_zonghe_home_ppgy_wzl_ppgy&utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d100000-0006-6bfd-576d-2eb43f45cb17&ClickID=2'
rq=requests.get(url,headers=headers)
rq.encoding='utf8'
data=rq.text
# print(data)
return data
#第一次运行,先存一次字体文件
def base_xml(data):
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
#base64解码
font_data_after_decode = base64.b64decode(ttf_text)
with open('./world.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./world.ttf')
font.saveXML('my.xml')
def get_ttf(data):
#正则匹配处理字体文件
ttf_text=re.findall("base64,(.*?)format",data)[0].replace("')",'')
# print(ttf_text)
#base64解码
font_data_after_decode = base64.b64decode(ttf_text)
with open('./newworld.ttf', 'wb') as f:
f.write(font_data_after_decode)
font = TTFont('./newworld.ttf')
font.saveXML('newmy.xml')
#打开xml文档
dom = xml.dom.minidom.parse('newmy.xml')
#dom对象转字符串
collection = dom.documentElement.toxml()
soup=BeautifulSoup(collection,'lxml')
cmap_format_4=soup.find('cmap_format_4')
# print(cmap_format_4)
dict={'glyph00010':'9','glyph00001':'0','glyph00002':'1','glyph00003':'2','glyph00004':'3','glyph00005':'4','glyph00006':'5','glyph00007':'6','glyph00008':'7','glyph00009':'8'}
font1= TTFont('./world.ttf')
uni_list1=font1.getGlyphOrder()[1:]
print(uni_list1)
font2= TTFont('./newworld.ttf')
uni_list2=font2.getGlyphOrder()[1:]
print(uni_list2)
unicode_news=[]
nums=[]
for uni2 in uni_list2:
obj2=font2['glyf'][uni2]
for uni1 in uni_list1:
obj1=font1['glyf'][uni1]
if obj1==obj2:
print(uni2,dict[uni1])
unicode_div=soup.find('map',attrs={'name':uni2})
print(unicode_div)
unicode_new=unicode_div['code'].replace('0x','')
print(unicode_new,dict[uni1])
unicode_news.append(unicode_new)
nums.append(dict[uni1])
#替换编码为数字
new_data=data.replace(unicode_news[0],nums[0]).replace(unicode_news[1],nums[1]).replace(unicode_news[2],nums[2]).replace(unicode_news[3],nums[3]).replace(unicode_news[4],nums[4]).replace(unicode_news[5],nums[5]).replace(unicode_news[6],nums[6]).replace(unicode_news[7],nums[7]).replace(unicode_news[8],nums[8]).replace(unicode_news[9],nums[9]).replace('&#x','').replace(';','')
# print(new_data)
soup=BeautifulSoup(new_data,'lxml')
print(soup)
if __name__=='__main__':
data=pc()
#第一次运行,先运行 base_xml(data),注释掉get_ttf(data)。此时是为了调试,查看
base_xml(data)
#以后运行,注释掉 base_xml(data),运行get_ttf(data)即可
# get_ttf(data)
老规矩,看下效果: