上一篇讲的是网页中使用固定的字体映射表,也就是网页中显示的数据编码和真实数据的映射在一段时间内不会发生变化。但是,有的网页中对应的映射会随时发生变化,我们以东方财富网为例,下图是我们一开始所抓取得到的部分信息
我们在不同的时间内分别下次其网页中使用的字体文件,并将其转化为xml文件来进行查看。
字体1 | 字体2 |
---|---|
![]() | ![]() |
会发现每个字体文件中的code
都会发生变化,并且name
也是一些看不懂的字符串,不像我们之前所看到的uni
编码的字符可以直接进行转化,那怎么办呢?仔细观察的话可以发现两个字体文件中的name
的值都是一样,因此我们可以判断不管你在网页中使用什么样的自定义字体,虽然每一次code
都会发生变化,但是其中的name
并不会发生变化,多尝试几次后发现事实便是如此。因此我们只需要把name
多代表的真实数据的映射给表示出来即可,再通过该映射把网页中使用的自定义字体的code:name
映射进行替换即可。
对于name
所对应的值,我们可以去查看xml文件中其对应的对象,以bgldyy
为例,可以看到
其中每对(x,y)表示的便是bgldyy
所代表的真实数据的坐标,如下
显然,通过我们自己是无法知道这些坐标的组合所代表的数据到底是多少,因此这个时候就需要借助其他软件了,这里我们使用百度字体编辑器,将我们得到的字体文件导入到其中,可得(论文推导多了,现在不管什么都是写个可得(捂脸))
因此我们可以写出name
与真实数据的映射(上图中的$xxxx为网页中所对应的代码点)
{'zbxtdyc':'4', 'whyhyx':'9', 'wqqdzs':'3', 'bgldyy':'7', 'nhpdjl':'5', 'qqdwzl':'1', 'bdzypyc':'0', 'zwdxtdy':'8', 'sxyzdxn': '6', 'zrwqqdl':'2'}
通过上述映射,我们便可以实现当前网页中所使用的自定义字体的映射表,如
{'0xe273':'nhpdjl',
'0xe375':'bdzypyc',
'0xe426':'bgldyy'}
所对应的真实数据的映射为
{'0xe273':'5',
'0xe375':'0',
'0xe426':'7'}
再将所得到的映射表与网页源码中的显示的代码点(如:)进行替换即可得到真实的数据信息。
其他不多说,直接上源码,想说的都写在注释里了:
import requests
from pyquery import PyQuery as pq
import re
from fontTools.ttLib import TTFont
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'
}
def download_html(url):
"""
用于下载网页源码,当网页下载失败时,抛出异常
:param url: 网页链接
:return: 网页源码
"""
response = requests.get(url = url, headers = HEADERS)
if response.status_code == 200:
return response.text
else:
raise Exception('url download failed, please retry')
def font_map(html):
"""
获取当前网页中所使用的自定义字体的映射,其具体步骤为:
1.获取当前网页中所使用的自定义文件的地址,并对其进行下载
2.首先在不提取数据的情况下观察字体文件(转化为xml文件观察)内容,得到相应的{name:真实数据}映射
3.得到当前网页所使用的字体映射{code:name}
4.根据2中得到的映射表,将3中的映射表进行相应的替换,得到{code:真实数据}映射
:param html: 当前网页的源码
:return: 当前网页所使用的自定义字体{code:真实数据}映射
"""
font_url = re.findall(r'"WoffUrl":"(.*?woff)', html, re.M) # 当前网页所使用的自定义字体文件地址
font = requests.get(font_url[0], headers = HEADERS).content
with open('new_font.woff', 'wb+') as f:
f.write(font)
font = TTFont('new_font.woff')
# font.saveXML('new_font.xml') # 将字体保存为xml文件以方便查看
fontdict = {'zbxtdyc':'4', 'whyhyx':'9', 'wqqdzs':'3', 'bgldyy':'7', 'nhpdjl':'5', 'qqdwzl':'1',
'bdzypyc':'0', 'zwdxtdy':'8', 'sxyzdxn': '6', 'zrwqqdl':'2'} # 一个基本的{name:真实数据}映射表,供后面使用
font_map = {} # 用于存储当前页面所使用的自定义字体映射的字典
for key, value in font.getBestCmap().items(): # 使用getBestCmap()获取字体文件中包含的映射关系
if value.startswith('uni'): # 判断是否是uni编码
font_map[hex(key).upper()] = chr(int(value[3:], 16)) # 这里的upper需看网页中显示的代码点是否是大写的,如果是小写的话,就不需要使用这个了
else:
font_map[hex(key).upper()] = value
font_map.pop('0X78') # 去掉第一个没用的code
for key, value in font_map.items(): # 根据之前得到的{name:真实数据}映射,将font_map中的name进行替换
font_map[key] = fontdict[value]
new_font_map = {}
for key, value in font_map.items(): # 根据网页中的code显示格式来进行替换
key = key.replace('0X', '&#x')
new_font_map[key] = value
return new_font_map
def parse_data(html, new_font_map):
"""
抓取我们所需要的数据,并将其中的code代码点进行替换为真实数据显示
这里我们只选用了众多数据中的一条来进行显示
:param html: 当前网页的源码
:param new_font_map: 当前网页所使用的自定义字体的{code:真实数据}映射表
:print: 抓取的数据
"""
results = re.findall(r'defjson:.*?data: \[(.*)],font', html, re.S)
datas = results[0]
result = re.findall(r'{(.*?)}', datas)[0]
for key, value in new_font_map.items(): # 根据所得到的{code:真实数据}映射将网页源码进行替换
if key in result:
result = re.sub(key, value, result)
result = result.replace(';', '')
print(result)
def main(url):
"""
爬虫入口
:param url: 当前访问的网页链接
"""
html = download_html(url)
new_font_map = font_map(html)
parse_data(html, new_font_map)
if __name__ == '__main__':
url = 'http://data.eastmoney.com/bbsj/201806/lrb.html'
main(url)
得到的部分结果为:
与原始网页比较可知,能够正确的实现字体的替换。
好好学习,天天向上。