字体反爬案例解析:大众点评

文章目录

  • 字体反爬简介
  • 发送请求,获取网页源码
  • 提取字体信息,并将字体文件下载到本地
  • 建立基准字典
  • 引例
  • 提取需要字体反爬处理的信息
  • 提取不需要字体反爬的信息
  • 整理提取到的所有信息,并存入excel

字体反爬简介

       什么是字体反爬?
       就是我们在网页上看到的内容和我们直接解析出来的内容不一样,以大众点评网站为例:
       我们在网页上看到的是这样:
在这里插入图片描述
       这些数字是正常显示的,但是我们点击F12,查看HTML时,却是这样的:
在这里插入图片描述
       用requests发送请求,获取到网页信息,发现是这样的:
在这里插入图片描述
       也就是说,这个网页中,有些数字和字是经过了加密处理,像原来那样直接解析的话,得到的是每个字的‘密码’,而不是我们想要的汉字或者数字。
       字体反爬大致可以分为两种,一种是不同字体文件中,每个字的Unicode编码不一样,但字形完全一样(即每个字的contour的坐标完全一样),比如大众点评;一种是Unicode编码不一样,并且在不同字体文件中,字形也不完全一样,只是非常相似,比如美团。
       对于第一种,我们的处理方式如下:
       设置基准字典,以contour为key,以该contour描绘出来的字作为value;当遇到需要解析的字时,我们通过这个字的‘密码’先找到这个字的Unicode,然后用Unicode来找到这个字的contour,最后用这个contour去基准字典中找到对应的汉字或数字。
       对于第二种,我们的处理方式如下:
       设置基准字典,以contour为key,以该contour描绘出来的字作为value;当遇到需要解析的字时,我们通过这个字的Unicode来找到这个字的contour;然后对比基准字典中的所有contour,看哪个与待解析字的contour最接近(比如可以使用K近邻的方法),然后把最接近的那个contour的value赋给待解析的字。
       本文主要介绍第一种。

import requests
import re
from lxml import etree
import fontTools
from  fontTools.ttLib import TTFont 
import hashlib
import pandas as pd

发送请求,获取网页源码

url ='http://www.dianping.com/dayi/ch10'
headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Mobile Safari/537.36'}
html = requests.get(url,headers=headers)

提取字体信息,并将字体文件下载到本地

一个网页中,可能使用了多种字体文件来对页面中的某些文字进行加密,这些字体文件信息保存在一个css文件中,因此我们首先要拿到该css文件的链接,然后在该链接的网页中查找字体信息

#拿到含有字体的css url
css_url = re.findall('href="(.*s3plus.*)"',html.text)#拿到字体的url
woff_html = requests.get('http:'+css_url[0],headers=headers)
woff_url = ['http:'+url for url in re.findall(",url\(\"(.*?)\"\);}",woff_html.text)]#拿到字体的名字
woff_name = re.findall('font-family: "PingFangSC-Regular-(.*?)";',woff_html.text)#将字体名字和url一一对应起来
woff_name_url = {}
for i in range(len(woff_name)):if woff_name[i] !=  'reviewTag': #这样处理的原因是本例中用不到reviewTag这个字体文件,并且它是重复的,所以去掉它。woff_name_url[woff_name[i]] = woff_url[i]
woff_name_url
{'shopNum': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/3afae22b.woff','tagName': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/276defdb.woff','address': 'http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/33f1a1f2.woff'}

可以看到,一共有3个字体文件,名字分别是shopNum、tagName、address,目前我们都拿到了它们对应的链接。

#下载字体文件到本地
for key in woff_name_url:name = keyurl = woff_name_url[key]response = requests.get(url,headers=headers)with open('%s.woff'%name,'wb') as f:f.write(response.content)f.close()fonts = {}
for key in woff_name_url:file_name = '{}.woff'.format(key)fonts[key] = TTFont(file_name)
fonts
{'shopNum': <fontTools.ttLib.ttFont.TTFont at 0x2609b2bdf48>,'tagName': <fontTools.ttLib.ttFont.TTFont at 0x2609b2bd4c8>,'address': <fontTools.ttLib.ttFont.TTFont at 0x2609b2c1bc8>}

我们使用在线的字体解析工具看看这些字体文件:
在这里插入图片描述
可以看到,每个字都有对应的Unicode编码(红色圈起来的那个)

建立基准字典

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

引例

首先以店铺地址信息的提取来解释一下大概的过程:

