Python爬虫系列之爬取美团美食板块商家数据(二)

今天为你们重写一个美团美食板块小爬虫,说不定哪天作旅游攻略的时候也能够用下呢。废话很少说,让咱们愉快地开始吧~json

开发工具

Python版本:3.6.4
相关模块:

requests模块;api

argparse模块;app

pyquery模块;echarts

jieba模块;dom

pyecharts模块;ide

wordcloud模块;函数

以及一些Python自带的模块。工具

环境搭建

安装Python并添加到环境变量,pip安装须要的相关模块便可。学习

原理简介

前期准备:开发工具

由于我想让这个小爬虫能够爬取美团上任意城市美食板块的数据,可是每一个城市的URL是不同的,其格式为:

https://{城市拼音缩写}.meituan.com/

不一样的城市须要不一样的URL来构造请求从而爬取咱们所须要的数据,因而如今的问题就变成了:如何获取全部城市对应的城市拼音缩写呢?

其实很简单,点击网页上的切换城市按钮:

图片

而后查看网页源代码:

图片

因而咱们很easy地就能够爬取全部城市对应的城市拼音缩写了,代码实现以下:

'''城市名-拼音码爬取'''
def downCitynamesfile(citynamesfilepath):
  url = 'https://www.meituan.com/changecity/'
  doc = PyQuery(requests.get(url).text)
  cities_dict = dict()
  [cities_dict.update({city.text(): city.attr('href').replace('.', '/').split('/')[2]}) for city in doc('.cities a').items()]
  with open(citynamesfilepath, 'w', encoding='utf-8') as f:
    f.write(json.dumps(cities_dict, indent=2, ensure_ascii=False))

爬虫主程序:

如今随便切换到一个城市,以杭州为例。简单抓个包,能够发现美食商家的数据能够经过请求下图这个URL得到:

图片

其构造方式为上图红框框出的baseURL加上下图所示的一堆参数:

图片

其中变量为:

cityName:城市名
page:页码
uuid:uuid
_token:_token

其余均为不变量,直接copy过来就好了。前面两个变量很明显是什么,就很少说了。变量uuid在网页源代码里就能找到:

图片

至于_token,稍微麻烦一点。考虑到_token结尾出现了=,因此猜想是base64编码,可是解码后发现是一堆16进制ASCII码,因此考虑原数据是先进行二进制压缩而后base64编码的。反向操做一波,发现果真是这样的:

图片

全局搜索找生成相关参数的源代码:

图片

一顿分析以后就能够开始写_token生成的代码了,具体以下:

'''获取SIGN'''
def getSIGN(cityname, page, uuid, city_code):
  url = 'https://{}.meituan.com/meishi/'.format(city_code)
  sign = 'areaId=0&cateId=0&cityName={}&dinnerCountAttrId=&optimusCode=1&originUrl={}&page={}&partner=126&platform=1&riskLevel=1&sort=&userId=&uuid={}'
  sign = sign.format(cityname, url, page, uuid)
  return sign


'''获取_token参数'''
def getToken(brfilepath, city_code, uuid, page, cityname):
  ts = int(time.time() * 1000)
  with open(brfilepath, 'r') as f:
    brs_dict = json.load(f)
  key = random.choice(list(brs_dict.keys()))
  info = brs_dict[key]
  _token = {
        'rId': 100900,
        'ver': '1.0.6',
        'ts': ts,
        'cts': ts + random.randint(100, 120),
        'brVD': info.get('barVD'),
        'brR': [info.get('brR_one'), info.get('brR_two'), 24, 24],
        'bI': ['https://{}.meituan.com/meishi/'.format(city_code),''],
        'mT': [],
        'kT': [],
        'aT': [],
        'tT': [],
        'aM': '',
        'sign': getSIGN(cityname, page, uuid, city_code)
      }
  return base64.b64encode(zlib.compress(str(_token).encode())).decode()

OK,知道了baseURL,得到了全部参数,咱们就能够愉快地写主程序了:

'''主函数'''
def MTSpider(cityname, maxpages=50):
  data_pages = {}
  citynamesfilepath, uafilepath, uuidfilepath, brfilepath, savedatapath = initialProgram(cityname)
  base_url = 'https://{}.meituan.com/meishi/api/poi/getPoiList?'.format(cityname2CODE(cityname, citynamesfilepath))
  try:
    for page in range(1, maxpages+1):
      print('[INFO]: Getting the data of page<%s>...' % page)
      data_page = None
      while data_page is None:
        params = getGETPARAMS(cityname, page, citynamesfilepath, uuidfilepath, brfilepath)
        url = base_url + urlencode(params)
        headers = {
              'Accept': 'application/json',
              'Accept-Encoding': 'gzip, deflate, br',
              'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
              'User-Agent': getRandomUA(uafilepath),
              'Connection': 'keep-alive',
              'Host': 'bj.meituan.com',
              'Referer': 'https://{}.meituan.com/'.format(cityname2CODE(cityname, citynamesfilepath))
            }
        res = requests.get(url, headers=headers)
        data_page = parsePage(json.loads(res.text))
        if data_page is None:
          time.sleep(random.random()+random.randint(3, 6))
          initialProgram(cityname)
      data_pages.update(data_page)
      if page != maxpages:
        time.sleep(random.random()+random.randint(3, 6))
  except:
    print('[Warning]: Something wrong...')
  with open(savedatapath, 'wb') as f:
    pickle.dump(data_pages, f)

其中解析返回的json数据的函数以下:

'''解析一页数据'''
def parsePage(data_page):
  data_parse = dict()
  infos = data_page.get('data')
  if infos is None:
    return None
  else:
    infos = infos.get('poiInfos')
    for info in infos:
      # 店名: 地址, 评论数量, 平均得分, 平均价格
      data_parse[info.get('title')] = [info.get('address'), info.get('allCommentNum'), info.get('avgScore'), info.get('avgPrice')]
  return data_parse

一些细节和tricks就不细说了。

All Done!完整源代码详见主页我的介绍获取相关文件。

数据可视化

按惯例随手可视化一波,以抓取的杭州美食数据为例吧(这里只爬取了前50页),省的从新爬了。

先来搞个词云玩玩吧,用爬到的全部商家名/商家地址来搞个词云:

图片

而后咱们假设美食性价比的定义为(这个假设极可能是不合理,这里只是为了方便本身作下简单的数据分析随便假设了一下。):

性价比 = 评论数量 x 平均得分 / 平均价

因而咱们能够获得"杭州性价比最高的十家店"为(只是个小例子,不供参考,若有雷同,不胜荣幸。):

图片

OK。完整源代码详见主页中我的介绍获取相关文件。**

为了帮助学习Python进步慢的伙伴们,在这里为你们准备了丰富的学习大礼包

image

相关文章
相关标签/搜索