如何“攻破”大众点评的文字加密防线?

百家 作者:CSDN 2018-12-20 15:26:54

作者 | 左伊雅

责编 | 郭芮

相信在座的各位一定有爬过大众点评的数据,但是却因为它的各种加密力不从心。这里必须告诉你,看完这篇文章,大众点评加密你就能轻松搞定。

现以麦当劳(罗宾森购物中心店)为例进行说明:http://www.dianping.com/shop/77335766。

从上图就可以看到,7,2,8,0,6,城,中,路等等都无法被选择,再打开开发者工具定位,它的人均27元中的7对应.zl-tvPf;2对应.zl-gc5M。这就是大众点评的加密,将部分文字用标签代替。

文字加密的方法有两种:一种是映射,一种是图片。而大众点评用的正是后者——这是怎么看出来的呢?

大家仔细看一看下面的两幅图,笔者分别选择了.zl-gc5M;.zl-tvPf。观察一下两幅图的右侧(也就是两个标签对应的style),它们的右上是d[class^="zl-"];右下分别是.zl-gc5M 和.zl-tvPf。

点击可放大图片

现在来分析右上的d[class^="zl-"]部分。这里的意思是说,以zl-开头的映射对应的background-image为:url(//s3plus.meituan.net/v1/mss_0a06a47…/svgtextcss/ab22723….svg)——这个URL就是数字图片的地址,将这个链接打开会看到这样一组数字图片:

再来看右下.zl-gc5M 和.zl-tvPf部分。background:-36.0px -7.0px;background:-106.0px -7.0px;懂一点CSS的都知道,这是图片平移,分别往左平移36个像素点和106个像素点。回到上副图,将数字2往左平移36个像素点,把数字7往左平移106个像素点,数字2和7就可以在网页上显示出来。这就是文字图片加密的原理。

找到对应的CSS文件,建议不要格式化,全部复制存为字符串。这里要说明一下,大众点评把所有文字、数字的平移放在同一个CSS文件里,所以这个CSS格式化以后有19301行。

数字的分类字母为‘zl-’:

数字的分类字母是 'zl-',从上面的CSS文件里将 'zl-'调出:

zl-tvPf{background:-1060px -70px;}
zl-Jvp2{background:-220px -70px;}
zl-giSW{background:-780px -70px;}
zl-Cg3x{background:-1340px -70px;}
zl-gc5M{background:-360px -70px;}
zl-htaN{background:-640px -70px;}
zl-TohQ{background:-500px -70px;}
zl-JTyc{background:-1200px -70px;}
zl-FhcV{background:-80px -70px;}
zl-Bthl{background:-910px -70px;}

可以看到不同的class只有左右偏移量不同,最小偏移量为80Px,最大的偏移量为1340Px。这是一个公差为140的等差数列,所以整体思路是:根据平移的多少来和数字串‘9426831705’匹配,即偏移量为80对应数字9,偏移量为220对应数字4......这里要说明一下的是偏移量为910的zl-Bthl,它对应的是数字1,但是大众点评的1是没有加密的,就把数字1对应的真正的偏移量920改为了910。这里对整体是不影响的。

def number():
   #正文不能超过50000字,这个q先删了,小伙伴自行去CSS文件复制即可

   q = q.split('.')
   number = '9426831705'
   data,changedic,s ,w,j,p= {},{},[],'',0,0
   for i in q:
       w = w + i
       if j == 3:
           if 'zl-' in w:
               #print(w)
               data[int(re.findall(r'background:-(.*?)px', w)[0])] = w.split('{')[0]
               s.append(int(re.findall(r'background:-(.*?)px', w)[0]))
           w ,j= '',0
       j = j + 1
   for i in sorted(s, reverse=False)[:-1]:
       changedic[''.format(data[i])] ,p= number[p],p+1
   print(changedic)
   return changedic

这是匹配的结果:

{'zl-FhcV': '9', 'zl-Jvp2': '4', 'zl-gc5M': '2', 'zl-TohQ': '6', 'zl-htaN': '8', 'zl-giSW': '3', 'zl-Bthl': '1', 'zl-tvPf': '7', 'zl-JTyc': '0', 'zl-Cg3x': '5'}

找了美食、健身馆、游泳馆、按摩、景点五种网页进行实验,所有的数字都成功地识别出来了。

{'tel''021-59991297''review''117''人均''27''口味''7.8''环境''8.0''服务''7.6'}
{'tel''021-64161059''review''1326''人均''340''技师''9.3''环境''9.2''服务''9.3'}
{'tel''16621127940 xa0 17321127275''review''133''人均''755''水质''9.3''环境''9.2''划算''9.4'}
{'tel''021-67734074''review''8183''费用''77'}
{'tel''13611752158''review''66''人均''''设施''9.1''环境''9.1''服务''9.1'}

这里把最核心的代码贴出来,完整版会上传到GitHub。

def change(dic,text):
   for key,value in dic.items():
       text=text.replace(key,value)
   return text

def score(changedic,urls):
   for url in urls:
       inf={}
       inf['tel']=re.findall(r' (.*?) ',change(changedic,str(soup.find('p',class_='expand-info tel'))))[0]
       inf['review']=re.findall(r'> (.*?) 条评论',change(changedic,str(soup.find('span',id='reviewCount'))))[0]
#不同的网页结构不同,所以加了两个try/except使代码适用于全部网页
       try:
           inf[re.findall(r'>(.*?) 元',change(changedic,str(soup.find('span',id='avgPriceTitle'))))[0].split(':')[0]]=re.findall(r'>(.*?) 元',change(changedic,str(soup.find('span',id='avgPriceTitle'))))[0].split(':')[1].strip()
       except:
           inf[soup.find('span', id='avgPriceTitle').text.split(':')[0]]=''
       try:
           soup = soup.find('span', id='comment_score').find_all('span', class_='item')
           for i in soup:
               k = re.findall(r'(.*?) ', change(changedic, str(i)))[0]
               inf[k.split(':')[0]] = k.split(':')[1].strip()
       except Exception:
           continue
       finally:
           print(inf)

至此,大众点评的数字解密就讲完啦。

看到这里的小伙伴都很优秀噢,接下来讲的会比数字难很多。Are you ready?

现在开始看评论,以(http://www.dianping.com/shop/77335766)的第一条评论为例:

可以看到评论里的部分文字都被替换成了标签,显示出的文字完全是读不通的,和数字一样,它有一张文字图(background-image),通过移动各个文字的位置(background)将文字显示出来。

打开(background-image)的URL,可以看到有很多文字,大众点评所有加密的文字都在这里啦。和数字一样,是通过将单个字往左,往上平移显示出来;找到相应的CSS文件,再将文字与标签匹配。

仔细看下图,第一个数字对应的左移,第二个数字对应的上移,下面把左移称为第一组数据,上移称为第二组数据。

第一组是以0为首项,140为公差的等差数列,最大为5740,对应第42列;第二组是以70为首项,公差为300的等差数列,最大值为23770,对应第80行。整个思路就是做一个循环,一行一行的匹配,第一行对应70px,第二行370px,依此类推。在每一行中,根据它的第一组数据(左移量)从小到大排列,和每行的文字一一对应。

注意一下,它的换行表达为以及空格对应xa0。所以在匹配的时候,用代替,用代替xa0。即result['
']='。' ;result['xa0']=';'。匹配的结果:

代码如下:

def chinese(text):
   q = q.split('.')
   w,j,s,data,col,row='',0,[],{},[],[]
   for i in q:
       w = w + i
       if j == 3:
           if 'fu-' in w :
               data[re.findall(r'px -(.*?)px', w)[0]+'/'+re.findall(r'background:-(.*?)px', w)[0]] = w.split('{')[0]
               col.append(re.findall(r'background:-(.*?)px', w)[0])
               row.append(re.findall(r'px -(.*?)px', w)[0])
           w ,j,s= '',0,[]
       j = j + 1
   result={}
   for i in range(70,23770+300,300):
       t=text[int((i-70)/300)]
       l=len(t)
       for j in range(0,l):
           if j==0:
               key=str(i)+'/00'
           else:
               key=str(i)+'/'+str(j*140)
           try:
               result[''.format(data[key])]=t[j]
           except KeyError:
               continue
   result['
'
]='。'
   result['xa0']=';'
   return result

标签和字对应后就来试着爬一下评论吧。在大众点评里,长的评论会只显示部分,它的全部内容在class='desc J-desc'的

标签,短的评论在class='desc'的

标签内。

和前面一样,选择了美食、按摩、健身、旅游、游泳五个网页进行测试,每组只显示前三条评论,得到的结果如下:

http://www.dianping.com/shop/77335766
['坐标:罗宾森广场(以前的巴贝拉?暴露年纪?)。早上七点,想吃早饭,选麦当劳没错了[馋]上下两层,楼下只卖冰淇淋?(早上没有的),二楼点餐,座位蛮多的,随便找了个座位~。选了最爱的猪柳蛋汉堡配豆浆¥16!口味就是连锁店的味道(都一样、没有任何差别)~。最近推送的新品看着就好想好想吃[笑哭]但是早上木有[笑哭]那个鸡排以前吃过,超级香的!新出的冰淇淋颜色超好看,名字好绕口好奇怪!下次有机会再来打个甜品的卡吧[机智][机智][机智]']
['一大早来一份差评其实我也不想的。终端自助点餐,选择的是堂吃,结果在店里根本不忙的情况下还是出错,装了袋子打包[发呆]。单子上都有写明堂吃的低级错误也会犯我也是醉了。至于食物方面,快餐店也就不说了,随便吃吃就好了']
['在支付宝的麦当劳生活号领了一个大脆鸡扒麦满分的早餐免费券,今天来换汉堡,虽然一个不大,但是可以➕7元换购豆浆或者其他东西,这个麦满分口感还不错,鸡肉里有黑胡椒,儿子吃了觉得有点辣[偷笑]但是又想吃,就是单吃这个觉得有点干,最好是加一杯饮料或者水哦。']
http://www.dianping.com/shop/9799086
['最近身体各处都很紧绷;硬帮帮的;肯定大众点评上面推荐的这家;感觉还不错就预定了;中午订好;下午就去做了;初次体验了全身精油按摩;首先前台小姐姐安排挑选精油;然后按摩霜小姐姐带上楼去;是单人间呢;非常安静;地方不大;她们都是英文沟通;会几句简单的中文;想洗了澡之后;小姐姐开始按摩;力度很不错;轻重自己选择;真的太舒服了;全身上下都是放松了;全程很享受;还有大自然的音乐;真的很细心;最后她会把身上多余的精油擦干;按摩头部;这个人轻松很多;回家路上就很想睡觉了哈哈;下次还会再来的;下次体验一下别的。']
['去过很多Spa馆,这一家真的可以算得上上海非常专业的了~;靠谱~全程都是柬埔寨小姐姐英文跟你沟通~不用担心不会英语小姐姐会一些基础中文,还有自己制作的小卡片~;哈哈哈哈~完美!再有现场的地址~也蛮好找的~在一个幽静的小巷弄里面~;很有感觉的哇~老板也很Nice~;还有手法专业~;没有推销!']
['环境一般,但是手法不错。洗个澡做个精油很舒服,全是柬埔寨人,男女技师都有。门面倒是真的不大,前台态度很好!服务很到位,一楼换完拖鞋去二楼等待洗浴,就是浴室里有股味道,整洁程度还行,主要看按摩手法,总体体验不错,去过几次。']
http://www.dianping.com/shop/92871773
['不喜欢大班课,所以买的一对二课程,团购560,然后游泳门票30-40/次,教练门票一共240(120/人),一共六节课平均下来150/次,还是很划算的~我和闺蜜想跟着女教练比较方便,我们很幸运遇到了常教练,非常耐心[捂脸]碰到我和闺蜜两个旱鸭子,真的很笨拙~哈哈,我希望可以跟着常教练把自由泳学会学好~(吐槽天冷了游泳馆没有空调!实在太冷了!大型游泳场所也别想水可以多干净[笑哭])']
['因为零基础,所以选择了一对一的课程,。看评论说常教练很好,所以请客服安排了她,很漂亮的一位教练哦,哈哈,没有当面和教练说过。。真的是一位经验很丰富,负责的教练,根据我的自身情况安排课程进度,让我通过短短的六节课就从一个旱鸭子,变得可以自行游个50米。打算蛙泳游的顺手了,再去找常教练学习自由泳。。建议要游泳的朋友选择常教练。没错的。零基础的朋友建议一对一吧。']
['客服安排和一个可爱的妹子拼了一对二的6节课程,一开始超怕的,以前从来没有游起来过,不过我们的教练(常军英教练)超级nice,温柔又耐心,讲解和示范都很到位,旱鸭子六节课下来也可以游到半个泳池长啦!超开心~']
http://www.dianping.com/shop/23635007
['[爱心]行走的英伦风格,小时代的拍摄点,整个小镇弥漫着文艺气息,兜兜转转可以逛一天,因为带着狗狗,其实比较建议骑自行车,那会省力很多哦~。。[爱心]这个教堂是泰晤士小镇的标志性建筑,顶尖的大教堂十分引人注目,也是许多人慕名而来拍婚纱照的热门地点,也路过可以看到好几对新人在拍照。沾沾喜气,哈哈~。。[爱心]逛累了还能去钟书阁装文艺,我进去拍照的时候,臭狗就在门口等我,哈哈哈,还挺乖的~。。[心碎]唯一觉得不足的地方就是,交通不太方便,如果没有车,真的还是不太方便哦~本本族表示...如果地铁可以直达就更完美了!']
['小雪已过天仍暖,银杏满地金灿灿。。小镇畅游把景看,回味无穷是此山。。上海佘山世茂洲际酒店(深坑酒店)体验之后,午时退房返回市区,顺便重游泰晤士小镇,或许是平常日观光者并不多,传说中拍婚纱照的也不多,兜兜转转一圈下来仅仅看到二对拍婚纱照的,但路旁两边依旧停满了车,想必小镇的人气还是挺旺滴,最吸引我的便是银杏树下满地飘落的叶子……那是深秋初冬最美的瞬间!']
['这是我记忆中第二次来泰晤士小镇了!记得好几年前来的时候,这里还有很多店没有开出来,吃的也没有现在那么多!每次来泰晤士小镇都会下雨,而且每次来都是这个季节,也许是莫名的一种缘分吧!若能开展一些网红店,或者把室内装修设计再考究一写,做到了外观漂亮像英国的泰晤士,也要做到室内装修精致的泰晤士!这一次我最喜欢的最映入眼帘的是“钟书阁”,地板下都是书,感觉整个人都在书海里,氛围很不错,上海有好几家“钟书阁”,我最喜欢泰晤士小镇的这家,饮品好喝外,还有美丽的小姐姐服务生,还有宽敞的空间坐在店里看书!希望我在去的时候,泰晤士小镇可以有些变化呢[吐]']
http://www.dianping.com/shop/78965850
['喜欢这的教练(我的美女教练),喜欢这的关键(安静),也喜欢这的氛围(和谐)。练了快两个月才来评价的,明显感觉自己身体机能变好了,有力量也有塑型。最让人舒服的一点:这儿的教练从来不会向你推荐这这那那(从来不强买强卖),全凭你自己的心所向,当然刚开始教练会根据你自身的身体情况给你些合理的建议。以上纯真话,也是练了这么久之后的真实想法?']
['里面小小的;不是特地来还真没想到这是个这么小的,就跟家里房间那样的大小。放了一些器材。隔壁就是厕所还是蛮方便的。不过进来试了一下后就发现教练还是蛮负责满耐心的,虽然没有健身房的那么有激情,但是也够了;毕竟价钱要比健身房里的便宜一半。我看了一下;生意也是很好的,羡慕这些教练,工作即兴趣。群里也会发出来该吃的食谱什么的;还是很不错的']
['说实话;我本身是不爱运动的人;真的是下了很大的决心才决定来这里尝试一下的;等上了几节课后;真的就喜欢上了这里;感觉这里的教练们都是小伙伴;不是那种一板一眼的;每堂课都有哈哈大笑的时候;我觉得我可能离不开这里了;希望我的教练小姐姐不要嫌我烦[奸笑]']

第一条评论就是前面举例的那条,可以对比一下噢,内容是完全一样的。

大众点评的解密到这里就全部讲完了,小伙伴可以自己动手做一次。所有代码已全部上传到Github(https://github.com/zuobangbang/text-decoding/tree/master/dianping)。之前上传的比较乱,以后上传的代码会进行精简和说明,帮助大家更好理解,欢迎在Github上关注我。

作者:左伊雅,目前在南京某 211 大学读研二,喜欢数据挖掘和爬虫。

声明:本文为作者投稿,文章仅用于学习交流,不得用于其他途径。


 热 文 推 荐 

 突发!5G 标准推迟三个月

 谁还能救小黄车?

 Python 分析 35 年的考研英语真题词汇,解读孤独的考研大军!

 刚发布!Python 一二线城市月薪 15K 起!12 月再夺语言榜首

☞ 程序员真的都不爱炫富吗?

☞ Elastic:Beyond Search!

 会玩! 比特币诈骗手法升级, 从要钱到要命!

☞ 特斯拉加速“国产化”,上海工厂一期建设曝光

print_r('点个赞吧!');
var_dump('点个赞吧!');
NSLog(@"点个赞吧!");
System.out.println("点个赞吧!");
console.log("点个赞吧!");
print("点个赞吧!");
printf("点个赞吧!n");
cout < < "点个赞吧!" < < endl;
Console.WriteLine("点个赞吧!");
fmt.Println("点个赞吧!");
Response.Write("点个赞吧!");
alert("点个赞吧!")
echo "点个赞吧!"

点击“阅读原文”,打开 CSDN App 阅读更贴心!

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接