addrs = re.findall('<span class="addr">(.*?)</span>',html.text)  
#addrs得到的是15个店的地址print(addrs[0]) #这个是第一个店的信息
#<svgmtsi class="address">&#xeb29;</svgmtsi><svgmtsi class="address">&#xe6b0;</svgmtsi><svgmtsi class="address">&#xe568;</svgmtsi><svgmtsi class="address">&#xe013;</svgmtsi>60<svgmtsi class="address">&#xf31d;</svgmtsi>
addr = addrs[0].replace('<svgmtsi class="address">','').replace('</svgmtsi>','')
print(addr) #可以看到,地址信息中每一个汉字的‘密码’都是以&#x开头,#然后是4个字符,然后以;分割   #并且经过多次观察我们还可以发现,每个字的‘密码’中间的那4个字符,刚好就是该字对应的Unicode编码的后4位。#因此,按照此规律我们就可以进行‘解密’了。
#   &#xeb29;&#xe6b0;&#xe568;&#xe013;60&#xf31d;#按照上述发现的规律进行解密
str_need_replace = re.findall('&#x(.*?);',addr)
print(str_need_replace) #['eb29', 'e6b0', 'e568', 'e013', 'f31d']
for i in str_need_replace:unicode = 'uni'+i #拿到这个字对应的Unicode编码contour_code=hashlib.md5(bytes(str(address_font['glyf'][unicode].coordinates),encoding='utf-8')).hexdigest()#这里是将contour进行一下md5处理,有如下原因:#1.一般一个字的字形的坐标都很长(即有很多个点组成),不进行md5处理的话,就会是很长一串#2.不同字的字形的坐标一般长短不一样,不进行md5处理的话,放在一起不好看#address_font['glyf'][unicode].coordinates 就是拿到这个字的字形的坐标old_str = '&#x'+i+';'new_str = base_dict[contour_code]addr=addr.replace(old_str,new_str)
print(addr) #长春西路60号

提取需要字体反爬处理的信息

# 定义一个提取信息的函数
def mes_extract(re_str,woff_name):if woff_name in fonts.keys():font = fonts[woff_name]final_mes = []replace_str = '<svgmtsi class="{}">'.format(woff_name)mes_for15 = re.findall(re_str,html.text,flags=re.S)if len(mes_for15)==15:if re_str.count('(.*?)')==1:for i in range(15):mes = mes_for15[i].replace(replace_str,'').replace('</svgmtsi>','')str_need_replace = re.findall('&#x(.*?);',mes)for j in str_need_replace:unicode = 'uni'+jcontour_code=hashlib.md5(bytes(str(font['glyf'][unicode].coordinates),encoding='utf-8')).hexdigest()old_str = '&#x'+j+';'new_str = base_dict[contour_code]mes=mes.replace(old_str,new_str)final_mes.append(mes)if re_str.count('(.*?)')==2:for i in range(15):mes = mes_for15[i][1].replace(replace_str,'').replace('</svgmtsi>','')str_need_replace = re.findall('&#x(.*?);',mes)for j in str_need_replace:unicode = 'uni'+jcontour_code=hashlib.md5(bytes(str(font['glyf'][unicode].coordinates),encoding='utf-8')).hexdigest()old_str = '&#x'+j+';'new_str = base_dict[contour_code]mes=mes.replace(old_str,new_str)final_mes.append(mes)else:print('字体有误')return final_mes
region_re = 'click-name="shop_tag_region_click"(.*?)><span class="tag">(.*?)</span>'
addr_re = '<span class="addr">(.*?)</span>'
dish_type_re = 'click-name="shop_tag_cate_click"(.*?)><span class="tag">(.*?)</span>'
taste_score_re = '口味<b>(.*?)</b>'
surroundings_score_re = '环境<b>(.*?)</b>'
service_score_re = '服务<b>(.*?)</b>'
review_num_re = 'class="review-num"(.*?)<b>(.*?)</b>'
regions = mes_extract(region_re,'tagName')
addrs = mes_extract(addr_re,'address')
dish_types = mes_extract(dish_type_re,'tagName')
taste_scores = mes_extract(taste_score_re,'shopNum')
surrounding_scores = mes_extract(surroundings_score_re,'shopNum')
service_scores = mes_extract(service_score_re,'shopNum')
review_nums = mes_extract(review_num_re,'shopNum')
# 人均消费价格的提取
# 人均消费价格由于有缺失值,所以需要单独处理
avgprices_nodes = re.findall('shop_avgprice_click(.*?)<b>(.*?)</b>',html.text,flags=re.S)avg_prices = []
for i in range(len(avgprices_nodes)):if '¥' in avgprices_nodes[i][1]:avg_price = avgprices_nodes[i][1].replace('<svgmtsi class="shopNum">','').replace('</svgmtsi>','')str_need_replace = re.findall('&#x(.*?);',avg_price)for j in str_need_replace:unicode = 'uni'+jcontour_code=hashlib.md5(bytes(str(fonts['shopNum']['glyf'][unicode].coordinates),encoding='utf-8')).hexdigest()old_str = '&#x'+j+';'new_str = base_dict[contour_code]avg_price=avg_price.replace(old_str,new_str)avg_prices.append(int(avg_price.strip('¥')))else:   avg_prices.append('-')

