python爬虫破解css加密之大众点评


前言

本文参考了多篇文章,如有侵权请联系qq3294575459,文章及代码会及时删除
声明:代码仅供学习参考,请勿用于非法数据爬取
‘’’
项目难点:css字体加密反爬、且不定时切换两套不同的反爬机制
项目开发过程:
反爬种类一: svg字体反爬
解决过程:抓取css文件中的svg文件,解析svg文件,根据x,y偏移量可计算出文字在SVG源文件的索引值,再生成映射关系字典,替换被修改的字体。
反爬种类二: woff字体反爬
解决过程:抓取css文件中的woff文件,用FFfont库以及第三方图片识别接口解析woff文件,生成文字映射关系字典,再做替换。
‘’’
本篇文章使用的第二种woff字体反爬


一、分析页面

url:https://www.dianping.com/

1.1 页面分析

在这里插入图片描述
F12,打开开发者模式

1.1.1标题没有加密

在这里插入图片描述

1.1.2评论数加密

在这里插入图片描述

1.1.3价格加密

在这里插入图片描述

1.1.4种类加密

在这里插入图片描述

1.1.5地址加密

在这里插入图片描述

1.1.6推荐菜无加密

在这里插入图片描述

1.2字体分析

我们通过开发者选项看到的特殊符号其实是一种字体,是大众点评专门加密的一种CSS字体。
在这里插入图片描述

在这里插入图片描述
通过上述源码与图片对比,我们可以看出,大众点评的自定义字体初步判断为两个。

二、字体下载

2.1字体文件的获取

在这里插入图片描述
ctr+鼠标点击 进入到css文件中
在这里插入图片描述

2.1.1获取css的url

我们可以得知css文件的网址,接下来就是在源代码中找出,并提取出来。
在这里插入图片描述
首先进行抓包,可以在工具栏进行搜索,找出所在的包。
在这里插入图片描述
在源码栏里搜索.css,找出所对应的源码,可以看出,这就是我们想要的css文件的url。
在这里插入图片描述
我们用正则表达式进行提取,然后进行拼接

 page = requests.get(url=url, headers=headers).content.decode('utf-8')  # 请求页面
    ex = '<link rel="stylesheet" type="text/css" href="//s3plus.meituan.net/v1/(.*?).css">'
    pattern = re.compile(ex)
    css = pattern.findall(page)[0]  # 提取css地址
    cssUrl = f'http://s3plus.meituan.net/v1/{css}.css'



2.1.2woff文件获

我们可以看出,下面的url就是我们想要的woff字体文件的下载地址
在这里插入图片描述
接下来用正则提取shopNum和tagName的网址

    ex = ',url\("\/\/s3plus\.meituan\.net\/(.*?)\.woff'
    pattern = re.compile(ex)
    woffLs = pattern.findall(cssTxt.split('.tagName')[0])  # 提取woff地址
    tagName = 'http://s3plus.meituan.net/' + woffLs[-1] + '.woff'

2.2查看woff文件

下载地:http://www.fontcreator.com/

在这里插入图片描述
这就是我们下载的两个字体文件

三 构建解密字典

words = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下臬凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
def build_dict(font_file, words):
    # 传入woff文件名与words字序
    font = TTFont('{}.woff'.format(font_file))
    lis = font.getGlyphOrder()[2:]  # 删去前两个字符
    dic = {}  # 构建解密字典
    for index, value in enumerate(words):
        string = lis[index].replace('uni', '\\u')  # 替换font中的uni为\u以便于后续解密
        dic[json.loads(f'"{string}"')] = value
    return dic

从上面查看的woff文件中,我们可以看到开头两个是空白的,所以要去掉,之后对words建立索引,源码中"美"字\uf2cc,而woff文件中是unif2cc,所以要进行替换,从而进行解密。

四完整代码



import requests
from fontTools.ttLib import TTFont
from lxml import etree
from time import sleep
import pandas as pd
import json
import re


def build_dict(font_file, words):
    # 传入woff文件名与words字序
    font = TTFont('{}.woff'.format(font_file))
    # font.saveXML('{}.xml'.format(font_file)) 可保存为xml格式
    lis = font.getGlyphOrder()[2:]  # 删去前两个字符
    dic = {}  # 构建解密字典
    for index, value in enumerate(words):
        string = lis[index].replace('uni', '\\u')  # 替换font中的uni为\u以便于后续解密
        dic[json.loads(f'"{string}"')] = value
    return dic


def collect(url, cookie):
    # 抓取url对应页面
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        'Accept-Encoding': 'gzip,deflate',
        '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',
        'Cookie': cookie,
        'Host': 'www.dianping.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.114Safari/537.36Edg/91.0.864.54'
    }
    response = requests.get(url=url, headers=headers).text
    return response


def decrypt(word_list, dic):
    # 利用解密解密字典对加密字符进行解码
    res = ''
    for word in word_list:
        res += dic.get(word, word)  # 在解密字典中查询加密字符
    return res


