WHCSRL 技术网

超细致通用,python爬取豆瓣游戏短评——以王者荣耀评论为例


写在前面

1.文章是用来给自己练手的,纯属自娱自乐
2.案例选的《王者荣耀》,但理论上说,适用与全部的豆瓣游戏短评爬取,只要稍微改动下爬其他的也行👌。
3.爬取的网页:https://www.douban.com/game/26655376/comments?start=0&sort=score

一、介绍

本项目利用的是简单的python爬虫技术,并没有多线程,换ip等骚操作。通过获取并解析豆瓣短评页面的html文件,拿到想要的数据,并且导出为excel文件,或者csv文件等,最后利用powerbi简单的可视化分析。


二、步骤

1.观察

1.1网页结构

请添加图片描述

打开网页,直接F12,鼠标滚动浏览,明确自己想要的数据。
只看第一条评论栏中,因为其他结构都是类似的。
豆瓣评论

令我感兴趣的就只有他的评论,评分,评价时间以及点赞人数。
在元素浏览框中,定位想要的数据,分析出大致的框架,对后面选取元素有帮助。
这里画了一个流程图帮助理解。

div 评论框
div 用户信息框
span 点赞
p 评论
昵称
评论日期
评分
手机型号

1.2网页层次逻辑

打开关于《王者荣耀》游戏短评的豆瓣评论
点开第一页评论地址为:https://www.douban.com/game/26655376/comments?start=0&sort=score
点开第二页评论地址为:https://www.douban.com/game/26655376/comments?start=20&sort=score
点开第三页评论地址为:https://www.douban.com/game/26655376/comments?start=40&sort=score

然后就是简单的评断,发现规律啦:
网页链接后面带有两个参数,第二个参数“sort”,工地英语翻译一下就知道是排序方式,一直不变,就不用管了。第一个参数“start”每增加一个页码,就多出20。
代入验证下发现,这个”start"就是页码数乘个20。只要换这个数就能得到不同页码的html文件了。


2.准备

2.1引入库

首先需要导入三个非常常用,更好用的三个库。
第一个是request,最基本获取网页的库。
第二个是解析网页的,相对于原生态,这个工具能大大简化爬取流程。
第三个就是pandas,数据预处理,导出xlst什么的非常方便。

import requests as rq
from bs4 import BeautifulSoup
import pandas as pd
  • 1
  • 2
  • 3

2.2获取html文件并且解析

豆瓣短评这里用了一点反爬的手段,需要我们构造一个请求头。
经实验发现豆瓣现在不仅仅需要’User-Agent’了,还要其他参数。
这里为了省事就直接用了自己的cookie,而且存活的时间还挺长的,需要替换成你自己的。
然后get这个网页保存到“html”变量。

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML,like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.4','Cookie':'你自己的cookie'}
html=rq.get("https://www.douban.com/game/26655376/comments?start=0&sort=score",headers=headers)
  • 1
  • 2

使用BeautifulSoup解析这个html。

  soup = BeautifulSoup(html.text,'lxml')
  • 1

用soup.contents就可以看到网页的大致内容了。
请添加图片描述


3.选取

3.1具体分析

前面已经分析过网页的基本结构了,我们选取第一个评论框。
这里用的是

soup.find_all('div','info')[0]
  • 1

第一个参数是标签名称,第二个参数根据属性筛选。
这行代码就是找到html所有带class=“info”的div标签,然后选取第一个。
也就是短评里的第一个评论框了。
请添加图片描述
至于为什么要找第一个评论框,因为我们要的数据类型都包含在里面了,后续再进一步选取的更方便点,因为整个逻辑就是获取不同页面,再获取页面的评论框,再获取框内的数据。
比对后发现:

  1. 这个2426就是点赞数了,在一个span里,包含在类别为digg的span中。
  2. 2017年7月2日,就是评论日期,在个类别为pubtime的span里。
  3. 很差,就是评分,比较特殊,是一个类别为allstar10的span的属性。
  4. p标签里的就是具体评论内容了。

按照这个逻辑,看看第二个和第三个评论框,发现其他都没有什么较大的变化,除了第三个
特殊
可以看到,allstar根据评分高低而变化。
比如allstar10就是非常差,allstar20就是比较差。
根据评分标准推测,有五个档次的,后续都要考虑进去。

3.2 操作选取需要的单个元素

soup.find_all('div','info')[0].find('span','pubtime').get_text()
  • 1

找到第一个评论框里的的第一个类别为pubtime的span并获取内容文字,得到’2017年7月2日’

soup.find_all('div','info')[0].find('span','digg').find('span').get_text()
  • 1

找到第一个评论框里的的第一个类别为digg的span,再获取嵌套的span并获取内容文字,得到*‘2426’*

soup.find('span',{'allstar50','allstar40','allstar30','allstar20','allstar10'}).get('title')
  • 1

第三个特殊处理
找到第一个评论框里的的含有类别’allstar50’,‘allstar40’,‘allstar30’,‘allstar20’,'allstar10’的span并获取元素title值,得到’很差’
方法很多,比如这里位置比较特殊,一直是第三个,也可以按位置选。
考虑到类别少,我直接全列举了。

soup.find('span','short').get_text()
  • 1