提取不需要字体反爬的信息

html_xpath = etree.HTML(html.text)# 店铺名称的提取:
shop_names = []
shop_names_nodes = html_xpath.xpath('//div [@class="tit"]/a/h4')
for i in range(15):shop_names.append(shop_names_nodes[i].text)# 店铺详细页面链接的提取
shop_urls = []
shop_urls_nodes = html_xpath.xpath('//div [@class="tit"]/a')
for i in range(15):shop_urls.append(shop_urls_nodes[i].attrib['href'])# 店铺总体评分的提取
shop_scores = []
shop_scores_nodes = html_xpath.xpath('//div [@class="nebula_star"]/div[2]')
for i in range(15):shop_scores.append(shop_scores_nodes[i].text)# 店铺推荐菜的提取
recommend_dishs = []
shop_nodes = html_xpath.xpath('//div[@class="recommend"]')
for i in range(15):dish_in_one_shop = []dish_nodes = shop_nodes[i].xpath('.//a')for node in dish_nodes:dish_in_one_shop.append(node.text)recommend_dishs.append(dish_in_one_shop)

整理提取到的所有信息,并存入excel

data = pd.DataFrame([shop_names,dish_types,shop_urls,regions,addrs,shop_scores,taste_scores,surrounding_scores,service_scores,review_nums,avg_prices,recommend_dishs]).Tdata.columns= ['shop_name','dish_type','shop_url','region','addr','overall','taste','surroundings','service','review_num','avgprice','recommend_dish']data.to_excel('./dazong.xls')

最终的结果:
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

作文 我眼中的计算机1000字,我眼中的自己作文范文1000字(精选6篇)

我眼中的自己作文范文1000字(精选6篇) 在日常生活或是工作学习中&#xff0c;许多人都有过写作文的经历&#xff0c;对作文都不陌生吧&#xff0c;作文根据写作时限的不同可以分为限时作文和非限时作文。还是对作文一筹莫展吗&#xff1f;以下是小编为大家整理的我眼中的自己作…

基于深度学习的高精度家禽猪检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度家禽猪检测识别系统可用于日常生活中或野外来检测与定位家禽猪目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的家禽猪目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检…

智能管理PoE交换机

在这个万物互联的时代&#xff0c;数据与数据之间的相互传输交流&#xff0c;显得尤为重要。那么要怎样才能使计算机与传统的物联设备相连接呢&#xff1f;这时&#xff0c;串口服务器这一媒介的作用就凸显出来了。那么&#xff0c;你知道什么是串口服务器吗&#xff1f;串口服…

chatgpt赋能python:Python中构造函数的名称

Python中构造函数的名称 作为一名有10年Python编程经验的工程师&#xff0c;我深知Python语言中构造函数的重要性。在本文中&#xff0c;我将着重介绍Python中构造函数的名称&#xff0c;并阐述其在Python编程中的作用。 什么是构造函数&#xff1f; 构造函数是一种特殊类型…

C++ stack容器介绍

&#x1f914;stack容器介绍&#xff1a; &#x1f4d6; stack是一种数据结构&#xff0c;也可以被称为堆栈。它是一个容器&#xff0c;只允许在最顶层进行插入和删除&#xff0c;并且只能访问最后一个插入的元素。这个元素称为栈顶。所有新插入的元素都被放置在栈顶上面&#…

Mysql source命令报错

Mysql source命令报错 情况一&#xff1a;目录包含中文 放到没有中文的路径再执行 情况二&#xff1a;不小心加了分号 mysql会将分号当做文件名的一部分 固然报错 情况三&#xff1a;没有选择数据库 使用 use加数据库名 选择数据库后再执行 执行成功画面

Linux中的source命令

Linux中的source命令 1、source命令是什么&#xff1f; source命令也称为“点命令”&#xff0c;也就是一个点符号&#xff08;.&#xff09;&#xff0c;是bash的内部命令。 注意&#xff1a;该命令通常用命令“.”来替代 2、source命令 功能&#xff08;能干什么&#xff0…

