公司最近致力于实现餐饮行业的AI发展模式,领导希望采集一些餐饮数据来提供理论支持。所以没多少头发的我 ,被喊过来做数据收集。
想到餐饮数据的收集,第一反应是去爬取美团/大众点评的数据,对比了下美大众点评的数据,发现两者差不多,没什么太大的不同,但大众点评的数据更符合我们的需求(其实是听说大众点评的反爬没有那么狠),就果断选择爬取大众点评的数据。很怀念大众点评没有被美团收购的时光,那个时候数据是随便爬的。

在爬取大众点评之前,已经想到会遇到反爬,只是没想到反爬措施这么狠。
爬取的关键问题主要是2点:

  1. IP被封
  2. 网页内容加密

关于ip的问题,可以使用代理来解决,网上有一些开源的代理ip,当然如果你是有钱人家的孩子,可以购买代理。

因为我要爬取每个商户页面的详细信息,需要进入到每个商户的主页,url类似这种:http://www.dianping.com/shop/66755991.
后面那一串数字是代表商户的id,我只要获取到所有的shopid就可以进行店铺详情页爬取。这里的问题是如何获取到shopid。

如何获取shopid?

大众点评只显示前50页的内容,这也是为了反爬,为了获取全部的内容,采取组合美食分类和区域id的方式,这样基本上就可以限制页数小于50。
url类似这种:
http://www.dianping.com/shenzhen/ch10/g117r1949
其中g117代表美食分类,r1949代表区域id。

  1. 先获取大众点评的美食分类的标签
  2. 按照行政区获取各区域的id
  3. 将美食分类和区域组合就可以获取到该链接下的shopid。

这里获取到所有的shopid之后,我尝试进行了店铺详情的爬取,在成功爬取几个店铺详情页后,楼主的IP就被封了(大概封了2周的时间),果断采用代理的方式,但是发现开源的代理中本来就没有几个能 用的ip,而一个ip在爬取2页面差不多就要被封了,这样导致效率非常低。这个时候想到爬取移动端(APP)的数据。在进行一番验证后,发现shopid在网页端和移动端是一致的。爬取移动端的数据,代理ip的有效时间会好很多。最后就果断采取爬取app端的数据。
说明下:我也尝试了使用自动化测试框架selenium去模拟人为操作,但是大众点评可以识别出selenium框架,直接就进入到了验证中心,输入验证码后仍然会报服务拒绝。 就果断弃之(貌似有阉割版的浏览器驱动,可以跳过大众点评的验证)。

采用代理ip和爬取移动端的数据解决了反爬的第一个问题。

下面来讲述一下破解页面加密的血泪史。
打开某个店铺的页面,以地址为例,我们看到的内容是这样的,

按下F12,源码中是这样的。

(备注:图中不是同一个门店的页面和源码,因为我的ip被封了,就找了本地文件截图,但意思明白就好。)
这里看到源码中的白色框框就是大众点评加密的内容,需要破解的也就是这部分。这是什么鬼东西,关键信息全部乱码。验证了一个道理,你看到的不一定都是真的。
经过一番查找博客,才搞明白这是css的一种机制,之所以显示白色的方框,是因为在开发网页时指定了字体。这里的乱码是由于unicode编码导致,查看源文件可以看到具体的编码信息。

这些字体文件是通过css加载的,我们在源码搜索svgtext可以看到

复制href后面的内容,加上http:(http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/aa8cc71119730f22aed80ecd0ca67b7d.css)
我们打开该css链接,内容如下:

其中网页不同的地方使用了不同的字体,绿色框的部分就是我们要找的字体网址,输入该链接(加上http:),会下载该字体,使用百度在线字体编译器(http://fontstore.baidu.com/static/editor/index.html)
打开后,下载的woff文件如下:

绿色部分对应的就是该字体的编码,与源码中的unicode编码相比可知,编码的后四位是一样的,只要找到源码中unicode后四位一致的字符编码,该编码对应的汉字就是网页源码中要显示的信息。这里只要建立编码—汉字字典,取对应的编码的汉字就可以破解网页源码中的白色框框。这里的难点就是建立编码—汉字字典。暂时没有成熟的技术实现。这里我采用比较笨拙的办法,打开下载不同的woff文件,发现woff文件中的汉字和顺序都是一样,不同的是汉字编码。所以就手动按照顺序把汉字复制下来。然后再建立编码—汉字字典。(如果有大佬实现了自动建立,多多指教),具体代码会在后面给出。
获取shopid的代码,这里使用的是本机ip,没有使用代理ip

import requests
import re
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
from time import sleep
import random# 获取食品类别
def get_classfy():classfy_list = []url = 'http://www.dianping.com/shenzhen/ch10'user_agent = UserAgent().randomheaders = {'User-Agent': user_agent}res = requests.get(url, headers=headers)print(res.text)soup = BeautifulSoup(res.text, 'html')classfy = soup.find('div', id='classfy')for i in range(len(classfy.find_all('a'))):classfy_list.append(int(classfy.find_all('a')[i]['data-cat-id']))return classfy_listregionList = [# 福田区('futian', 'http://m.dianping.com/shenzhen/ch10/r29'),# 南山区('nanshan', 'http://m.dianping.com/shenzhen/ch10/r31'),# 罗湖区('luohu', 'http://m.dianping.com/shenzhen/ch10/r30'),# 盐田区('yantian', 'http://m.dianping.com/shenzhen/ch10/r32'),# 龙华区('longhua', 'http://m.dianping.com/shenzhen/ch10/r12033'),# 龙岗区('longgang', 'http://m.dianping.com/shenzhen/ch10/r34'),# 宝安区('baoan', 'http://m.dianping.com/shenzhen/ch10/r33'),# 坪山区('pingshan', 'http://m.dianping.com/shenzhen/ch10/r12035'),# 光明区('guangming', 'http://m.dianping.com/shenzhen/ch10/r89951')
]# 获取各行政区单位id
def get_region_list(regionUrl):region_id_name = []user_agent = UserAgent().randomheaders = {'User-Agent': user_agent}res = requests.get(regionUrl, headers=headers)print(res.text)soup = BeautifulSoup(res.text, 'lxml')region = soup.find('div', class_='menu sub')for i in range(1, len(region.find_all('a', class_="item Fix"))):region_id_name.append((int(region.find_all('a')[i]['data-itemid']), str(region.find_all('a')[i]['data-itemname'])))return region_id_name
def get_all_area_list(regionurlList):all_area = []for regionname, regionurl in regionurlList:region_dict={}region_id_name = get_region_list(regionurl)region_dict[regionname]=region_id_nameall_area.append(region_dict)return all_area
#组合分类和区域id获得URL
def recostution_url(classfy_list, all_area):Reurl = []for classfy in classfy_list:for data in all_area:for region,regiondata in data.items():for area_id, area_name in regiondata:Reurl.append((region,area_name, area_id,'http://m.dianping.com/shenzhen/ch10/' + 'g' + str(classfy) + 'r' + str(area_id)))return Reurl# def get_shopContent(Reurl):user_agent = UserAgent().randomheaders = {'User-Agent':user_agent}res=requests.get(Reurl,headers=headers)return res.text
def get_shopId(Reurl):resource=get_shopContent(Reurl)while '验证中心' in resource:print('出现验证码')sleep(random.randint(1,3))resource=get_shopContent(Reurl)shopidList=[]soup=BeautifulSoup(resource,'lxml')p2 = re.compile(r'{.*}', re.S)string=soup.find_all('script')[2].string.strip()string=string.replace('true','True')string=string.replace('false','False')content=eval(re.findall(p2,string)[0])for adshop in content['mapiSearch']['data']['list']:shopidList.append(adshop['shopUuid'])return shopidList
shopList=[]
for i in range(len(Reurl)):print(i)shopidList=get_shopId(Reurl[i][3])for shopid in set(shopidList):shop_location={}shop_location['shopid']=shopidshop_location['region']=Reurl[i][0]shop_location['area']=Reurl[i][1]shopList.append(shop_location)sleep(random.randint(1,3))

这里我为了实现分布式爬虫,使用redis构建队列,将爬取下来的shopid加入到队列中。
爬取店铺详情页的代码,这里我保存店铺详情页源码到hdfs中,页面的解析是下一步工作:

import requests
import json
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import time
import re
import redis
import random
from hdfs.client import Client
#获取开源代理ip
def getListProxies():ip_list = []session = requests.session()headers = {'User-Agent': UserAgent().random}page = session.get("http://www.xicidaili.com/nn", headers=headers)soup = BeautifulSoup(page.text, 'lxml')taglist = soup.find_all('tr', attrs={'class': re.compile("(odd)|()")})for trtag in taglist:tdlist = trtag.find_all('td')proxy = {'http': 'http://' + tdlist[1].string + ':' + tdlist[2].string}ip_list.append(proxy)return ip_listip_list=getListProxies()
client = Client("http://192.168.31.51:50070")q = RedisQueue('ftshop')
r = redis.Redis(host='localhost', port=6379)
region_dict = {'市中心区': 1949,'车公庙': 7475,'上沙/下沙': 12322,'梅林': 1560,'华强北': 1556,'欢乐海岸': 30824,'皇岗': 1559,'景田': 12321,'新洲': 12225,'香蜜湖': 1951,'荔枝公园片区': 1573,'石厦': 12226,'八卦岭/园岭': 1557,'竹子林': 12324,'市民中心': 12323,'华强南': 3138,'岗厦': 12320,'福田保税区': 12319}def save_page_hdfs(ipPort, file_path, contents):"""保存网页源码到hdfs:param ipPort: hdfs连接地址:param file_path: 文件路径:param contents: 网页内容:return: None"""client = Client(ipPort)with client.write(file_path) as writer:writer.write(bytes(contents, encoding='utf8'))proxy = ip_list[0]
error=0
while 1:queue_len = r.llen('queue:allshop')queue_index = 0s = requests.session()n = str(q.get_nowait(), encoding='utf8')data = json.loads(n)shopid = data['shopid']region = data['region']area = data['area'].encode("utf-8").decode("utf-8")headers = {'User-Agent': UserAgent().random,'Referer': 'https://m.dianping.com/shenzhen/ch10/r{0}'.format(region_dict[area])}url = 'https://m.dianping.com/shop/' + shopidtry:respon = s.get(url, headers=headers, proxies=proxy)except Exception as e:error = 1i = 0while '验证中心' in respon.text or '抱歉!页面暂' in respon.text or respon.status_code != 200 or error == 1:i = i + 1if i < len(ip_list):proxy = ip_list[i]try:respon = s.get(url, headers=headers, proxies=proxy)except Exception as e:error == 1else:q.put(n)break# 用来判断队列是否循环一遍,更新ip池queue_index += 1if queue_index == queue_len:ip_list = getListProxies()if '验证中心' not in respon.text:if '抱歉!页面暂' not in respon.text:print('success')print(n)filepath = '/dazhongdianping/sz/{0}/{1}/{2}.html'.format(region, area, shopid)try:save_page_hdfs('http://192.168.31.51:50070', filepath, respon.text)#r.rpush('metadata', json.dumps(#   {'shopid': shopid, 'url': url, 'region': region, 'area': area, 'filepath': filepath}))except Exception as e:passtime.sleep(1)

建立编码—汉字字典的代码:

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

git:https://github.com/zt0910/dp_scripy.git

爬取大众点评数据的血泪史相关推荐

  1. python爬取大众点评数据

    python爬取大众点评数据 参考博客: python+requests+beautifulsoup爬取大众点评评论信息 大众点评评论抓取 Chrome如何获得网页的Cookies 如何查看自己访问网 ...

  2. python爬取大众点评数据_python爬虫实例详细介绍之爬取大众点评的数据

    python 爬虫实例详细介绍之爬取大众点评的数据 一. Python作为一种语法简洁.面向对象的解释性语言,其便捷性.容易上手性受到众多程序员的青睐,基于python的包也越来越多,使得python ...

  3. python爬取大众点评数据_Python 爬取大众点评 50 页数据,最好吃的成都火锅竟是它!...

    作者 | 胡萝卜酱 责编 | 伍杏玲 成都到处都是火锅店,有名的店,稍微去晚一点,排队都要排好久,没听说的店,又怕味道不好. 那么如何选择火锅店呢?最简单的肯定是在美团.大众点评上找一找啊.所以,本文 ...

  4. python爬取大众点评数据_利用Node.js制作爬取大众点评的爬虫

    前言 Node.js天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C.Java不一样):for循环体({})内引用i的值实际上是循环结束之后 ...

  5. 用Python爬取大众点评数据,推荐火锅店里最受欢迎的食品

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:有趣的Python PS:如有需要Python学习资料的小伙伴可以加点 ...

  6. 爬取大众点评黄焖鸡米饭的数据

    学习python已经一段时间,就想着利用他爬取大众点评上的一些数据,用于分析. 这里,我选择爬取国内各个地区和省份关于黄焖鸡米饭的店面数据 具体的格式:店面 id,省份,城市,开店时间,店名 首先声明 ...

  7. 为了部落 来自艾泽拉斯勇士的python爬虫学习心得 爬取大众点评上的各种美食数据并进行数据分析

    为了希尔瓦娜斯 第一个爬虫程序 csgo枪械数据 先上代码 基本思想 问题1 问题2 爬取大众点评 URL分析 第一个难题 生成csv文件以及pandas库 matplotlib.pyplot库 K- ...

  8. 爬取大众点评页面数据教程,图片文字如何爬取

    大众点评的商家地址和详细分类,居然是用svg图形展示的文字,哇,真是用心良苦,为了反爬,可谓是脑洞大开啊,图形文字.滑块验证码.封ip,全都用上了,真是让人头疼.不过正所谓道高一尺,魔高一丈,没有达不 ...

  9. python爬取大众点评_浅谈python爬取58同城,大众点评这两个网站

    1.爬取58同城租房网遇到的坑:我爬了一页数据被封了ip,此时的我是非常的不爽,才十几条数据就封我,于是我就想着一定得找ip代理来解决这个问题,后面就写了个ip代理去弄,另外把之前头部信息ua改成了u ...

最新文章

  1. GnuPG如何安全地分发私钥(1)GnuPG的用法
  2. 面试:HashMap 夺命二十一问!
  3. Linq to sql和lambda
  4. 推荐:两款实用的Jupyter插件~
  5. gitlab-ci详细说明
  6. jvm7 jvm8_我们真的仍然需要32位JVM吗?
  7. 不与最大数相同的数字之和(信息学奥赛一本通-T1113)
  8. mysql学习day05—子查询 / CASH语句 / 连接查询
  9. 7-2 查找指定字符 (15 分)
  10. C语言——自动关机小程序
  11. linux万能密码,Linux pam 后门纪录root用户密码以及设置万能密码登录root
  12. 建立民间贷款集资合法化
  13. system.Exception:端口已被占用1080
  14. Android中浏览PDF文件
  15. Crazy Mother
  16. 【Unity3D】图片纹理压缩方式,干货走起!
  17. bootstrap 5 表单验证
  18. Logical Architecture
  19. 一步步学习微软InfoPath2010和SP2010--第一章节--介绍InfoPath2010
  20. 一分钟搞懂云计算和大数据对人到底有啥用?

热门文章

  1. DirectX9.0C Aug.2007
  2. WebGL实时视频(5) awrtc.js理解并修改
  3. Unity高频面试题总结
  4. SpringBoot库存管理系统,拿来学习太香了(附源码)
  5. java 指定垃g1圾收集_java垃圾回收G1收集器
  6. 002-ESP32学习开发(SDK)-测试网络摄像头(OV2640),实现远程视频监控(花生壳http映射)
  7. 制作圆形图片,你会以下几种?
  8. day11函数进阶作业
  9. jQuery 图像 360 度旋转插件
  10. 快速找到python第三方库