Python—猫眼电影票房爬虫实战 轻松弄懂字体反爬!

 

目标

1.爬取猫眼票房信息

2.将信息保存起来(MySQL、Redis、MongoDB或者Excel等等),我们保存的是Excel

爬取网址

https://piaofang.maoyan.com/?ver=normal&isid_key=2d9aacd2f26d4d0dba63

分析

1.打开chrome,输入网址我们看到的效果如下

2.我们要获取的信息如下图

3.右击检查打开开发者工具,选择左下角鼠标按钮,然后点击一个电影的标题

可以看到这个标签里面就是我们想要获取的电影标题信息,别急,我们在接着往下看,再次点击标题下面的7.86亿和实时票房,可以发现开发者工具里面显示的并不是我们看见的数字,这就是我们本次爬虫的重点字体反爬

字体反爬顾名思义,就是通过css加载本地的字体文件,在网页上展示特定的字体,我们想要获取信息,就必须要弄到这个字体文件,然后解析得到我们想要的数据

4.开发者工具上无法显示这个特殊的字体,这时候我们右击点查看源代码,就可以看见一串编码,这每个编码就表示一个对应的数字

5.想要破解字体,我们要先得到他的字体文件,在源代码页面Ctrl+F搜索font,可以看到这个就是加密的字体

通过分析这段代码可以看出这个是这个是通过base64进行加密的,文件后缀为woff

通过上面的分析可以得出结论:我们想要得到数据,打开开发者工具发现我们想要的数据进行了字体反爬,查看网页原代码分析找到了字体文件是通过base64加密的,即我们只要吧字体文件下载下来,然后进行base64解密,保存到本地,在通过字体文件解析我们爬下来的网页数据就能得到我们想要的数据了

看到这里,肯定很多小伙伴就会想,这字体反爬好简单啊,就分析出他的字体文件,然后下载下来进行解析就能得到我们的数据了,确实,简单的字体反爬分析到这里就已经结束了,但我只能说,你还是太年轻了,下面我们在接着往下分析

6.点击左上角重新请求这个网站,数据还是这些数据,这时候我们在右击查看源代码,找到一个有反爬字体的地方和font,和前一个网页源代码进行比较,可以发现同样的数据,但编码号变了,base64加密后的font也变了,这就是本次猫眼爬虫的另外一个难点,即每次重新请求都会有一套新的编码和字体文件

结论

  • 我们想要得到数据,就需要解决以下这些问题
    1.先解码数据
    2.需要字体文件
    3.每次请求字体文件会产生变化

前期准备

为了完成下面的代码,我们需要先做一些准备

  • 首先我们先将源代码页面的加密字体复制下来,放到py中生成基础的字体文件
# 使用bs64进行解码
import base64


# 保存加密字体
font_s = "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"

# base64解密
b = base64.b64decode(font_s)

# 打开文件保存字体文件
with open('maoyan_pf.woff', 'wb') as f:
    f.write(b)
  • 使用TTFont模块读取字体文件,并转换成pycharm可以打开的xml格式
from fontTools.ttLib import TTFont


# 建立字体文件对象
base_font = TTFont("maoyan_pf.woff")
# 转换成xml格式
base_font.saveXML("maoyan_pf.xml")
  • 打开字体xml文件就可以看见解密后保存的字体文件了,code对应的是每个字体的形状名,即name,而name对应的就是每个字体的形状,即坐标,对的,这个字体文件里的每个字都是通过坐标画出来的
  • 下面是一个字体对应的一部分坐标
  • 是不是根本看不明白?别急,下面我们可以借助一个软件High-Logic FontCreator来打开字体文件打开软件后,将刚刚保存的woff字体文件拖到软件中
  • 通过软件我们一眼就能看出每个编码对应的数字了,有了这个我们就能建立一个基础的字体映射字典,通过刚刚的字体对象里面的glyf可以取出所有name对应的字体形状,在参照软件上打开的字体文件信息,用数字当做基础映射字典的key,用取出来的形状当基础映射字典的value,这个基础映射字典只能看着软件手动组成
from fontTools.ttLib import TTFont