def analyse(page, font_dict):
    # 解析页面数据
    tree = etree.HTML(page)
    shop_list = []  # 创建数据列表
    for li in tree.xpath('//div[@id="shop-all-list"]/ul/li'):  # 分别解析li标签
        try:
            name = li.xpath('.//div[@class="tit"]/a/h4/text()')[0]  # 店名
        except IndexError:
            name = ''
        price = li.xpath('.//div[@class="comment"]/a[2]/b//text()')  # 人均消费
        area = li.xpath('.//div[@class="tag-addr"]/a[2]/span//text()')  # 地区
        comment = li.xpath('.//div[@class="comment"]/a[1]/b//text()')  # 评价数
        try:
            total_score = eval(li.xpath('.//div[@class="nebula_star"]/div/span/@class')[0].split()[1].split('_')[-1]) / 10  # 评分
        except IndexError:
            total_score = ''
        kind = li.xpath('.//div[@class="tag-addr"]/a[1]/span//text()')  # 类型
        recommend = li.xpath('.//div[@class="recommend"]/a//text()')  # 推荐菜
        # 创建店铺字典
        shop_dict = {
            'name': name,
            'price': decrypt(price, font_dict[0]),
            'area': decrypt(area, font_dict[1]),
            'comment': decrypt(comment, font_dict[0]),
            'total_score': total_score,
            'kind': decrypt(kind, font_dict[1]),
            'recommend': ' '.join(recommend)
        }
        print(shop_dict)
        shop_list.append(shop_dict)
    return shop_list


def save(lis):
    # 保存为excel文件
    pf = pd.DataFrame(list(lis))
    # 更改为中文名
    columns = {
        'name': '店名',
        'price': '人均消费',
        'area': '地区',
        'comment': '评价数',
        'total_score': '评分',
        'kind': '类型',
        'recommend': '推荐菜'
    }
    pf.rename(columns=columns, inplace=True)
    path = pd.ExcelWriter('大众点评new.xlsx')
    pf.fillna('', inplace=True)
    pf.to_excel(path, encoding='utf-8', index=False)  # 保存文件
    path.save()


def woff(url, cookie):  # 爬取字体文件
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        'Accept-Encoding': 'gzip,deflate',
        'Cookie': cookie,
        'Host': 'www.dianping.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.114Safari/537.36Edg/91.0.864.54'
    }
    page = requests.get(url=url, headers=headers).content.decode('utf-8')  # 请求页面
    ex = '<link rel="stylesheet" type="text/css" href="//s3plus.meituan.net/v1/(.*?).css">'
    pattern = re.compile(ex)
    css = pattern.findall(page)[0]  # 提取css地址
    cssUrl = f'http://s3plus.meituan.net/v1/{css}.css'
    cssTxt = requests.get(url=cssUrl).text
    print(f'cssTxt: {cssTxt}')
    ex = ',url\("\/\/s3plus\.meituan\.net\/(.*?)\.woff'
    pattern = re.compile(ex)
    woffLs = pattern.findall(cssTxt.split('.tagName')[0])  # 提取woff地址
    tagName = 'http://s3plus.meituan.net/' + woffLs[-1] + '.woff'
    open('tagName.woff', 'wb').write(requests.get(tagName).content)  # 爬取并保存字体文件
    print('tagName' + ': ' + tagName)
    woffLs = pattern.findall(cssTxt.split('.shopNum')[0])
    shopNum = 'http://s3plus.meituan.net/' + woffLs[-1] + '.woff'
    open('shopNum.woff', 'wb').write(requests.get(shopNum).content)
    print('shopNum' + ': ' + shopNum)
    print('-' * 100)
    sleep(5)


def main():
    # 主函数
    u = 'https://www.dianping.com/chengdu/ch10'
    cookie = ''
    print('-' * 100)
    woff(u, cookie)
    words = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下臬凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'
    font_dict = [build_dict('shopNum', words), build_dict('tagName', words)]
    # print(font_dict)
    total_list = []  # 创建总列表
    for n in range(50):  # 循环爬取
        try:
            page = collect('{}/p{}'.format(u, n+1), cookie)
            total_list += analyse(page, font_dict)
        except Exception as e:
            print('Error:' + str(e))
            print('-' * 100)
        else:
            print('成功爬取第{}页!'.format(n+1))
            print('-' * 100)
            sleep(5)  # 睡眠5秒防封
    save(total_list)
    print('爬取完成!')


if __name__ == '__main__':
    main()  # 执行主函数

要是想实现翻页功能,需要进行账号登陆,将cookie加入其中
在这里插入图片描述

五运行结果

在这里插入图片描述
在这里插入图片描述

总结

希望这篇文章对大家有帮助,本人才疏学浅,文章如果出现错误,也希望大家能够指正,共同进步。接下来每周会更新自己做的一些项目,希望对大家有所帮助。
本文参考了多篇文章,如有侵权请联系qq3294575459,文章及代码会及时删除
声明:代码仅供学习参考,请勿用于非法数据爬取

http://www.fontcreator.com/
posted @ 2022-06-24 12:37  可乐314  阅读(494)  评论(0)    收藏  举报