找到第一个评论框里的第一个类别为short的span并获取内容文字,得到
'周围几乎所有人都在玩,为了社交也没办法下了一个。抄袭现象太严重,不说LOL毕竟都自家的,dota2风暴守望都抄了个遍,还用历史人物命名英雄名字带歪历史人物风气。更可笑的是一个手机游戏居然还有职业联赛,看一群人低着头在那儿搓手机打比赛就觉得可笑。这种游戏火了真是中国游戏界的悲哀。’


4.页面

接下来就是页面操作了
具体逻辑:首先,一个从0到整个列表数量for循环,对每一个评论框都做一次取元素的过程,把四个元素整合成一个列表,最后把列表添加到data这个空列表里来。
后面发现,有些人没打分,或者没日期,导致报错就中断,但我想让他出错继续爬。
其中稍微进阶一点的try,except异常处理就很好的解决了这个问题。
try后面跟可能会出错的代码,except后面跟出错后怎么处理,然后继续执行。
比如第一个爬不到时间,就让time直接为‘null’,而不会中断了。

data=[]
for i in range(0,len(soup.find_all('div','info'))):
            a=soup.find_all('div','info')[i]
            try:
                time=a.find('span','pubtime').get_text()
            except:
                time='null'
            try:
                like=a.find('span','digg').find('span').get_text()
            except:
                like='null'
            try:
                score=a.find('span',{'allstar50','allstar40','allstar30','allstar20','allstar10'}).get('title')
            except:
                score='null'
            try:
                preview=a.find('span','short').get_text()
            except:
                preview='null'
            group=[time,like,score,preview]
            data.append(group)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

输出的data
请添加图片描述
这里方便看的话可以直接

pd.DataFrame(data)
  • 1

请添加图片描述


5.整合

最后一步就是整合全部的页面。
写了个方法,具体见每一步注释。

def getData(m:int):
    """
    m:需要爬取多少也页 
    返回
    """
    index=0 #正在爬取第几页
    data=[] #整个数据列表
    pddata=pd.DataFrame() #最后返回的DataFrame
    
    while index<m:#正在爬的页数小于设定值就一直循环执行
         #开始部分,构造头并解析
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.4'
              ,'Cookie':'你的cookie'}
        html=rq.get("https://www.douban.com/game/26655376/comments?start="+'start='+str(index*20)+"&sort=score",headers=headers)
        soup = BeautifulSoup(html.text,'lxml')
		#中间部分,循环获取整页需要的元素
        for i in range(0,len(soup.find_all('div','info'))):
            a=soup.find_all('div','info')[i]
            try:
                time=a.find('span','pubtime').get_text()
            except:
                time='null'
            try:
                like=a.find('span','digg').find('span').get_text()
            except:
                like='null'
            try:
                score=a.find('span',{'allstar50','allstar40','allstar30','allstar20','allstar10'}).get('title')
            except:
                score='null'
            try:
                preview=a.find('span','short').get_text()
            except:
                preview='null'
            group=[time,like,score,preview]
            data.append(group)
      	#结束部分,正在爬的页数index加一,并且打印提示一下      
        index+=1
        print('爬取到'+str(index)+'页')       
     
   	#格式化data为DataFrame,并且返回pddata  
    pddata=pd.DataFrame(data)
    return pddata
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

6.修改升级

稍微修改了一下,
m填你想爬的页数;site:https://www.douban.com/game/35236561/comments按照格式换成你想要的豆瓣游戏短评网站;path是想要导出的excel文件路径。
不出问题的话适用与全部的短评爬取。
完整的就直接放下面了,直接复制,改成你的cookie就能用了

def getDoubanShortPrewiew(m:int,site:str,path:str=''):
    """
    m:需要爬取多少也页
    site:短评网页地址
    path:导出路径,如D:/data.xlsx
    返回
    """
    index=0 #正在爬取第几页
    data=[] #整个数据列表
    pddata=pd.DataFrame() #最后返回的DataFrame
    
    while index<m:#正在爬的页数小于设定值就一直循环执行
         #开始部分,构造头并解析
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.4'
              ,'Cookie':'你的cookie'}
        html=rq.get(site+'?'+'start='+str(index*20)+"&sort=score",headers=headers)
        soup = BeautifulSoup(html.text,'lxml')
      
        #中间部分,循环获取整页需要的元素
        for i in range(0,len(soup.find_all('div','info'))):
            a=soup.find_all('div','info')[i]
            try:
                time=a.find('span','pubtime').get_text()
            except:
                time='null'
            try:
                like=a.find('span','digg').find('span').get_text()
            except:
                like='null'
            try:
                score=a.find('span',{'allstar50','allstar40','allstar30','allstar20','allstar10'}).get('title')
            except:
                score='null'
            try:
                preview=a.find('span','short').get_text()
            except:
                preview='null'
            group=[time,like,score,preview]
            data.append(group)
          #结束部分,正在爬的页数index加一,并且打印提示一下      
        index+=1
        print('爬取到'+str(index)+'页')       
    
       #格式化data为DataFrame,并且返回pddata  
    pddata=pd.DataFrame(data)
    
    if path!='':
        try:
            pddata.to_excel(path)
        except:
            print('检查路径是否正确')
    return pddata
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

请添加图片描述
如例子:英雄联盟手游
请添加图片描述


看到结尾了^-^
推荐阅读