• 爬虫系列(十一) 用requests和xpath爬取豆瓣电影评论


    这篇文章,我们继续利用 requests 和 xpath 爬取豆瓣电影的短评,下面还是先贴上效果图:

    1、网页分析

    (1)翻页

    我们还是使用 Chrome 浏览器打开豆瓣电影中某一部电影的评论进行分析,这里示例为《一出好戏》

    和之前一样,我们可以通过构造 URL 获取全部网页的内容,但是这次我们尝试使用一种新的方法 —— 翻页

    使用快捷键 Ctrl+Shift+I 打开开发者工具,然后使用快捷键 Ctrl+Shift+C 打开元素选择工具

    此时用鼠标点击网页中的 后页,就会在源代码中自动定位到相应的位置

    接下来我们用 xpath 匹配下一页的链接地址:

    html.xpath('//div[@id="paginator"]/a[@class="next"]/@href')
    

    这样一来,我们只要在每一页中通过循环不断获取下一页的内容即可

    核心代码如下:

    # 获取网页源代码
    def get_page(url):
        # 构造请求头部
        headers = {
            'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
        # 发送请求,获得响应
        response = requests.get(url=url,headers=headers)
        # 获得网页源代码
        html = response.text
        # 返回网页源代码
        return html
    
    # 解析网页源代码,获取下一页链接
    def parse4link(html,base_url):
        # 初始化返回结果
        link = None
        # 构造 _Element 对象
        html_elem = etree.HTML(html)
        # 匹配下一页的链接地址,注意,它是一个相对地址
        url = html_elem.xpath('//div[@id="paginator"]/a[@class="next"]/@href')
        # 若匹配成功,则将匹配结果与初始 URL 拼接,构成完整的链接地址
        if url:
            link = base_url + url[0]
        return link
    

    (2)分析网页内容

    这一次我们需要的数据包括(这里还是使用 xpath 进行匹配):

    • 赞同人数://div[@class="comment-item"]/div[2]/h3/span[1]/span/text()
    • 评论者://div[@class="comment-item"]/div[2]/h3/span[2]/a/text()
    • 评价://div[@class="comment-item"]/div[2]/h3/span[2]/span[2]/@title
    • 评论内容://div[@class="comment-item"]/div[2]/p/span/text()

    核心代码如下:

    # 解析网页源代码,获取数据
    def parse4data(html):
        # 构造 _Element 对象
        html = etree.HTML(html)
        # 赞同人数
        agrees = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[1]/span/text()')
        # 评论作者
        authods = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/a/text()')
        # 评价
        stars = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/span[2]/@title')
        # 评论内容
        contents = html.xpath('//div[@class="comment-item"]/div[2]/p/span/text()')
    	# 获得结果
        data = zip(agrees,authods,stars,contents)
        # 返回结果
        return data
    

    (3)保存数据

    下面将数据分别保存为 txt 文件、json 文件和 csv 文件

    import json
    import csv
    # 打开文件
    def openfile(fm):
        fd = None
        if fm == 'txt':
            fd = open('douban_comment.txt','w',encoding='utf-8')
        elif fm == 'json':
            fd = open('douban_comment.json','w',encoding='utf-8')
        elif fm == 'csv':
            fd = open('douban_comment.csv','w',encoding='utf-8',newline='')
        return fd
    
    # 将数据保存到文件
    def save2file(fm,fd,data):
        if fm == 'txt':
            for item in data:
                fd.write('----------------------------------------
    ')
                fd.write('agree:' + str(item[0]) + '
    ')
                fd.write('authod:' + str(item[1]) + '
    ')
                fd.write('star:' + str(item[2]) + '
    ')
                fd.write('content:' + str(item[3]) + '
    ')
        if fm == 'json':
            temp = ('agree','authod','star','content')
            for item in data:
                json.dump(dict(zip(temp,item)),fd,ensure_ascii=False)
        if fm == 'csv':
            writer = csv.writer(fd)
            for item in data:
                writer.writerow(item)
    

    2、代码实现

    注意,本程序需要用户输入电影 ID,用于构造初始 URL ,例如:

    如果电影的链接地址为:https://movie.douban.com/subject/26985127/comments?status=P

    那么电影 ID 为:26985127

    【PS:虽然这种做法对用户不太友好,但是由于个人水平以及时间问题,目前也还没想到比较好的解决方法,

    最初的想法是让用户输入电影名称,然后由程序自动将电影名称映射为电影 ID,从而构造出初始 URL】

    import requests
    from lxml import etree
    import re
    import json
    import csv
    import time
    import random
    
    # 获取网页源代码
    def get_page(url):
        headers = {
            'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
        }
        response = requests.get(url=url,headers=headers)
        html = response.text
        return html
    
    # 解析网页源代码,获取下一页链接
    def parse4link(html,base_url):
        link = None
        html_elem = etree.HTML(html)
        url = html_elem.xpath('//div[@id="paginator"]/a[@class="next"]/@href')
        if url:
            link = base_url + url[0]
        return link
    
    # 解析网页源代码,获取数据
    def parse4data(html):
        html = etree.HTML(html)
        agrees = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[1]/span/text()')
        authods = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/a/text()')
        stars = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/span[2]/@title')
        contents = html.xpath('//div[@class="comment-item"]/div[2]/p/span/text()')
        data = zip(agrees,authods,stars,contents)
        return data
    
    # 打开文件
    def openfile(fm):
        fd = None
        if fm == 'txt':
            fd = open('douban_comment.txt','w',encoding='utf-8')
        elif fm == 'json':
            fd = open('douban_comment.json','w',encoding='utf-8')
        elif fm == 'csv':
            fd = open('douban_comment.csv','w',encoding='utf-8',newline='')
        return fd
    
    # 将数据保存到文件
    def save2file(fm,fd,data):
        if fm == 'txt':
            for item in data:
                fd.write('----------------------------------------
    ')
                fd.write('agree:' + str(item[0]) + '
    ')
                fd.write('authod:' + str(item[1]) + '
    ')
                fd.write('star:' + str(item[2]) + '
    ')
                fd.write('content:' + str(item[3]) + '
    ')
        if fm == 'json':
            temp = ('agree','authod','star','content')
            for item in data:
                json.dump(dict(zip(temp,item)),fd,ensure_ascii=False)
        if fm == 'csv':
            writer = csv.writer(fd)
            for item in data:
                writer.writerow(item)
    
    # 开始爬取网页
    def crawl():
        moveID = input('请输入电影ID:')
        while not re.match(r'd{8}',moveID):
            moveID = input('输入错误,请重新输入电影ID:')
        base_url = 'https://movie.douban.com/subject/' + moveID + '/comments'
        fm = input('请输入文件保存格式(txt、json、csv):')
        while fm!='txt' and fm!='json' and fm!='csv':
            fm = input('输入错误,请重新输入文件保存格式(txt、json、csv):')
        fd = openfile(fm)
        print('开始爬取')
        link = base_url
        while link:
            print('正在爬取 ' + str(link) + ' ......')
            html = get_page(link)
            link = parse4link(html,base_url)
            data = parse4data(html)
            save2file(fm,fd,data)
            time.sleep(random.random())
        fd.close()
        print('结束爬取')
    
    if __name__ == '__main__':
        crawl()
    

    写完之后,我们运行代码试一下效果:

    咦?好像有点怪怪的,怎么只有 11 页评论?不科学呀,《一出好戏》这部电影明明有十多万条评论的呀

    我们直接用浏览器打开最后一个链接看一下:

    原来,11 页之后的评论是需要登陆之后才有权限访问的,没办法,那就只好再写一个模拟登陆呗

    我们这里使用最最简单的方法进行模拟登陆,那就是使用 Cookie,并且是手动获取 Cookie (懒)

    简单来说,Cookie 是为了记录用户信息而储存在用户本地终端上的数据

    当我们在浏览器上登陆后,我们登陆的信息会被记录在 Cookie 中

    之后的操作,浏览器会自动在请求头中加上 Cookie,说明这是一个特定用户发送的请求

    那么怎样获取 Cookie 呢?也很简单,用浏览器打开 豆瓣电影首页 进行登陆,然后进行抓包就可以

    最后,我们只需要把 Cookie 信息复制下来,放到请求头中一起发送,这样就可以继续愉快的爬取评论啦

    【PS:注意 Cookie 的有效期,获取 Cookie 后应该尽快使用】

    【爬虫系列相关文章】

    版权声明:本博客属于个人维护博客,未经博主允许不得转载其中文章。
  • 相关阅读:
    Educational Codeforces Round 6
    Codeforces Round #373 (Div. 2)
    尺取法
    Codeforces Round #542 [Alex Lopashev Thanks-Round] (Div. 2)
    逆元(数论倒数)
    最大公约数gcd,最小公倍数lcm,扩展欧几里得
    hdu 6395 Sequence (分段矩阵快速幂)
    快速幂
    hdu 6432 Cyclic
    hdu 6397 charactor encoding
  • 原文地址:https://www.cnblogs.com/wsmrzx/p/9527087.html
Copyright © 2020-2023  润新知