# 建立字体文件对象
base_font = TTFont("maoyan_pf.woff")
# 转换成xml格式
base_font.saveXML("maoyan_pf.xml")
# 获取name和形状之间的关系
base_glyph = base_font["glyf"]

# 组装出基础映射字典
base_num_glyph_map = {
    0: base_glyph["uniF1DD"],
    1: base_glyph["uniE09F"],
    2: base_glyph["uniEF13"],
    3: base_glyph["uniED29"],
    4: base_glyph["uniEE6F"],
    5: base_glyph["uniE853"],
    6: base_glyph["uniF44B"],
    7: base_glyph["uniEF72"],
    8: base_glyph["uniF814"],
    9: base_glyph["uniF466"],
}

代码

  • 准备完成之后,下面我们开始创建项目,开始编写爬虫,这次我们使用requests模块来进行数据爬取
    1.导入requests模块,创建类,将前面准备的基础映射字典、请求url和请求体放到__init__初始化函数,定义run方法作为开启爬虫的函数,在run方法中请求url,打印获取到的HTML
import requests


class MYSpider(object):
    """爬取猫眼即将上映页面"""

    def __init__(self):
    	# 请求url
        self.url = "https://piaofang.maoyan.com/?ver=normal&isid_key=2d9aacd2f26d4d0dba63"
        # 请求头
        self.headers = {
            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
        }
        # 建立字体文件对象
        base_font = TTFont("maoyan_pf.woff")
        # 获取name和形状之间的关系
        base_glyph = base_font["glyf"]

        # 组装出基础映射字典
        self.base_num_glyph_map = {
            0: base_glyph["uniF1DD"],
            1: base_glyph["uniE09F"],
            2: base_glyph["uniEF13"],
            3: base_glyph["uniED29"],
            4: base_glyph["uniEE6F"],
            5: base_glyph["uniE853"],
            6: base_glyph["uniF44B"],
            7: base_glyph["uniEF72"],
            8: base_glyph["uniF814"],
            9: base_glyph["uniF466"],
        }
        
	def run(self):
        """开始爬取"""
        # 发送请求
        response = requests.get(url=self.url, headers=self.headers)
        # 打印获取的HTML
        print(response.text)
        

if __name__ == '__main__':
    my = MYSpider()  # 创建爬虫对象
    my.run()  # 运行爬虫

运行之后可以发现确实是我们想要的数据

2.下面开始提取加密字体文件,进行base64解密

  • 观察源代码里面的加密字体是从base64, 开始到  结束的这一段就是加密字体
  • 将获取到的HTML转换成lxml对象进行xpath提取这段加密字体,并调用方法进行解密
def run(self):
        """开始爬取"""
        # 发送请求
        response = requests.get(url=self.url, headers=self.headers)
        # print(response.text)
        # 转换成lxml对象进行xpath提取
        html = etree.HTML(response.text)

        # 获取字体加密
        # 提取style标签里面的内容
        encryption_font = html.xpath("//style[@id='js-nuwa']/text()")[0].split()[0]
        # 使用正则提取想要的加密字体
        encryption_font = re.search(r";base64,(.+)\)$", encryption_font).group(1)
        # 调用方法对字体进行解密
        self.decryption_font(encryption_font)
  • 定义新方法decryption_font将获取到的新加密字体进行base64解密,并保存到新字体文件中
def decryption_font(self, encryption_font):
	   """字体解密保存"""
	   # 使用bs64进行解码
	   b = base64.b64decode(encryption_font)
	
	   # 打开文件保存字体文件
	   with open('maoyan_pf_new.woff', 'wb') as f:
	       f.write(b)
  • 定义新方法font_map对新字体进行映射,这里用到了一个 knn 算法(K近邻)的思想,因为每次请求得到的都是新的字体,所以本次请求的字体与上次请求的字体坐标会有微差,但不管怎么变,这个字肯定不可能变成别的字,所以我们只有得到本次请求的字体坐标与基础的映射字典里的字体坐标进行对比就能,然后取出最接近的值,那么肯定就是我们想要的值了
