用python爬取豆瓣某本书的前n条书评并计算评分(star)的平均值

 这个爬虫小项目是中国大学MOOC的“用Python玩转数据”课程的一个课后作业,由南京大学张莉老师主讲,有兴趣的同学可以看一看。

虽然老师已经给出了参考代码,但由于豆瓣读书网站已经改版,参考代码中的爬取方法已经不可用,所以我将源代码稍作修改,并使之模块化,增强代码的可复用性。

爬取思路如下:

首先我们打开豆瓣读书的任何一本书的书评页,这里以《Python编程 从入门到实践(第2版)》为例。

 在页面空白处右击鼠标选择“检查”查看网页的HTML代码(我用的是Edge浏览器,其他浏览器的操作方法也应该相似),点击左上角的箭头按钮,可以查看页面中模块对应的代码位置,如下图所示:

 我们可以看到,每一条书评都放在一个列表模块<li>中,书评的内容嵌套在<span>模块中,class属性是“short”,相应的,评分(star)也放在<span>模块中,class属性是“user-stars allstar40 rating”,其中的数字40即表示评分,一颗星表示10分,四颗星就是40分。如下图:

 爬取思路是用Python的requests库获取网页的HTML,再通过BeautifulSoup库解析HTML获取相应的书评和评分信息。

我们再来看看书评页面的具体信息,发现每页只能显示20条书评,那如果我们想爬取多于20条的书评,是不是要通过爬取一个页面换一个URL的“笨方法”呢?其实也不用,通过观察网页的URL可以发现,每个页面的URL只有"start="后面的数字不同,这个数字是控制页面从第几条书评开始显示,第一页就是从第0条书评开始,第二页就是从第20条开始,我们可以把这个数字改成其他值,相应的页面就会从那一条书评开始显示。

 我们可以发现URL中还有一个参数limit,表示每一页显示的书评数,那我们可不可以把这个参数改成我们想要爬取的书评数目呢,这样我们就能在一个页面中完成爬取,但很可惜,试了一下发现不行,无论怎么改还是只能显示20条书评。

但既然每个页面的URL只有start的参数不同,我们就可以通过一个while循环(python的for循环好像只能用来遍历一个迭代器)来爬取多个页面。

为了代码的可复用性,我把这个项目分成信息获取和结果展示两个模块,分别定义两个函数getInfo(url, n) 和showRst(),函数getInfo中的两个参数表示要爬取的书评网页的URL和书评的数量。

下面是具体的代码:

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 12 17:43:45 2021

@discribe: douban_comments_spider
@author: 86150
"""

#定义爬取信息的函数,其中参数url是爬取书评首页的地址,n是爬取书评的数量
def getInfo(url, n):
    #导入要用到的第三方库
    import requests 
    from bs4 import BeautifulSoup
    import re
    import time
    count = 0 #用于记录书评数量的计数器
    i = 0 #用于控制翻页的计数器
    lis_comments = [] #用于存放书评内容的列表
    lis_stars = [] #用于存放评分的列表
    
    header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
#用于向服务器发送请求的头部信息

    while count<n:
            url_n=url.split('?')[0]+'?start='+str(i)+url.split('?')[1] #调用字符串的split()方法,以'?'为分隔符将URL分成两个字符串,中间加入'?start='+str(i)字符串组成新的URL
            r = requests.get(url_n,headers=header) #调用requests的get()方法获取页面信息
            soup = BeautifulSoup(r.text, 'lxml') #以'xml'格式解析r.text
            comments = soup.find_all('span','short') #调用soup对象的find_all方法获取具有“short”属性的所有span标签
            pattern = re.compile('<span class="user-stars allstar(.*?) rating"') #调用正则表达式库的compilere()方法,将正则表达式赋给pattern对象,(.*?)表示获取一个到多个除换行符以外的任意字符,加上?表示非贪婪匹配
            p = re.findall(pattern, r.text) #调用re的findall方法匹配HTML中的所有评分分数(star)
            for item in comments:
                lis_comments.append(item.string) #调用append方法将每一条书评添加到列表中
            for star in p:
                lis_stars.append(int(star)) #调用append方法将每一条评分添加到列表中
            count=len(lis_comments) #获取列表中的书评数目
            i+=20 #star数加上20,爬取下一个页面
            time.sleep(3) #根据豆瓣网的robot协议,每访问一个页面停留3秒钟,以免被当作恶意爬虫
    return lis_comments, lis_stars #返回书评和评分列表,便于被后面的函数调用

#定义显示结果的函数,其中参数num是想要显示的书评数量
def showRst(num):
    c,s = getInfo(url, n) #获取getInfo函数的返回值
    print("前%d条书评如下:"%n)
    for i in range(num):
        print(i+1,c[i]) #打印出每一条书评及其序号
        print('--------------------------------------------------------------')
    print("前%d条评分的平均值为:"%len(s),sum(s)/len(s))  #调用python的内部函数len()和sum(),计算评分的平均值
    
        
if __name__=="__main__": #程序的入口
    url='https://book.douban.com/subject/35196328/comments/?&limit=20&status=P&sort=new_score'
    n=100
    num=80
    getInfo(url, n) #调用getInfo函数获取前n条书评和评分,因为有些书评可能没有评分,所以评分数可能少于书评数
    showRst(num) #showRst函数显示前num条书评和评分的平均值
    

代码中用到正则表达式来匹配字符串,下面是常用的正则表达式:

 爬取结果如下:

 Life is short, I use Python.

  • 8
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

%左右%

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值