文章目录

  • 字体反爬简介
  • 发送请求,获取网页源码
  • 提取字体信息,并将字体文件下载到本地
  • 建立基准字典
  • 引例
  • 提取需要字体反爬处理的信息
  • 提取不需要字体反爬的信息
  • 整理提取到的所有信息,并存入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"></svgmtsi><svgmtsi class="address"></svgmtsi><svgmtsi class="address"></svgmtsi><svgmtsi class="address"></svgmtsi>60<svgmtsi class="address"></svgmtsi>
addr = addrs[0].replace('<svgmtsi class="address">','').replace('</svgmtsi>','')
print(addr) #可以看到,地址信息中每一个汉字的‘密码’都是以&#x开头,#然后是4个字符,然后以;分割   #并且经过多次观察我们还可以发现,每个字的‘密码’中间的那4个字符,刚好就是该字对应的Unicode编码的后4位。#因此,按照此规律我们就可以进行‘解密’了。
#   60#按照上述发现的规律进行解密
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')

最终的结果:

字体反爬案例解析:大众点评相关推荐

  1. 爬虫进阶-- 字体反爬终极解析

    爬取一些网站的信息时,偶尔会碰到这样一种情况:网页浏览显示是正常的,用python爬取下来是乱码,F12用开发者模式查看网页源代码也是乱码.这种一般是网站设置了字体反爬 什么是字体反爬? 字体反爬虫: ...

  2. 抓取设了CSS反爬机制的大众点评数据(下)

    该篇实现大众点评爬虫操作代码,所有原理都在(上)篇均已详细阐述,让我没想到的是大众点评不仅设置了CSS反爬,在ip限制方面也是十分的凶狠,不得已花了10块钱买了一天代理ip. 大众点评究的反爬竟有多恶 ...

  3. Python爬虫 | 以滑雪为例演示大众点评商铺信息采集(字体反爬)

    文章目录 1.简述 2.字体反爬处理 2.1.获取字体文件链接 2.2.创建三类字体与实际字符映射关系 3.单页店铺信息解析 4.全部页数据获取 4.1.获取数据页数 4.2.采集全部数据 5.总结 ...

  4. 爬虫逆向学习(二):那些年遇到的花式字体反爬

    常见字体反爬破解策略 CSS偏移反爬虫 案例场景 破解策略 SVG字体反爬 案例场景 破解策略 自定义字体反爬 案例场景 破解策略 CSS偏移反爬虫 案例场景 css偏移反爬虫是通过样式left偏移覆 ...

  5. 爬取在线全面小说网小说(字体反爬)

    小说网字体反爬 小说网址:https://www.tianhuajinshu.com/ 在手机端浏览小说时,有时候开启无图模式发现部分文字加载不出来,还有的不能使用浏览自带的阅读模式进行阅读,也就是无 ...

  6. 中文字体反爬,易易易易易易【Python脱敏】车车车车车车车车

    文章目录 ⛳️ 易 实战场景 车 ⛳️ 易 实战编码 车 ⛳️ 易 实战场景 车 本次字体反爬案例对应的是[易Python脱敏车]点评频道,该站点使用了字体反爬技术,并且是中文字符反爬,可以重点研究下 ...

  7. python爬虫进阶-大众点评店铺信息(字体反爬-静态映射)

    目的 获取大众点评店铺信息 详细需求 http://www.dianping.com/shenzhen/ch10 思路解析 一 通过F12查找目标信息位置,进行分析 同理进行其他信息的解析,分析汇总 ...

  8. 大众点评数据信息获取——字体反爬

    大众点评数据信息获取--字体反爬 大众点评的字体反爬算是比较常见的,这次来学习一下相关字体反爬的技巧 以店铺的评论页面和店铺列表页面进行研究,分别对应了css字体映射,woff字体加密的反爬虫手段. ...

  9. 终于解决大众点评的字体反爬了!

    为了防止自己忘了,还是在这里写个大概过程吧 还不完善,之后再改 之前虽然我接触过一个字体反爬的网站,但是比较简单的,字体文件直接就在源码里,大众点评的不一样,我们先去网页看一下,可以发现,评价数,人均 ...

  10. 大众点评 数据爬取 (字体反爬)

    大众点评 数据爬取 (字体反爬) 项目描述 在码市的平台上看到的一个项目:现在已经能爬取到需要的数据,但是在爬取的效率和反爬措施上还需要加强. 项目分析 1.打开大众点评的首页'http://www. ...

最新文章

  1. 神经网络优化算法综述
  2. echarts一个页面有多个tooltip_可视化工具ECharts入门
  3. 二、linux最小驱动
  4. 十三、欧拉离散化计算期权定价期权定价
  5. Tencent JDK 国产化CPU架构支持分享
  6. pytorch dropout_PyTorch初探MNIST数据集
  7. phpcms函数:用*号替换(私密信息)中间数据(如手机号、邮箱)
  8. Java的finally理解
  9. Java中循环删除list中元素的方法总结(总结)
  10. 公司买的机器不能自己装系统,问对方几天没回一个字
  11. 【定位问题】基于matlab RSSI和模拟退火优化粒子群算法求解无线传感器网络定位问题【含Matlab源码 1766期】
  12. mysql sql loader_Sql Loader的简单使用
  13. TOEFL wordlist 23
  14. iphone 操作手势种类
  15. [转载] 晓说——第26期:艺术北纬三十度 回忆印度(上)
  16. 春运火车上的这10种人,有一个就是你!
  17. 学习了罗昭锋的文献管理与信息分析的感受
  18. 去掉所有的空格、回车换行符
  19. 与苹果和谷歌抗争堡垒创造者发动了远征
  20. 物流里程 | 使用EXCEL制作物流配送城市里程表

热门文章

  1. win7 mysql 管理员权限_win7 管理员权限
  2. jetbrain account不能访问的问题
  3. shell 自动搜索历史记录
  4. Banner图片轮播器实现ViewPager图片切换效果及下方小圆点
  5. Python 基于豆瓣电影的可视化分析系统
  6. Ubuntu18.04安装灭霸SLAM:ORBSLAM3
  7. 667. 优美的排列 II
  8. win10 uwp 录制任意应用屏幕
  9. 压缩卷时可压缩空间远小于实际剩余空间解决方法
  10. 电子签名、数字签名、数字证书、电子签章、电子印章的概念和区别