def font_map(self):
        """对字体生成新的映射"""
        # 生成当前字体文件的对象
        font = TTFont('maoyan_pf_new.woff')
        font.saveXML('maoyan_pf_new.xml')  # 将ttf文件生成xml文件并保存到本地
        # 获取当前字体的code和name映射
        code_name_cmap2 = font.getBestCmap()
        # 获取当前字体的字形
        name_glyph_map = font['glyf']

        # 遍历当前字体的code和name
        for code, name in code_name_cmap2.items():
            # 判断是否是无用的数据
            if name == "x":
                continue

            # 通过name取出当前字体的所有字形坐标
            current_glyph = name_glyph_map[name]
            num_diff = dict()

            # 遍历基础字形字典,取出对应的映射数字和坐标
            for num, glyph in self.base_num_glyph_map.items():
                # 定义一个变量用来记录当前所有坐标的最小差值
                diff = 0

                # 遍历当前字形字典,取出所有坐标
                for coor1 in current_glyph.coordinates:
                    # 定义一个列表用来保存当前最小差值的所有差值
                    coor_diff_list = list()
                    for coor2 in glyph.coordinates:
                        coor_diff_list.append(abs(coor1[0] - coor2[0]) + abs(coor1[1] - coor2[1]))
                    diff += min(coor_diff_list)
                # 组成当前字体的映射字典
                num_diff[num] = diff

            # 取出对应的映射
            num = min(num_diff, key=num_diff.get)
            # code = str(hex(code)).replace("0", "&#", 1) + ";"
            # 将默认映射替换成想要的样式
            code = str(hex(code)).replace("0x", r"\u", 1)
            # print(code, num)
            # 将新字体的映射组成字典保存
            self.new_num_glyph_map[code] = num
  • 获取详情信息,定义新方法get_info获取想要的信息,组成字典,保存到列表中
 def run(self):
     """开始爬取"""
     # 发送请求
     response = requests.get(url=self.url, headers=self.headers)
     # print(response.text)
     # 转换成lxml对象进行xpath提取
     html = etree.HTML(response.text)

     # 获取字体加密
     # 提取style标签里面的内容
     encryption_font = html.xpath("//style[@id='js-nuwa']/text()")[0].split()[0]
     # 使用正则提取想要的加密字体
     encryption_font = re.search(r";base64,(.+)\)$", encryption_font).group(1)
     # 调用方法对字体进行解密
     self.decryption_font(encryption_font)

     # 对新字体进行映射
     self.font_map()

     # 获取详情
     ul_list = html.xpath("*//div[@class='content strip']/ul")
     # print(ul_list)
     # 调用方法
     self.get_info(ul_list)
     
 def get_info(self, ul_list):
        """获取详情信息"""
        for ul in ul_list:
            # print(ul)
            # 定义字典保存当前信息
            info_dict = dict()
            temp = ul.xpath(".//li[@class='c1']//text()")
            # print(temp)
            info_dict["片名"] = temp[1]
            info_dict["上映首日"] = temp[4]
            info_dict["实时票房(万元)"] = ul.xpath(".//li[@class='c2 ']/b/i/text()")[0]
            info_dict["票房占比"] = ul.xpath(".//li[@class='c3 ']/i/text()")[0]
            info_dict["排片占比"] = ul.xpath(".//li[@class='c4 ']/i/text()")[0]
            info_dict["上座率"] = ul.xpath(".//li[@class='c5 ']//i/text()")[0]
            # print(info_dict)
            # 调用方法进行解密
            info_dict = self.decryption_info(info_dict)

            # 保存进列表
            self.info_list.append(info_dict)
        # print(self.info_list)
  • 定义新方法save_excel将数据保存到Execl,就大功告成了
    def save_excel(self):
        """将数据保存到Excel"""
        # 创建一个workbook 设置编码
        workbook = xlwt.Workbook(encoding='utf-8')
        # 创建一个worksheet
        worksheet = workbook.add_sheet("Arknights")
        # 写入excel
        # 参数对应 行, 列, 值
        for i, content in enumerate(self.info_list):
            for x, info in enumerate(content.values()):
                worksheet.write(i, x, label=info)  # 将数据存入excel
        # 保存
        workbook.save('猫眼票房.xls')
  • 爬取到的Excel

到这里我们本次的猫眼爬虫就结束了,如果对你有帮助,不妨点个赞~

完整项目代码获取加群哦:1136192749

此文转载网络  如有侵权,联系删除

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值