今天为大家重写一个美团美食板块小爬虫,说不定哪天做旅游攻略的时候也可以用下呢。废话不多说,让我们愉快地开始吧~
开发工具
Python版本:3.6.4
相关模块:
requests模块;
argparse模块;
pyquery模块;
jieba模块;
pyecharts模块;
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 平均得分 / 平均价
于是我们可以得到"杭州性价比最高的十家店"为(只是个小例子,不供参考,如有雷同,不胜荣幸。):