爬虫练习(猫眼电影解密)

news2024/11/15 11:03:32

问题

        随便拿一篇电影做样例。我们发现猫眼的页面数据在预览窗口中全是小方框。在当我们拿到源码以后,数据全是加密后的。所以我们需要想办法破解加密,拿到数据。

破解过程 

        1.源码获取问题与破解

        分析

        在我们刚刚请求url的时候是可以得到数据的,但是过了一段时间后就无法获得数据。虽然状态码为200,但是却没有返回页面源码

一般这种应该是和时间戳有关系,在查看请求负载的时候我们发送,浏览器向这个url不仅发送了时间戳还有一个signKey的密钥。时间戳可以很容易得到,主要问题是如何获得signKey。

        全局搜索signKey,我们发现一段js代码,它的返回值就是我们请求负载的内容。所以需要想办法还原这段js代码。

        分析后发现:

  • d:获取当前时间的函数
  • r:随机数取整
  • c:内容如下method=GET&timeStamp=1725264890773&User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0&index=8&channelId=40011&sVersion=1
    • 可以发现就是多个信息进行拼接(时间戳+User-Agent+index+channelId+sVersion)。
  • f:固定为&key=A013F70DB97834C0A5492378BD76C53A

        分析图片如下:

        同时我们还发现signKey是通过MD5加密(c+f)后得到的。因为1经过MD5加密后得到的内容就是c4ca4238a0b923820dcc509a6f75849b,所以我们可以猜测(0,a[i(_0x140e("0xe4"))])('c+f')就是一个MD5的加密。

         js编写与调用

        有了以上分析后,我们就可以拿页面原始的js代码进行适当的改动。修改后的js代码如下,我们直接返回网页负载需要的params。

        添加首页cookie

        在完成上面步骤后,我们调用js,虽然得到了params,但是还是无法获得到页面的源代码,这可能和cookie有关系,所以我们创建一个session,通过访问首页来保存首页的cookie,然后再来访问这个url看看结果。

        我们发现浏览器请求了两次https://www.maoyan.com/,且第一次存在302跳转,跳转到https://www.maoyan.com/,所以是请求了两次。在python代码中,我们只需要请求有302跳转的链接即可,因为程序会自动进行第二次跳转。

        添加cookie后,使用python程序调用js代码返回params,使用js生成的params去访问url地址运行结果如下:

        2.字体破解

        字体图片下载

        在拿到页面源码以后,我们需要对数字进行获取。直接在返回的源码中搜索,获取.woff文件。得到url://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/e3dfe524.woff,因为每一次请求得到的源码中,woff文件的链接都不同,所以我们需要使用数据提取手段,提取每一次请求得到的woff文件链接并下载保存下来。

        下载并保存woff文件,使用python代码识别woff文件,并保存为图片,识别代码如下,之后会整合到源码中:

from fontTools.ttLib import TTFont
from reportlab.graphics.shapes import Drawing, Path, Group
from reportlab.graphics import renderPM
from reportlab.lib import colors
from reportlab.graphics.shapes import Path

class ReportLabPen(BasePen):
    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            path = Path()
        self.path = path

    def _moveTo(self, p):
        (x, y) = p
        self.path.moveTo(x, y)

    def _lineTo(self, p):
        (x, y) = p
        self.path.lineTo(x, y)

    def _curveToOne(self, p1, p2, p3):
        (x1, y1) = p1
        (x2, y2) = p2
        (x3, y3) = p3
        self.path.curveTo(x1, y1, x2, y2, x3, y3)

    def closePath(self):
        self.path.closePath()

def ttfToImage(fontName, imagePath, fmt="png"):
    font = TTFont(fontName)  # 打开 WOFF 字体文件
    gs = font.getGlyphSet()
    glyphNames = font.getGlyphNames()[1:]  # 排除第一个 .notdef 字形

    for i in glyphNames:
        g = gs[i]  # 获取当前字形的 Glyph 对象
        pen = ReportLabPen(gs, Path(fillcolor=colors.red, strokeWidth=1))  # 创建 ReportLabPen 对象,并设置相关参数
        g.draw(pen)  # 将当前字形通过 pen 绘制到 path 对象上
        
        # 字形的宽度和高度
        w, h = g.width, g.width + 300  
        g = Group(pen.path)
        g.translate(0, 100)  # 将图形向下移动 100 个像素
        
        d = Drawing(w, h)  # 创建 Drawing 对象,设置宽度和高度
        d.add(g)  # 将 Group 对象添加到 Drawing 对象中
        
        # 定义输出图片路径和文件名
        imageFile = f"{imagePath}/{i}.{fmt}"
        
        # 将 Drawing 对象渲染成图像文件并保存
        renderPM.drawToFile(d, imageFile, fmt)

# 示例用法:将 `mao.woff` 字体文件的字形保存为图像
ttfToImage(fontName="mao.woff", imagePath='images')

        识别结果如下:

 

         识别图片

        识别代码如下,之后会整合到源码中:

import os
import ddddocr  # 导入 ddddocr 库

def orc():
    # 创建一个 ddddocr 的 OCR 对象
    ocr = ddddocr.DdddOcr()
    dicts = {}  # 初始化一个空字典,用于存储识别结果
    lists = os.listdir('./images')  # 获取 images 目录下的所有文件列表
    
    # 遍历每个图片文件
    for imgs in lists:
        # 以二进制模式读取图片文件
        with open('./images/' + imgs, 'rb') as f:
            img_bytes = f.read()
        
        # 使用 OCR 对象的 classification 方法识别图片内容
        res = ocr.classification(img_bytes)
        
        # 输出文件名中提取的 Unicode 代码
        print(222222222222222222, imgs[3:-4])
        
        try:
            # 将文件名中的 Unicode 代码转换为字符,并将识别结果存入字典
            dicts[eval('u\'\\u' + imgs[3:-4].lower() + '\'')] = res
        except:
            # 如果转换或存储过程中出错,则跳过
            pass

        # 打印当前的字典内容
        print(dicts)

# 调用 orc 函数
orc()

        字典输出结果如下:

字典替换

        拿到页面加密的源码,然后根据指点的key来替换掉对应的数字

        替换后的数字与原始页面一样

源码

import requests
import execjs
import re
import shutil
import os
import ddddocr
from fontTools.pens.basePen import BasePen
from fontTools.ttLib import TTFont
from reportlab.graphics.shapes import Drawing, Path, Group
from reportlab.graphics import renderPM
from reportlab.lib import colors
from reportlab.graphics.shapes import Path


class ReportLabPen(BasePen):
    def __init__(self, glyphSet, path=None):
        BasePen.__init__(self, glyphSet)
        if path is None:
            path = Path()
        self.path = path

    def _moveTo(self, p):
        (x, y) = p
        self.path.moveTo(x, y)

    def _lineTo(self, p):
        (x, y) = p
        self.path.lineTo(x, y)

    def _curveToOne(self, p1, p2, p3):
        (x1, y1) = p1
        (x2, y2) = p2
        (x3, y3) = p3
        self.path.curveTo(x1, y1, x2, y2, x3, y3)

    def closePath(self):
        self.path.closePath()


def ttfToImage(fontName, imagePath, fmt="png"):
    font = TTFont(fontName)  # 打开 WOFF 字体文件
    gs = font.getGlyphSet()
    glyphNames = font.getGlyphNames()[1:]  # 排除第一个 .notdef 字形

    for i in glyphNames:
        g = gs[i]  # 获取当前字形的 Glyph 对象
        pen = ReportLabPen(gs, Path(fillcolor=colors.red, strokeWidth=1))  # 创建 ReportLabPen 对象,并设置相关参数
        g.draw(pen)  # 将当前字形通过 pen 绘制到 path 对象上

        # 字形的宽度和高度
        w, h = g.width, g.width + 300
        g = Group(pen.path)
        g.translate(0, 100)  # 将图形向下移动 100 个像素

        d = Drawing(w, h)  # 创建 Drawing 对象,设置宽度和高度
        d.add(g)  # 将 Group 对象添加到 Drawing 对象中

        # 定义输出图片路径和文件名
        imageFile = f"{imagePath}/{i}.{fmt}"

        # 将 Drawing 对象渲染成图像文件并保存
        renderPM.drawToFile(d, imageFile, fmt)


def download_woff():
    with open('猫眼.js','r',encoding='utf-8') as f:
        ctx = execjs.compile(f.read())

    headers_home = {
        "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-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
        "Cache-Control": "max-age=0",
        "Connection": "keep-alive",
        "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/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
        "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }

    cookies_home = {
        "_lxsdk_s": "191b2c23b90-602-526-0ba%7C%7C1"
    }

    url = "https://www.maoyan.com/"

    s = requests.session()

    # 访问首页,保存cookie
    r = s.get(url, headers=headers_home, cookies=cookies_home)

    headers = {
        "Accept": "*/*",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
        "Connection": "keep-alive",
        "Referer": "https://www.maoyan.com/films/1464004",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
        "X-Requested-With": "XMLHttpRequest",
        "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }

    url = "https://www.maoyan.com/ajax/films/1464004"

    params = ctx.call("get_params")

    response = s.get(url, headers=headers, params=params).text

    # 保存woff
    woff_url = "https:" + re.findall(r',url.*?woff', response)[0].split('"')[1]
    woff_res = s.get(woff_url).content
    with open('mao.woff', 'wb') as f:
        f.write(woff_res)
    f.close()

    result = re.findall('<span class="stonefont">(.*?)</span>', response)
    return result


def clear_folder(folder_path):
    # 确保指定路径是一个文件夹
    if os.path.isdir(folder_path):
        # 遍历文件夹中的所有文件和子文件夹
        for filename in os.listdir(folder_path):
            file_path = os.path.join(folder_path, filename)
            try:
                # 如果是文件则删除
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                # 如果是文件夹则删除整个文件夹
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print(f"删除 {file_path} 时出错: {e}")
    print("删除完成")


def orc():
    # 创建一个 ddddocr 的 OCR 对象
    ocr = ddddocr.DdddOcr()
    dicts = {}  # 初始化一个空字典,用于存储识别结果
    lists = os.listdir('./images')  # 获取 images 目录下的所有文件列表

    # 遍历每个图片文件
    for imgs in lists:
        # 以二进制模式读取图片文件
        with open('./images/' + imgs, 'rb') as f:
            img_bytes = f.read()

        # 使用 OCR 对象的 classification 方法识别图片内容
        res = ocr.classification(img_bytes)

        # 输出文件名中提取的 Unicode 代码
        print(222222222222222222, imgs[3:-4])

        try:
            # 将文件名中的 Unicode 代码转换为字符,并将识别结果存入字典
            dicts[eval('u\'\\u' + imgs[3:-4].lower() + '\'')] = res
        except:
            # 如果转换或存储过程中出错,则跳过
            pass

    # 返回字典内容
    return dicts


if __name__ == '__main__':
    data = download_woff()

    # 指定要清空的文件夹路径
    folder_path = './images'
    clear_folder(folder_path)

    # 转换 TTF 字体并将字形转换为 PNG 图片
    ttfToImage(fontName="mao.woff", imagePath='images')

    # 使用ocr识别图片,返回字典
    res = orc()

    print(data)
    print(res)
    # 遍历字典并将识别结果输出
    for i in data:
        # 首先去掉所有的 &#x 和 ;
        cleaned_str = i.replace('&#x', '').replace(';', '')

        # 然后进行字符替换
        for key, value in res.items():
            cleaned_str = cleaned_str.replace(key.encode('unicode_escape').decode('ascii').replace('\\u', ''), value)

        print(cleaned_str)


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Linux] 操作系统 入门详解

标题&#xff1a;[Linux] 操作系统 水墨不写bug 目录 一、冯 . 诺依曼体系结构 1.冯诺依曼体系结构简介 2.对冯诺依曼体系结构的理解 二、操作系统定位 1.为什么需要操作系统&#xff1f; 2.操作系统是什么&#xff1f; 三、系统调用和库函数 正文开始&#xff1a; …

可交互、会学习、自成长机器人——李德毅院士

在以“农业无人农场”为主题的中国工程科技论坛上&#xff0c;中国工程院院士、欧亚科学院院士、中国人工智能学会和中国指挥与控制学会名誉理事长&#xff0c;中科原动力首席科学家李德毅院士应邀做题为《机器具身交互智能》的演讲。李德毅院士表示&#xff0c;智能机器不但把…

CentOS7单机环境安装k8s集群

目录 1、环境准备 2、安装依赖工具 3、配置 Kubernetes 的国内 Yum 源 4. 安装 Kubernetes 组件 5、初始化 Kubernetes 集群 1. 容器运行时没有正常运行 1.1. 可能的原因 1.2. 解决办法 2. 初始化拉取镜像卡住 2.1. 使用国内的镜像源&#xff08;无法解决问题&#x…

机器学习之监督学习(二)二元逻辑回归

机器学习之监督学习&#xff08;二&#xff09;逻辑回归(二元分类问题&#xff09; 1. 分类 classification2.二元分类逻辑回归 binary-classified logistic regression模块1: sigmoid 激活函数 sigmoid function模型公式模块2: 决策边界 decision boundary代价函数梯度下降欠拟…

【Redis】Redis 主从复制原理与配置详解:解决单点故障与性能瓶颈的最佳方案

目录 主从复制配置建立复制断开复制安全性只读传输延迟 拓扑⼀主⼀从结构一主多从结构树形主从结构 原理复制过程数据同步 psync全量复制部分复制实时复制 小结 主从复制 这部分相关操作不需要记忆!!! 后续⼯作中如果⽤到了能查到即可. 重点理解流程和原理. 单点问题&#xff1…

onvif应用--IPC鉴权(认证)

一、鉴权原理 1&#xff09;onvif的用户验证&#xff0c;是基于WS_UsernameToken&#xff0c;所谓的WS_UsernameToken加密&#xff0c;就是将用户名、密码、Nonce、Created都包含在了header里面 参数 意义 username待认证的用户名Nonce客户端随机产生的字符串Created请求认证…

arXiv风评被害?

arXiv “风评被害”&#xff1f; arXiv是一个在学术界具有重要影响力的开放预印本论文网站&#xff0c;自三十多年前创立以来&#xff0c;它已经成为了物理学、计算机科学、统计学等科学论文最重要的发布平台之一&#xff0c;同时也是众多科研人员分享和交流研究成果的重要渠道…

单片机工程师:创新与挑战之路

摘要&#xff1a;本文全面深入地探讨了单片机工程师这一职业角色。详细阐述了单片机工程师的职责范围、所需技能&#xff0c;包括硬件设计、软件编程、调试与测试等方面。分析了单片机在不同领域的应用&#xff0c;如工业控制、消费电子、智能家居等。同时&#xff0c;探讨了单…

【计算机组成原理】计算机系统的基本组成

文章目录 计算机硬件的基本组成早期的冯诺依曼机冯诺依曼结构冯诺依曼机的特点 现代计算机现代计算机的结构 各硬件的工作原理主存储器主存储器的基本组成 运算器运算器的基本组成 控制器控制器的基本组成 计算机软件 计算机硬件的基本组成 早期的冯诺依曼机 冯诺依曼在研究 …

图纸文件怎么加密?2024六款图纸加密软件推荐,个个好用不踩雷!

想象一下&#xff0c;公司的设计图纸被无意间泄露&#xff0c;结果对手提前推出了相似的产品。为了避免这种令人头疼的情况发生&#xff0c;图纸加密就显得尤为重要。 别担心&#xff0c;今天我们就带你了解2024年六款超好用的图纸加密软件&#xff0c;让你的图纸“安全感”爆…

业务复杂度治理方法论--十年系统设计经验总结

一、复杂度综述 1、什么是复杂度 软件设计的核心在于降低复杂性。 --《软件设计的哲学》 业界对于复杂度并没有统一的定义&#xff0c;斯坦福教授John Ousterhout从认知负担和工作量方面给出了一个复杂度量公式 &#xfeff; &#xfeff;&#xfeff; 子模块的复杂度cp乘…

快充协议方案的工作原理及场景应用

快充协议芯片是支持各种快充快充协议的芯片&#xff0c;它们能智能识别插入的设备类型&#xff0c;并根据设备的需求调整充电电压和电流&#xff0c;从而实现快速充电。 XSP08Q芯片是内置快充功能的协议芯片&#xff0c;它基于先进的充电技术&#xff0c;通过协商电压和电流&a…

【数据结构】二叉树顺序存储结构堆的应用以及解决TOP-K问题

文章目录 前言1. 堆的应用1.1 堆排序1.2 TOP-K问题 2. 结语 前言 前面我们学习了堆这个数据结构&#xff0c;这种数据结构是一种顺序结构存储的完全二叉树&#xff0c;现在我们来看一看堆的应用。 1. 堆的应用 1.1 堆排序 版本一&#xff1a;基于已有数组建堆、取堆顶元素完…

Linux CentOS 部署Docker

1. yum 配置 &#xff08;1&#xff09;更新yum yum update -y 如果不升级更新yum 可能在后续docker部署后再更新容器会出现oci runtime error等 &#xff08;2&#xff09;安装yum工具类准备 yum install -y yum-utils device-mapper-persistent-data lvm2 &#xff08;3&…

不再为存储‘分家’烦恼,teamOS让你的数据全家桶,一键即达

在数字化浪潮下&#xff0c;数据管理已成为企业运营的核心环节。作为企业管理者&#xff0c;我深知数据的重要性&#xff0c;也明白数据管理所面临的种种挑战。 最近开始使用企业网盘&#xff0c;在体验了一段时间后&#xff0c;目前来说&#xff0c;让我比较满意的就是可道云…

C盘红了怎么办?C盘快满了怎么办?如何提高电脑运行速度?

在电脑的日常使用中&#xff0c;C盘红了&#xff08;也就是C盘满了&#xff09;&#xff0c;那是常有的事&#xff0c;本文将详述一下&#xff0c;C盘红了之后的多种处理方法&#xff0c;只要你看完了&#xff0c;就必有一款适合你。 一、系统自带的磁盘清理 当你的C盘红了&…

vue3 置空a-select数据

置空a-select数据 项目中遇到需求&#xff0c;选择第一个下拉框后&#xff0c;发请求获取数据第二个下拉框数据&#xff08;第二个下拉框已选的情况下需要置空&#xff09;。 解决方法&#xff1a;formData.value.checkUser {value: “” ,label:“”} <a-col :span"…

Win32中的字符串

ANSI字符和Unicode字符 在Visual C中&#xff0c;用CHAR来表示8位ANSI字符&#xff0c;用WCHAR来表示16位Unicode字符&#xff08;宽字符&#xff09; 1 typedef char CHAR 2 typedef wchar_t WCHAR 一般常用的定义如下 TypedefDefinitionCHARcharPSTR or LPSTRchar*PCSTR o…

一个跨平台的换源工具,一键更换操作系统上的软件源或镜像源

大家好&#xff0c;今天给大家分享的是一个全平台通用换源工具chsrc&#xff0c;旨在为各种软件在不同平台上提供从命令行进行换源的功能。 项目介绍 chsrc支持 Linux&#xff08;包括麒麟、openEuler、deepin 等&#xff09;、Windows、macOS、BSD 等多种操作系统&#xff0c…

MySQL数据类型-介绍

MySQL 支持多种数据类型&#xff0c;这些数据类型可以根据它们所存储的数据类型大致分为几类&#xff1a;数值类型、日期和时间类型、字符串&#xff08;字符&#xff09;类型、空间数据类型以及JSON数据类型。 一、数据类型 1.整数类型 TINYINT&#xff1a;非常小的整数。例…