【爬虫篇】基于selenium爬取美团评论
本文作者:张孟晗,中南财经政法大学统计与数学学院
本文编辑:孙一博
技术总编:王子一
Stata&Python云端课程来啦!
为了平衡团队运营成本,维系公众号的运营,也与国内动辄数千元的Stata课程缩短差距,我们的网课不得不上调价格,我们决定于2022年4月1日起调价,Python课程的价格调整为399.9元,Stata基础课程调为399.9元,Stata进阶课程调整到399.9元。大家可以告知一下身边想要购买的小伙伴,欲购从速哦,对报名有任何疑问欢迎在公众号后台和腾讯课堂留言~我们在这篇推文提供了每门课程的课程二维码,大家有需要的话可以直接扫描二维码查看课程详情并进行购买哦~
当我们想出去大吃一顿时,我们通常都会打开我们手机里的美团,进入美食一栏,搜索附近好吃的店铺。而作为衡量一个店铺好坏的重要标准便是查看店铺的评论以及评分,从中我们可以大致看出这家店铺的水准如何。那么,今天小编便带大家用selenium爬取美团的评论并做一个简单的分析。
美团的反扒的第一关便是需要登录才能够查看具体的页面信息,而如果使用request来对登录的url发送一个post请求,最大的挑战便是破解其data参数值,而这些参数值的来源不仅限于一些固定的预设值,还有一些例如token参数需要分析js才能够破解,难度极其巨大。而selenium模块是代码操纵浏览器来完成自动化的操作,因此是不需要破解这些参数的,我们只需要像正常登陆一样,传入要输入的值,便可完成登陆的操作。但需要注意的是,如今美团网页也加大了对webdriver的检测,我们使用selenium时,我们浏览器的window.navigator对象会被设置为webdriver属性,而我们需要用一段javascript代码将webdriver属性设置为空,这样我们就能够绕开检测。
下面我们来分析一下美团的登陆页面:我们需要①给手机号一栏传导手机号值②给密码一栏传导密码值③勾选我已阅读按钮④点击登录按钮。
代码如下:
#先导入所有需要用到的包
from selenium import webdriver
import time
from selenium.webdriver import ChromeOptions
from lxml import etree
import csv
import re
import pandas as pd
import numpy as np
option = ChromeOptions()
# 禁用gpu防止渲染图片
option.add_argument('disable-gpu')
# 设置不加载图片
option.add_argument('blink-settings=imagesEnabled=false')
#将webdriver属性设置为空
option.add_experimental_option("excludeSwitches",["enable-automation"])
option.add_experimental_option("useAutomationExtension", False)
option.add_argument('--disable-blink-features=AutomationControlled')
bro = webdriver.Chrome(executable_path="./chromedriver.exe",options=option)
bro.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
#最大化窗口
bro.maximize_window()
#进入美团登陆页面
bro.get("https://passport.meituan.com/account/unitivelogin")
#账号传值
bro.find_element_by_xpath('//*[@id="login-email"]').send_keys('186****3878')
#密码传值
bro.find_element_by_xpath('//*[@id="login-password"]').send_keys('121215zmh')
#点击我已阅读按钮
bro.find_element_by_xpath('//*[@id="user-agreement-wrap-text-circle"]/i').click()
#点击登录
bro.find_element_by_xpath('//*[@id="J-normal-form"]/div[5]/input[5]').click()
#防止有滑块操作,利用此等待时间可手动在页面操作
time.sleep(10)
#跳转到下一个页面,点击城市武汉(有时会出现这个选择城市的界面)
time.sleep(2)
try:
bro.find_element_by_xpath('//a[@href="//wh.meituan.com"]').click()
except:
pass
这样,我们就成功登陆上美团了
登陆成功后,我们首先得点击分类中的第一项美食,这样我们就会开启另一个窗口跳转到美团的美食专栏。这也意味着我们需要将webdriver的‘柄’切换到这个界面中,否则如果我们的‘柄’还在原来那个界面的话,是无法进行接下来的操作的,我们可以借助switch_to.window()命令来完成。同时还需要注意的一点是,进入了一家店铺后,由于评论是动态加载的,如果我们不下滑到页面底端,可能一些数据并还未加载出来,所以那部分数据就无法爬取了,我们要做的是先下滑到页面底端再进行爬取,可以借助execute_script('scrollTo(0,10000)')来完成,这样就能保证当前页所有内容都已全部加载出来了。
代码如下:
#切换窗口
time.sleep(2)
bro.switch_to.window(bro.window_handles[-1])
#划到最下面,防止页面有部分未加载出来
bro.execute_script('scrollTo(0,10000)')
#进入第一家店铺
bro.find_element_by_xpath('//*[@id="app"]/section/div/div[2]/div[2]/div[1]/ul/li[1]/div[1]/a/div[2]/img').click()
#划到最下面
time.sleep(2)
bro.execute_script('scrollTo(0,10000)')
随后,我们便可以用page_source命令来获取页面的源码数据并进行数据解析了。
从页面源代码中我们可以发现,一个页面有十条评论,均放在一个class属性为com-cont的div标签下。评论中我们可以获取用户名称,评价日期,具体评价以及所给评分,其中前三项可以根据class属性值来轻松获取。而评分的获取却大有门道,让小编带领大家分析一番。
通过对各星级的评分对比发现,各星级唯一的不同便在于style属性值的不同,style属性值内width后的数值越大,评分值越高。再经过仔细的比对后我们发现,星级乘以16.8便刚好是该数值的大小,所以我们可以根据width后数值的大小除以16.8来反推出星级。实现起来也很简单,只需要利用正则表达式提取出这部分数值,由于提取的是字符串,所以我们需要将其转化为数值类型,再作除法就大功告成啦!
最后我们将解析到的数据写入到csv文件中保存下来,代码如下:
#实例化html对象
html = etree.HTML(bro.page_source)
#获取店名、平均分及人均花费,用于保存文件的命名
name = html.xpath('//*[@id="app"]/section/div/div[2]/div[1]/div[1]/text()')[0]
avgscore = html.xpath('//*[@id="app"]/section/div/div[2]/div[1]/div[2]/p/text()[1]')[0]
avgcost = html.xpath('//*[@id="app"]/section/div/div[2]/div[1]/div[2]/p/span/text()[2]')[0]
name = name + '-' + avgscore + '-' + avgcost
#解析数据
with open(f'./{name}.csv','w',encoding='utf-8') as fp:
for page in range(100):
html = etree.HTML(bro.page_source)
for i in range(10):
dict = {}
username = html.xpath(f"//div[@class='com-cont']/div[2]/div[{i+1}]//div[@class='name']/text()")[0]
dict['username'] = username
comment = html.xpath(f"//div[@class='com-cont']/div[2]/div[{i+1}]//div[@class='desc']/text()")[0]
dict['comment'] = comment
date = html.xpath(f"//div[@class='com-cont']/div[2]/div[{i+1}]//div[@class='date']//text()")[0]
dict['date'] = date
star = html.xpath(f"//div[@class='com-cont']/div[2]/div[{i+1}]//ul[@class='stars-ul stars-light']/@style")[0]
width = re.findall('width: (.*?)px', star)[0]
star = float(width) / 16.8
dict['star'] = star
writer = csv.DictWriter(fp, fieldnames=['username', 'comment', 'date','star'])
writer.writerow(dict)
print(f'第{page+1}条爬取成功')
#翻页时间不停变化,防止时间固定被检测到
time.sleep(round(np.random.uniform(0,3),2))
#点击翻页按钮
bro.find_element_by_xpath("//span[@class='iconfont icon-btn_right']").click()
首先读入我们的爬取到的评论文件,看爬到的数据大致长什么样。
对数据的清洗首先便是去重和处理缺失值了。经过这个步骤后,我们仍然可以看到,评论中包含一些由「」所框起来的菜名,\r\n表示的换行,这部分是没有价值的,我们可以利用正则表达式将其替换清除。并且我们还发现,评论里包含了许多emoji小表情,这个也是可以借助名为emoji的库,调用其demojiz方法,将表情转换为字符串,同样借用正则表达式将其替换清除。对于过短的评论,可能是为了刷好评留下的,价值不是很大,我们也将其过滤清除,最后留下的便是清洗好的数据,我们可以用它来话画云图。
从清洗后的数据来看,效果还算不错。
代码如下:
from wordcloud import WordCloud
import numpy as np
from PIL import Image
import jieba
import emoji
#读入文件
data = pd.read_csv("F:/python/Spider/锅之恋焖锅烤鱼(江汉路店)-4.4-49.csv",header=None,names=['用户名','评价','评价日期','评分'])
comment = data[['评价']]
#数据清洗
comment = comment.dropna()
comment.drop_duplicates(keep='first',inplace=True)
comment['评价'] = comment['评价'].map(lambda x : emoji.demojize(x))
comment['评价'] = comment['评价'].map(lambda x : re.sub('【.*?】|「.*?」|\r|\n|:.*?:','',x))
comment = comment[comment['评价'].str.len() >= 5]
comment.reset_index(drop=True, inplace=True)
#绘制词云图
mask = np.array(Image.open('F:/python/daishu.jpeg'))
stopwords = set()
#添加停用词表
with open('F:/python/Spider/stopwords-master/cn_stopwords.txt',encoding='utf-8') as fp:
stop_words = [i.strip() for i in fp.readlines()]
for word in stop_words:
stopwords.add(word)
wcd = WordCloud(background_color='black',repeat=True,max_words=100,height=480,width=854,max_font_size=100,font_path="C:/Windows/Fonts/simhei.ttf",colormap="Reds",
mask=mask,mode="RGB",stopwords=stopwords)
text = " ".join(jieba.lcut("".join(comment['评价'].tolist())))
wcd.generate(text)
wcd.to_image()
从词云图上来看,大家多从味道、服务及性价比等方面来对这家店铺进行评价,我们也可以看到满意、热情、不错等正向评价的词语,说明这家店铺还是挺不错的!
对于数值型数据,我们往往使用matplotlib或者pyecharts进行可视化,小编在此使用新工具—Power BI来进行可视化。Power BI相较于前两者,使用更加简单,无需编写代码,所有的操作都是拖拽点点点来完成的,生成的图像颜色也更加绚丽多彩,但最重要的是其有着出色的动态交互性,因此广泛用于商业数据分析。
对于我们爬取到的店铺评分数据,不妨用Power BI来可视化一下吧。首先,我们将数据导入到Power BI,由于我们的评分数据只有0.5—5.0共10个等级,所以绘制一个条形图更加合适,选定了图的类型后,便可以将评分拖入到轴、图例和值中,并且把值的模式改为计数(默认是求和)。
后面要做的就是美化我们的图了,打开“油漆刷”,我们就可以改变各坐标轴标题及字体,设置柱子的颜色等等等,所有操作都是实时更改实时展现的,大家根据自己的喜好调到一个自己认为整洁美观的状态就行。
从我们绘制出的图像可以看出,1000条评论中绝大多数的评论集中在4分及以上,但一家店铺的差评或许更能反映出这家店铺的缺点所在。可以看到1分级以下的评论达到了20条,占比为2%。然而没有对比是无法对这家店铺进行评价的,因此找了一家总体评分相同的店铺,将两组数据化为簇状条形图展示如下:
通过对比我们可以发现,从近1000条评论来看,用来对比的店铺B评分1及以下的评论达到了58条,占比约6%,比我们之前提到的店铺A高出了4个百分点,这个数值还是挺大的。并且由于这两家店铺的总体评分相同,所以综合来说本推文所选的这家店铺在近期的表现情况还是挺不错的。
结语:小编只演示了如何爬取一家店铺的评论,如果要爬取多家,可以尝试在外层套一个循环,更改xpath中的语句来达到进入不同店铺的效果。最后小编还想说的是,美团的反扒真的做的很严(毕竟有那么多优秀的程序员在维护),连续爬了几面后经常会弹出额外的验证,我们可以设置一个较长的隐性等待时间,利用这段时间我们便可以手动操作来完成验证。
腾讯课堂课程二维码
对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐
双标的莱万——足球无关政治?!
Stata处理重复值:duplicates
It's time to send a flower to your lover! 2021各省GDP新鲜出炉爬虫实战-采集全国各省疫情数据
log——为你的操作保驾护航
一行代码教你玩转emoji
票房遇冷的春节档口碑冠军丨《狙击手》影评分析
学习丰县,营造良好营商环境!
大国丢娃图:从川渝到徐州!
丰县“失火”,殃及徐州:股市超跌近30亿!
Unicode转义字符——编码与解码
徐州!徐州!
B站弹幕爬虫——冬奥顶流冰墩墩&雪容融
不会用Stata做描述性统计表?so easy!
丰沛之地:备足姨妈巾
过年啦,用Python绘制一幅属于你的春联吧!
登上爬虫俱乐部“时光机” |上“机”出发 开启一段奇妙之旅【基础篇】查找并输出子字符串的定位
Stata中的小清新命令——添加观测值
PCA(主成分分析法)降维——Python实现
超好用的事件研究法
如何绘制任泽平《鼓励生育基金》的几幅图
Python 第六天——字符串
findname——想要什么找什么
Python字符串之“分分合合”
PDF转docx可批量操作?——wordconvert的小技巧
考研之后,文科生需以“do”躬“do”!
手绘五星兴家国——用Stata绘制五星红旗
Seminar丨董事会的性别多样化和企业创新:来自国际的证据Python与数据库交互——窗口函数
Stata之post命令——数据邮递爬虫俱乐部成员的Stata学习经验分享来啦!
Seminar丨2002年萨班斯·奥克斯利法案的经济后果我几乎画出了“隔壁三哥”家的国旗
Python基础——三大数字类型,你都了解吗?关于我们
微信公众号“Stata and Python数据分析”分享实用的Stata、Python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。
武汉字符串数据科技有限公司一直为广大用户提供数据采集和分析的服务工作,如果您有这方面的需求,请发邮件到statatraining@163.com,或者直接联系我们的数据中台总工程司海涛先生,电话:18203668525,wechat: super4ht。海涛先生曾长期在香港大学从事研究工作,现为知名985大学的博士生,爬虫俱乐部网络爬虫技术和正则表达式的课程负责人。
此外,欢迎大家踊跃投稿,介绍一些关于Stata和Python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。