qsort函数排序举例

使用qsort函数快速排序应用举例 这篇博客是用qsort函数来快速排列float型数据&#xff0c;分别按照年龄&#xff08;int型&#xff09;、姓名&#xff08;char型&#xff09;排列结构体。看懂就看懂&#xff0c;看不懂我也不想解释了。 简略解释一下qsort函数&#xff1a; v…

C语言qsort函数详解

目录 一、qsort函数的使用 二、qsort函数的模拟 一、qsort函数的使用 快排函数qsort是C的库函数&#xff0c;它可以对输入的任何类型的数组排序&#xff0c;通过该函数的函数声明我们可以看出它的使用方法&#xff1a; 举个栗子&#xff1a; #include<stdio.h> #inclu…

C语言 - qsort函数详解

文章目录 一.qsort函数简介1.qsort函数是C标准库<stdlib.h>库中的函数&#xff0c;使用时引入#include <stdlib.h>。**2.它的函数原型是 void qsort(void* base, size_t num, size_t width, int (*compare)(const void*, const void*))3.这些参数都是什么意思&…

qsort函数详解

上篇文章&#xff0c;笔者讲解了冒泡排序的方法&#xff0c;原文链接为&#xff1a;一个典列来带领大家了解冒泡排序思想_念君思宁的博客-CSDN博客&#xff0c;有意者请参考一下&#xff01; 最近笔者又浅学关于qsort函数的排序方法&#xff01;下面且听笔者一一道来&#xff…

C语言函数——qsort函数的使用

目录 一、qsort函数&#xff1a; 1、定义&#xff1a; 2、参数&#xff1a; &#xff08;1&#xff09;.基础 &#xff08;2&#xff09;.数字 &#xff08;3&#xff09;.大小 &#xff08;4&#xff09;.比较 二、总代码&#xff1a; 1、整型比较&#xff1a; 2、浮…

利用qsort函数快速排序

一.qsort函数的类型及参数 void qsort(void *base,size_t num,size_t width,int (*compare)(const void* elem1),const void* elem2)1.第一个参数base&#xff1a;待排序数组的首元素的地址&#xff0c;数据类型为void*。 2.第二个参数 num&#xff1a;待排序数组的元素个数&…

详解c语言中的qsort函数(有图)

目录 目录 一、qsort函数是什么 1、自定义冒泡函数时遇到的问题 2、qsort函数的作用 &#xff08;1&#xff09;int整形数组排序&#xff08;2&#xff09;浮点型数组排序&#xff08;3&#xff09;字符数组排序 &#xff08;4&#xff09;结构体排序 二、qsort函数…

qsort函数详情

文章目录 一.qsort函数的使用1.qsort函数定义&#xff1a;2.使用 二.qsort函数的模拟实现 一.qsort函数的使用 1.qsort函数定义&#xff1a; qsort函数实现的功能为&#xff1a;对一组数据进行排序。 表现形式&#xff1a; void qsort(void *base, size_t num, size_t size,…

qsort函数

目录 1.什么是qsort函数2.实现一个qsort函数3.用qsort函数排序一个结构体4.模仿qsort的功能实现一个通用的冒泡排序 1.什么是qsort函数 我们以前学习过的一些排序算法&#xff0c;如冒泡、希尔、快排等等&#xff0c;它们速度有快有满&#xff0c;但是这些排序都只能排序一种类…

qsort函数的使用方法

前言 qsort函数是C语言库函数内给我们提供的一个可以实现排序的函数 它不仅可以排序数组&#xff0c;还可以排序字符串&#xff0c;以及结构体类型 下面是qsort函数的使用方法以及注意事项 一、了解qsort函数 根据MSDN提供的参数 由此我们可以知道 使用qsort函数需要引用头文…

简单介绍一下qsort函数

目录 一.回调函数 二.qsort函数 三.void*指针 四.用qsort函数进行升序排序 1.整形数组排序 2.结构体数组排序 3.字符数组排序 五.使用冒泡排序模拟实现qsort函数 一.回调函数 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;…

C语言qsort函数的使用详解

文章目录 一、qsort函数简介1.函数原型2.参数含义3.比较函数详解 二、比较函数使用案例1.整型数组2.字符数组3.double型数组4.字符串1.按字符串首字母进行排序2.按字符串长度进行排序3.按字典进行排序 5.结构体 三、qsort函数完整使用案例1.整型数组2.字符数组3.double型数组4.…

qsort函数用法 + 模拟实现qsort函数

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞…