整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JS 解密和混淆破解

JS 解密和混淆破解

们已经学到很多反爬机制以及相应的反反爬策略。使用那些手段,其实已经完全可以完成绝大多数的爬虫任务。但是,还是有极个别的情况下,会出现诸如 JS 加密和 JS 混淆之类的高深反爬机制。

如果不幸遇到这种反爬机制,一个明智之举是给站长点个赞,然后恭恭敬敬选择放弃,去别的地方找数据。

当然,还是那句话,我们可以选择不爬,但是对付 JS 加密和 JS 混淆的方法却不可以不会。

这里就以中国空气质量在线检测平台为例,介绍 JS 加密和 JS 混淆的实现和破解方法。

要爬取的网站:https://www.aqistudy.cn/html/city_detail.html

这个网站正在升级,所以页面无法正常显示。这也意味着这个网站本身的 JS 解密是有问题的(如果没问题就能显示了),所以最后我们并不能完全解析出数据来。虽然如此,这个网站仍然是学习 JS 加密和 JS 混淆的相当不错的平台。

闲话少说,开始干活!

首先浏览器打开网页,并打开调试台的抓包工具。修改查询条件(城市的名称 + 时间范围),然后点击查询按钮,捕获点击按钮后发起请求对应的数据包。点击查询按钮后,并没有刷新页面,显然发起的是 ajax 请求。该请求就会将指定查询条件对应的数据加载到当前页面中(我们要爬取的数据就是该 ajax 请求请求到的数据)。

分析捕获到的数据包

  • 提取出请求的 url:https://www.aqistudy.cn/apinew/aqistudyapi.php
  • 请求方式:post
  • 请求参数:d: 动态变化一组数据(且加密)
  • 响应数据:是加密的密文数据

该数据包请求到的是密文数据,为何在前台页面显示的却是原文数据呢?

原来,在请求请求到密文数据后,前台接受到密文数据后使用指定的解密操作(JS 函数)对密文数据进行了解密操作,然后将原文数据显示在了前台页面。

接下来的工作流程:

首先先处理动态变化的请求参数,动态获取该参数的话,就可以携带该参数进行请求发送,将请求到的密文数据捕获到。

  • 将捕获到的密文数据找到对应的解密函数对其进行解密即可。
  • 【重点】需要找到点击查询按钮后对应的 ajax 请求代码,从这组代码中就可以破解动态变化的请求参数和加密的响应数据对应的相关操作。
  • 找 ajax 请求对应的代码,分析代码获取参数 d 的生成方式和加密的响应数据的解密操作。直接在页面中,并没有办法直接找到发送 ajax 请求的函数的,因为它以及被封装到别的文件中了。我们可以基于火狐浏览器定位查询按钮绑定的点击事件。
  • 抽丝剥茧,首先从 getData 函数实现中找寻 ajax 请求对应的代码。在该函数的实现中没有找到 ajax 代码,但是发现了另外两个函数的调用,getAQIData()getWeatherData()。ajax 代码一定是存在于这两个函数实现内部。

    另外,这里记住一个参数,type==’HOUR‘,它的含义是查询时间是以小时为单位。这个参数我们后来会用到。

    接下来我们就去分析 getAQIData()getWeatherData(),争取能够找到 ajax 代码。

    我们找到这两个函数的定义位置,还是没有找到 ajax 请求代码。不过我们却发现它们同时调用了另外一个函数,getServerData(method,param,func,0.5)。它的参数的值可以为:

    • method 可以是 ‘GETCITYWEATHER’ 或者 ‘GETDETAIL’
    • params 的值是 {city, type, startTime, endTime},也就是查询条件
    • func 是一个匿名函数,看样子是在处理数据。

    下一步当然就要找 getServerData 函数了,看看那个函数里面有没有我们一致想要的发送 ajax 请求的代码。

    我们尝试着在页面中搜索,却找不到这个函数。很显然,它是被封装到其他 js 文件中了。这时,我们可以基于抓包工具做全局搜索。

    好消息是,我们顺利找到了 getServerData 函数!坏消息是,这货长得一点也不像是函数。

    这是因为,这段 JS 函数代码被加密的。这种加密的方式,我们称为 JS 混淆。

    JS 混淆,也就是对核心的 JS 代码进行加密。

    JS 反混淆,则是对 JS 加密代码进行解密。

    接下来我们要做的,就是 JS 反混淆,让这段我们看不懂的东西,显现出庐山真面目。

    我们用的方法十分简单粗暴,也就是暴力破解。使用这个网站就可以实现对 JS 混淆的暴力破解:https://www.bm8.com.cn/jsConfusion/

    将 getServerData 函数所在的那一整行代码都复制过来,粘贴到这个网址的文本输入框中,然后点击 开始格式化 即可:

    终于,我们看到了 getServerData 的代码,并且在其中发现了发送 ajax 的请求:

    function getServerData(method, object, callback, period) {
        const key=hex_md5(method + JSON.stringify(object));
        const data=getDataFromLocalStorage(key, period);
        if (!data) {
            var param=getParam(method, object);
            $.ajax({
                url: '../apinew/aqistudyapi.php',
                data: {
                    d: param
                },
                type: "post",
                success: function (data) {
                    data=decodeData(data);
                    obj=JSON.parse(data);
                    if (obj.success) {
                        if (period > 0) {
                            obj.result.time=new Date().getTime();
                            localStorageUtil.save(key, obj.result)
                        }
                        callback(obj.result)
                    } else {
                        console.log(obj.errcode, obj.errmsg)
                    }
                }
            })
        } else {
            callback(data)
        }
    }

    从这段代码中,我们不难得出下面这几个信息:

    • ajax 请求成功后获得到的 data 是加密的响应数据(就是我们最开始通过抓包工具看到的那一串神秘的相应字符串),通过 decodeData(data) 函数,可以将加密的数据解密成我们需要的明文数据。
    • 发送请求时携带的参数,也就是 d 对应的值 param 是通过 getParam(method, object) 函数返回动的态变化的请求参数。这两个参数我们前面也分析过:参数 method 可以是 ‘GETCITYWEATHER’ 或者 ‘GETDETAIL’参数 object 则为 {city, type, startTime, endTime},是我们的查询条件我们当然还可以继续最终下去,刨根问题找到它们究竟是通过什么方式进行加密和解密的。然后,使用 Python 代码,重复这个加密和解密的过程,完成请求数据的生成和响应数据的解析过程。

    但是我们并不打算这么做。因为再继续深挖下去,难度将会陡然增加。此时我们已经很疲惫了,如果继续下去恐怕要疯掉。而且,JavaScript 和 Python 毕竟是两种语言,它们之间的方法和各种包都不相同。JavaScript 能实现的,Python 未必能够轻松完成。所以重新写一个加密和解密的脚本,并不是明智之举。

    更好的解决方案是,我们提供请求的明文数据,通过网站自己的 JS 代码进行加密,得到加密的请求参数。使用这个参数,我们发送请求给服务端。拿到加密的响应数据后,再通过网站的 JS 代码进行解密。

    也就是说,我们接下来需要做的就是要调用两个 JS 函数 decodeData 和 getParam,并拿到返回结果即可。

    现在的问题是,在 Python 程序中如何调用 JS 函数呢?

    这就涉及到一个新的概念:JS 逆向。JS 逆向,也就是在 Python 中调用 JS 函数代码。

    能够实现 JS 逆向的方式有两种:

    1. 手动将 JS 函数改写称为 Python 函数并执行。
    2. 这种方法我刚刚谈过了,并不现实。因为 JS 能实现的,Python 未必能够轻易实现。而且毕竟还要重写函数,比较麻烦。
    3. 使用固定模块,实现自动逆向(推荐)。
    4. 一个很好用的实现 JS 逆向的 Python 库 是 PyExecJS。
    5. PyExecJS 库用来实现模拟 JavaScript 代码执行获取动态加密的请求参数,然后再将加密的响应数据带入 decodeData 进行解密即可。
    6. PyExecJS 需要在本机安装好 nodejs 的环境。
    7. PyExecJS 的安装:
    pip install PyExecJS

    接下来,我们就可以生成加密的请求数据了。

    首先,把我们解析出来的那串代码保存到本地,比如名为 code.js 的文件中。在里面我们补充一个函数,比如名字叫 getPostParamCode,用来发起我们的数据请求。之所以这样做是因为使用 PyExecJS 调用 JS 函数时,传入的参数只能是字符串。而 getParam 方法的参数需要用到 JS 的自定义对象。

    我们只需在 code.js 中加上下面的代码即可:

    function getPostParamCode(method, type, city, start_time, end_time) {
        var param={};
        param.type=type;
        param.city=city;
        param.start_time=start_time;
        param.end_time=end_time;
        return getParam(method, param)
    }

    然后,使用 PyExecJS 调用里面的 getParam 方法,将我们的请求数据加密:

    # 模拟执行decodeData的js函数对加密响应数据进行解密
    import execjs
    import requests
    
    node=execjs.get()
    
    # 请求参数
    method='GETCITYWEATHER'
    type='HOUR'
    city='北京'
    start_time='2020-03-20 00:00:00'
    end_time='2020-03-25 00:00:00'
    
    # 编译js代码
    file='code.js'    # js代码的路径
    ctx=node.compile(open(file, encoding='utf-8').read())
    
    # 将请求数据加密
    encode_js=f'getPostParamCode("{method}", "{type}", "{city}", "{start_time}", "{end_time}")'
    params=ctx.eval(encode_js)
    
    # 使用加密的参数,发起post请求
    url='https://www.aqistudy.cn/apinew/aqistudyapi.php'
    response_text=requests.post(url, data={'d': params}).text
    
    # 将响应数据解密
    decode_js=f'decodeData("{response_text}")'
    decrypted_data=ctx.eval(decode_js)    # 如果顺利,返回的将是解密后的原文数据
    print(decrypted_data)    # 执行会报错:目前页面中没有数据。解密函数只是针对页面中原始的数据进行解密。

    自此,我们完成了 JS 加密和 JS 混淆的处理。这里我们总结一下这几个概念:

    • JS 加密,也就是通过 JS 代码,将数据进行加密处理,将明文数据变成密文数据。如果不能将其解密,密文数据将毫无用处。
    • JS 解密:通过 JS 代码,将加密的数据解密,也就是将密文数据解析成明文数据。JS 解密是 JS 加密的逆过程。
    • JS 混淆:将 JS 代码(比如 JS 函数)本身进行加密。
    • JS 反混淆:将加密了的 JS 代码解密成常规的 JS 代码。通常直接使用暴力破解即可。
    • JS 逆向(重要):通过 Python 代码调用 JS 的函数。

    附,ajax 请求的各个数据的含义:

    头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。

    如需学习视频,请复制以下信息到手机浏览器或电脑浏览器上:

    zcwyou.com

    1. 前言

    随着互联网活动的增加,网络攻击变得更加复杂和具有挑战性。

    毫无疑问,互联网用户的数据安全意识已经成倍增长。如果你是站长或博主,保护用户的敏感数据和隐私不受网络罪犯的恶意侵害就成了你的首要责任。

    在这里,SSL证书在加强您的网站安全性方面起着最有效和最关键的作用。因此,我们很有必要了解SSL的基本知识。

    https vs http


    2. SSL是什么?

    安全套接字层(SSL)是一种安全协议,为互联网上传输的数据提供加密,并对网络服务器进行身份验证。当您提交任何敏感资料时,SSL会对您的数据进行加密,以确保数据得到充分保护和安全,并且只有预期的收件人才能理解。SSL证书是由证书颁发机构颁发给网站的数字证书,它保证了用户的web浏览器和web服务器之间的所有信息都是加密的。因此,SSL证书保护您的数据免受恶意窃取或破坏,如窃听、中间人攻击等。

    一旦安装SSL证书,网站协议将从HTTP转移到安全的HTTPS。此外,您可以从浏览器中看到一个信任的标识,一个挂锁,被添加到您的网站的URL中。如上图所示。这可以确保访问者使用安全连接进行通信。SSL证书增加了网站的用户体验,并帮助提高其在谷歌搜索引擎结果页面上的排名。它还可以验证网站的真实性。但是你怎么检查这些信息呢?

    ssl原理


    3. 如何访问SSL证书包含的信息?

    在浏览器上单击地址栏中网址前面的挂锁,可以查看SSL证书信息。它还包含关于网站身份的信息。以下是SSL证书的主要内容:

    • 为其颁发证书的“域名”
    • 颁发证书的组织、人员或机构
    • 颁发证书的证书颁发机构的名称
    • 签发CA的数字签名
    • 与之关联的子域
    • 证书签发日期
    • 证书的有效期

    web浏览器与web服务器通信,并使用该数据文件验证网站的身份和SSL证书的状态。

    4. SSL证书如何保护用户数据和隐私?

    SSL证书安装在您的web服务器上后,它会以公钥和私钥的形式提供一个可识别的数字识别号码,用于服务器的认证。这些只不过是一长串任意生成的数字。它还允许服务器对用户和服务器之间交换的敏感信息进行加密和解密。

    • 当一个访问者访问您的网站时,用户web浏览器将尝试验证您网站的SSL证书的有效性,以及通过一个称为握手的过程验证您的服务器。
    • 然后,web服务器发送其SSL证书的副本以及服务器的公钥。
    • 浏览器根据受信任的ca列表、其到期日期和其真实性检查SSL证书。
    • 一旦浏览器确信SSL证书是有效的,并且您的服务器已经过身份验证,它将此指示给web服务器。
    • 此后,一个数字签名的确认被发送回浏览器,以建立一个安全的加密路径,用于在web服务器和用户之间的信息传输。
    • 如果浏览器发现您的SSL证书无效,则会向用户显示一个错误信息“您的连接不是可信的”,这将导致您的访问者立即离开您的网站。

    根据您的安全需求和预算,您可以从Internet上许多著名的证书颁发机构(CAs)提供的各种选择中购买SSL证书。因此,即使你预算有限,也要确保你的网站安全,使用有效的、便宜的SSL证书来赢得用户的信任,保护你的品牌声誉。因此,我们要了解不同类型的SSL证书。

    SSL证书如何保护用户数据和隐私


    5. SSL证书的类型

    各种验证级别

    SSL证书的类型



    证书颁发机构(CA)根据不同的验证级别需求颁发各种SSL证书。因此,SSL证书可以根据这些不同的验证级别过程进行分类,如下所示:

    5.1 域验证(DV) SSL证书

    DV SSL证书申请验证过程非常简单,几乎不需要复杂的申请流程。因此,在SSL市场上可用的所有其他类型中,它需要的时间最短。DV SSL证书广泛用于非金融交易或用户敏感数据的小型网站。它们是最便宜的选择,也被广泛用于保护博客。它只能提供基础的认证。

    5.2 组织验证(OV) SSL证书

    CA需要进行域验证,并验证所有者公司的真实性后,才能签发OV SSL证书。OV SSL证书显示公司的身份信息,可通过访问证书上的信息查看。该公司的详细信息显示在“主题标签”中,如下面的例子所示:

    ssl certification


    中型企业比较普遍使用OV SSL证书,其发放时间比DV SSL证书长。由于证书所有者公司的验证过程需要更多的时间,所以比DV SSL证书的费用更高。

    5.3 扩展验证(EV) SSL证书

    EV SSL证书与最高程度的信任和声誉相关联。EV SSL证书必须经过以下严格的认证后才能签发给组织。非常严格和彻底的背景验证是签发验证程序的一部分,如其地址、目前的运营状况、法律状况等。因此,与DV和OV SSL证书相比,此SSL证书的签发时间最长,成本也更高。该公司的名称显示在地址栏,因此可以验证。可信任的标识,网站Logo在网站上的展示增加了用户的信任。EV SSL证书广泛应用于银行、大型金融机构、电子商务网站等。

    6. 不同数量的域/子域

    使用不同类型SSL证书确保网站安全的另一个决定因素是需要保护的域和子域的数量,它们如下:

    6.1 单域证书

    单域SSL证书为您的网站保护一个域或子域。

    为了保护多个域和子域,购买具有成本效益的通配符SSL证书或多域证书提供了更好的投资回报和更好的时间管理,而不是为每个域和子域使用单一域证书。

    6.2 通配符SSL证书

    通配符SSL证书仅使用一个证书就可以保护主域和其所有子域。通配符证书使用格式为*.example.com的通用名称,因此它将使用单个证书来保护多个子域。如果考虑到预算问题,建议购买并安装通配符SSL证书,该证书将提供与较昂贵证书相同的加密级别。

    6.3 多域/SAN SSL证书

    多域或SAN(主题可选名称)SSL证书仅使用一个证书就可以保护多个域/子域。您可以添加和修改Subject Alternative Name字段,并轻松保护不同域和子域之间的多个名称。例如,只需一个多域(SAN) SSL证书。

    6.4 统一通信证书(UCC)

    统一通信证书(UCC)只不过是多域SSL证书,早期设计用于保护Microsoft Exchange和Live Communications服务器。现在,它被用来保护任何多个域名,只需一个证书。UCC证书具有组织验证功能,还可以用作EV SSL证书,为用户提供最高程度的信任和安全性。

    7. 结论

    总之,我们可以说,要赢得用户的信任,并将你的在线业务或博客带到新的成功高度,关注用户数据的安全是至关重要的。要做到这一点,没有比使用廉价的SSL来保护你的网站或博客更好的方法了,它可以帮助你瞄准你的安全需求和预算。


    如果喜欢本文,欢迎转发。本文已同步至博客站,尊重原创,转载时请在正文中附带以下链接:https://www.linuxrumen.com/rmxx/1994.html

    在这里我就不再一一介绍每个步骤的具体操作了,因为在爬取老版数据的时候都已经讲得非常清楚了,所以在这里我只会在重点上讲述这个是这么实现的,如果想要看具体步骤请先去看我的文章内容,里面有非常详细的介绍以及是怎么找到加密js代码和api接口。

    私信小编01即可获取大量Python学习资料


    58同城网站分析

    58同城的数据爬取非常简单,唯一有点难的就是字体的加密,除此之外其他的数据用xpath即可获取。

    想爬取不同地方的直接访问链接即可:


    数据在链接中,直接请求获取即可。


    字体加密破解

    既然是字体加密那么就先把字体寻找出来,寻找简单,在开发者工具中的分类找到Font,然后搜索这个链接进行查找。


    已经找到这个字体了,他是在请求页面的时候返回的,然后他还是个base64的,只需要转换一下再保存就可以了。

    请求链接获取字体

    import requests
    from lxml import etree
    
    def get_data():
        url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
        headers={
            'authority': 'bj.58.com',
            'method': 'GET',
            'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
            'scheme': 'https',
            '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.9',
            'accept-encoding': 'gzip, deflate, br',
            'accept-language': 'zh-CN,zh;q=0.9',
            'cache-control': 'max-age=0',
            'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
            '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/86.0.4240.75 Safari/537.36'
        }
        response=requests.get(url=url, headers=headers)
        return response.text
    
    def get_font(data):
        html=etree.HTML(data)
        script=html.xpath('//script[2]/text()')[0]
        ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
        with open('fangchan-secret.ttf','wb') as f:
            f.write(base64.b64decode(ttf))
    
    if __name__=='__main__':
        data=get_data()
        get_font(data)

    解析字体字符

    import base64
    from fontTools.ttLib import TTFont
    import re
    import requests
    from lxml import etree
    
    def get_data():
        url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
        headers={
            'authority': 'bj.58.com',
            'method': 'GET',
            'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
            'scheme': 'https',
            '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.9',
            'accept-encoding': 'gzip, deflate, br',
            'accept-language': 'zh-CN,zh;q=0.9',
            'cache-control': 'max-age=0',
            'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
            '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/86.0.4240.75 Safari/537.36'
        }
        response=requests.get(url=url, headers=headers)
        return response.text
    
    def get_font(data):
        html=etree.HTML(data)
        script=html.xpath('//script[2]/text()')[0]
        ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
        with open('fangchan-secret.ttf','wb') as f:
            f.write(base64.b64decode(ttf))
    
    def parse_font():
        font=TTFont('fangchan-secret.ttf')
        bestcmap=font['cmap'].getBestCmap()
        newmap=dict()
        for key in bestcmap.keys():
            value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
            key=hex(key)
            newmap[key]=value
        print(newmap)
    
    if __name__=='__main__':
        data=get_data()
        get_font(data)
        parse_font()


    我们发现字体编号和之前的不符合,比如:
    0x9476=7,而这里的是2,这是什么原因呢?是因为他的字体是动态生成的,每次返回的数字编号对应的值都是不同的,但是不影响我们代码的正常运行与结果。

    import base64
    from fontTools.ttLib import TTFont
    import re
    import requests
    from lxml import etree
    
    def get_data():
        url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
        headers={
            'authority': 'bj.58.com',
            'method': 'GET',
            'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
            'scheme': 'https',
            '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.9',
            'accept-encoding': 'gzip, deflate, br',
            'accept-language': 'zh-CN,zh;q=0.9',
            'cache-control': 'max-age=0',
            'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
            '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/86.0.4240.75 Safari/537.36'
        }
        response=requests.get(url=url, headers=headers)
        return response.text
    
    def get_font(data):
        html=etree.HTML(data)
        script=html.xpath('//script[2]/text()')[0]
        ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
        with open('fangchan-secret.ttf','wb') as f:
            f.write(base64.b64decode(ttf))
    
    def parse_font():
        font=TTFont('fangchan-secret.ttf')
        bestcmap=font['cmap'].getBestCmap()
        newmap=dict()
        for key in bestcmap.keys():
            value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
            key=hex(key)
            newmap[key]=value
        return newmap
    
    def parse_data(data,newmap):
        for key,value in newmap.items():
            key_=key.replace('0x','&#x') + ';'
            if key_ in data:
                data=data.replace(key_,str(value))
        html=etree.HTML(data)
        house_list=html.xpath('//ul[@class="house-list"]/li')[:-1]
        for house in house_list:
            room=house.xpath('.//p[@class="room"]/text()')[0]
            money=house.xpath('.//b[@class="strongbox"]/text()')[0]
            print(room,money)
    
    if __name__=='__main__':
        data=get_data()
        get_font(data)
        newmap=parse_font()
        parse_data(data,newmap)

    声明:本文仅供学习交流使用,请勿用于商业用途,违者后果自负。