上一篇文章我们使用 Scrapy + Selenium 爬取了某个电影网站即将上映的影片
但是该网站针对一些比较敏感的数据(比如:票房、热度、评分等)做了字体反爬
本篇文章将以「 影片热度 」为例,讲解字体反爬的完整处理方案
1、安装依赖
# 依赖
# OCR
pip3 install ddddocr
# 字体管理
pip3 install fontTools
# 图片管理
pip3 install Pillow
2、下载字体及格式转换
通过分析,我们发现关键数字与网页中中引入的字体样式有关,并且每次刷新页面,引用的字体地址是变化的
因此,我们需要获取网页源码,利用正则表达式解析出字体的下载地址
def download_font(url, font_path):
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Host": "www.maoyan.com",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Google Chrome\";v=\"114\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\""
}
# 获取网页源码
resp_pre = requests.get(url, headers=headers)
resp = resp_pre.text
# 解析出字体文件(woff格式)的下载地址
font_file = re.findall(r's3plus\.meituan\.net\/v1\/mss_73a511b8f91f43d0bdae92584ea6330b\/font\/(\w+\.woff)', resp)[
0]
font_url = 'https://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/' + font_file
获取字体 URL 下载地址后,我们将字体文件下载到本地
需要注意的是,下载字体时设置请求头和上面请求头不一致,不然下载的字体可能受损
font_headers = {
'authority': 's3plus.meituan.net',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'origin': 'https://www.**.com',
'referer': 'https://www.**.com/',
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'font',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
}
# 下载字体文件到本地
# 注意:这里下载字体必须使用特有的Header,不然下载的字体会受损
font_content = requests.get(font_url, headers=font_headers)
with open(f'./{font_file}', 'wb') as f:
f.write(font_content.content)
f.close()
最后,将 woff 字体文件转换为 ttf 格式
from fontTools.ttLib.woff2 import decompress
...
# 将 woff文件转成 ttf 文件
decompress(font_file, font_path)
3、字体映射关系
通过 FontCreator 工具打开字体文件,可以获取数字和字体编码的映射关系
通过对多个字体文件进行对比发现,上面的映射关系不是固定的
因此,我们需要借助字体图片绘制及 OCR,动态获取字体中的映射关系
from PIL import ImageFont, Image, ImageDraw
from io import BytesIO
from fontTools.ttLib import TTFont
from fontTools.ttLib.woff2 import decompress
import ddddocr
def get_font_keymap(font_path):
...
# 映射字典
font_dict = {}
# 解析字体文件中的编码对应关系
for cmap_code, glyph_name in font.getBestCmap().items():
# print(cmap_code,glyph_name)
# 实例化一个图片对象(给定的模式和大小创建一个新图像) 白色
img = Image.new('1', (img_size, img_size), 255)
# img.show()
# 绘制图片
draw = ImageDraw.Draw(img)
x, y = draw.textsize(chr(cmap_code), font=font_img)
draw.text(((img_size - x) // 2, (img_size - y) // 2), txt, font=font_img, fill=0)
bytes_io = BytesIO()
img.save(bytes_io, format="PNG")
# 使用OCR识别字体
content= ocr.classification(bytes_io.getvalue())
# 加入到键值对中
font_dict[glyph_name] = content
return font_dict
4、网页内容还原
通过上面数字与字体编码的映射关系,我们将网页中做了字体反爬的内容替换为正确的数字
# 3、替换源码,将加密内容替换为明文
# .
for key in font_dict.keys():
# print("key:", f'&#x{key[3:]};'.lower())
# 去掉前面3个字符,将内容替换成对应的数字
resp = resp.replace(f'&#x{key[3:]};'.lower(), font_dict[key])
5、爬虫
接下来,我们就可以对网页关键数据进行提取的
import re
from lxml import etree
import os
# 使用lxml解析HTML
root = etree.HTML(resp)
elements = root.xpath('//span[@class="stonefont"]/text()')
for element in elements:
print(element)
我已经将文中所有源码上传到后台,回复关键字「 230710 」即可以获取完整源码
如果你觉得文章还不错,请大家 点赞、分享、留言 下,因为这将是我持续输出更多优质文章的最强动力!
推荐阅读