《哪吒2》数据分析实践——爬虫篇(国家三等奖项目开源)

        今年与朋友一起参与了全国市场调研分析大赛并获得国家三等奖,本人在团队中主要负责《哪吒2》电影的爬虫、情感分析、情感预测、相关性分析及岭回归分析、延伸热门电影预测的模块,以此开源供大家一起学习交流。

目录

一、爬虫篇

1.b站爬虫

2.豆瓣爬虫


一、爬虫篇

        我们主要对各个媒体平台进行《哪吒2》电影的影评进行爬虫,由于我们非常看重影评的数量和质量,因此从豆瓣、b站、音符等多平台进行爬取影评信息。在爬虫部分,本人负责对豆瓣和b站的影评内容进行爬取。使用的python版本为3.11,详细的数据、库版本、代码集详见本人的github中的博客(稍后进行更新)。

1.b站爬虫

        首先找到《哪吒2》电影短评的入口:b站入口,通过观察可知,b站的评论不是以翻页的形式进行浏览,而是以滚动的形式进行浏览,因此,考虑使用selenium库进行短评内容的滚动加载。本人电脑默认的浏览器是edge,因此这里使用edge的滚动浏览(现在网络上大多关于selenium滚动加载的示例浏览器都是chrome,因此本博客对edge浏览器的用户可提供一定的参考)。

        由于要用selenium模拟浏览器滚动加载的行为,需要显示浏览器,为了方便,使用无头模式,即浏览器界面不显示,后台运行:

edge_options = Options()
edge_options.add_argument('--headless')
edge_options.add_argument('--disable-gpu')
edge_options.add_argument('--no-sandbox')
driver = webdriver.Edge(options=edge_options)

接下来进行模拟滚动,设置滚动次数为 20 次,设置每次滚动后等待的时间为 2 秒,等待页面加载新内容,循环滚动20 次(因为在此项目中不需要对一个平台收取全部的数据,原因是b站越往后的评论点赞数及浏览数不高,参考价值不太大):

try:
    driver.get(url)
    scroll_times = 20  # 假设滚动加载5次
    scroll_interval = 2  # 每次滚动间隔2秒

    # 模拟滚动加载
    for i in range(scroll_times):
        # 执行 JavaScript 代码,将页面滚动到底部
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(scroll_interval)

    # 获取网页的 HTML 内容
    html = driver.page_source
finally:
    driver.quit()  # 确保退出浏览器

return html

        然后,对想要爬取的内容进行选择,本文使用正则表达式进行爬取特定内容。首先F12进入开发者模式,目标是爬取每条评论的内容、时间、点赞数、用户名、评价星级,爬取的思路是,先找到每个评论的整个框,再运用循环依次找每个评论的内容、时间、点赞数、用户名、评价星级。先找每个评论的整个框:

由图可知,每个框的标签都是clearfix,因此锁定—找寻带有class="clearfix"的表达式。对于用户名,可以找到是在div标签下的class="review-author-name"中:

对于影评内容,在div标签中的class="review-content"里:

对于点赞数,由下图可知,要获取的是236,即先找到是隶属于div标签下的class="review-data-like"中,并且取[-1],即最后一个文本,对应的就是"236 ";只要取数字,则使用正则表达式的\d+来获取一段连续数字:

对于时间,在div标签中的class="review-author-time"里:

对于评论星级,可以从下面两张图看到,如果评了星,则所属的class为"icon-star icon-star-light",如果该星没有评,则class为"icon-star",所以,只需统计span标签下的class="review-stars"下的包含”icon-star-light“的数量即可:

        对想要爬取的内容进行选择的总代码为:

def get_movie_review_by_url(url):
    comments_dict = []

    tree = etree.HTML(get_html_edge(url))

    comment_list = tree.xpath('//li[@class="clearfix"]')
    if len(comment_list) == 0:
        return comments_dict

    for comment_div in comment_list:
        try:
            name = comment_div.xpath('.//div[contains(@class, "review-author-name")]/text()')[0].strip()
        except:
            name = ''
        try:
            content = comment_div.xpath('.//div[@class="review-content"]/text()')[0].strip()
        except:
            continue
        try:
            # 点赞数提取(通过正则获取数字)
            upvote_text = comment_div.xpath('.//div[@class="review-data-like"]/text()')[-1].strip()
            upvote = re.search(r'\d+', upvote_text).group()
        except:
            upvote = '0'
        time = comment_div.xpath('.//div[@class="review-author-time"]/text()')[0]
        try:
            # 星级计算(统计包含icon-star-light的i标签数量)
            stars = len(comment_div.xpath('.//span[@class="review-stars"]/i[contains(@class, "icon-star-light")]'))
        except:
            stars = 0

        comments_dict.append({
            'name': name,
            'content': content,
            'upvote': upvote,
            'time': time,
            'stars': stars
        })

    return comments_dict

        由于还会爬取其他平台的数据,所以对一个平台不用爬太多,主要爬一些较热门的评论,设置总评论数为400,设置爬取等待时间:t.sleep(random.uniform(1, 3)),时间最好设置一下,不然有些网站如果太频繁爬取的话可能会触发反爬机制。最后将结果保存在excel中,b站爬虫的全部代码如下:

from lxml import etree
import re
import time as t
import random
import os
import pandas as pd
from selenium import webdriver
from selenium.webdriver.edge.options import Options
import time



def get_html_edge(url):
    # 配置 Edge 使用无头浏览器
    edge_options = Options()
    edge_options.add_argument('--headless')
    edge_options.add_argument('--disable-gpu')
    edge_options.add_argument('--no-sandbox')


    driver = webdriver.Edge(options=edge_options)

    try:
        driver.get(url)
        scroll_times = 20  # 假设滚动加载5次
        scroll_interval = 2  # 每次滚动间隔2秒

        # 模拟滚动加载
        for i in range(scroll_times):
            # 执行 JavaScript 代码,将页面滚动到底部
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(scroll_interval)

        # 获取网页的 HTML 内容
        html = driver.page_source
    finally:
        driver.quit()  # 确保退出浏览器

    return html


def get_movie_review_by_url(url):
    comments_dict = []

    tree = etree.HTML(get_html_edge(url))

    comment_list = tree.xpath('//li[@class="clearfix"]')
    if len(comment_list) == 0:
        return comments_dict

    for comment_div in comment_list:
        try:
            name = comment_div.xpath('.//div[contains(@class, "review-author-name")]/text()')[0].strip()
        except:
            name = ''
        try:
            content = comment_div.xpath('.//div[@class="review-content"]/text()')[0].strip()
        except:
            continue
        try:
            # 点赞数提取(通过正则获取数字)
            upvote_text = comment_div.xpath('.//div[@class="review-data-like"]/text()')[-1].strip()
            upvote = re.search(r'\d+', upvote_text).group()
        except:
            upvote = '0'
        time = comment_div.xpath('.//div[@class="review-author-time"]/text()')[0]
        try:
            # 星级计算(统计包含icon-star-light的i标签数量)
            stars = len(comment_div.xpath('.//span[@class="review-stars"]/i[contains(@class, "icon-star-light")]'))
        except:
            stars = 0

        comments_dict.append({
            'name': name,
            'content': content,
            'upvote': upvote,
            'time': time,
            'stars': stars
        })

    return comments_dict


def get_movie_review(url):
    comments_dict = []
    max_comments = 400
    t.sleep(random.uniform(1, 3))  # 随机等待时间是0.5秒和1秒之间的一个小数
    tmp_comments_dict = get_movie_review_by_url(url)
    for comment in tmp_comments_dict:
        if len(comments_dict) < max_comments:
            comments_dict.append(comment)
        else:
            break

    print("==================影评获取完毕===================")
    print(f'共获取{len(comments_dict)}条影评')
    return comments_dict


def save_movie_review(comments_dict):
    dir_path = f'./data/nezha2'
    os.makedirs(dir_path, exist_ok=True)
    df = pd.DataFrame(comments_dict)  # 将字典转换为 DataFrame
    df.to_excel(os.path.join(dir_path, 'comments2.xlsx'), index=False)


def main():
    url = f'https://www.bilibili.com/bangumi/media/md22638046#short'
    comments_dict = get_movie_review(url)
    save_movie_review(comments_dict)



if __name__ == '__main__':
    main()

2.豆瓣爬虫

        豆瓣爬虫这部分由于做项目时时间太赶,主要参考了github上的一篇文章:豆瓣爬虫(感恩),该作者还整理了一个查询功能,可以输入电影名称进行查询,比较方便。该代码实现的整体思路与我上面写的b站爬虫差不多,都是用正则+标签进行查找。本人使用此代码时,由于直接使用作者自己的headers爬不了,所以主要就改了headers的cookies和user-agent,都是改为自己电脑上的,并且爬之前先登录了豆瓣,本人发现,如果不登陆豆瓣的话,只能爬120条,登录后即可爬取400条左右,该网站有反爬机制,所以无法再进一步获取后面的评论。对于更改headers,本人的更改方式如下:

        先在该网站的《哪吒2》界面点击F12打开开发者模式,点击网络,在筛选器筛选条件中点击文档,按F5刷新,一般点击第一个文档即可看到:

        豆瓣爬虫的完整代码如下,仅修改了cookies和user-agent,user-agent这里采用的是另一个,如果使用自己电脑上显示的不行的话,可以尝试使用这个user-agent,本人使用这个user-agent时百分之70的不可爬的都可以进行爬取,有奇效。完整的代码介绍、疑问解答可访问原作者的github,这里就不详细展开说了:

import requests
from lxml import etree
import re
import time as t
import random
import os
import json
import pandas as pd



def get_html(url):
    cookies = {
        'bid': '1gMxgR_xU5U',
        'll': '"118282"',
        'Hm_lvt_19fc7b106453f97b6a84d64302f21a04': '1733372099',
        '_ga_PRH9EWN86K': 'GS1.2.1733372100.1.0.1733372100.0.0.0',
        '_pk_id.100001.8cb4': 'b471986e3f50b77b.1733372155.',
        '_vwo_uuid_v2': 'D8DB7696A3F6AD5AF442F89BBAA685C83|ba3269f7a883157ff71737fd00d2c8c0',
        '_ga': 'GA1.1.857826835.1733372040',
        '_ga_Y4GN1R87RG': 'GS1.1.1735141304.2.1.1735141359.0.0.0',
        'dbcl2': '"224267170:eEXqtpqkCjk"',
        'push_noty_num': '0',
        'push_doumail_num': '0',
        'ck': 'N56a',
        'ap_v': '0,6.0',
        '_pk_ref.100001.8cb4': '%5B%22%22%2C%22%22%2C1742305309%2C%22https%3A%2F%2Fsearch.douban.com%2Fmovie%22%5D',
        '_pk_ses.100001.8cb4': '1',
        'frodotk_db': '"5321b30168c70291f96519043a5507b3"',
        '__yadk_uid': 'gOqlfIhhHdGTu9Q4DmnXxqLQBNZrSMFz',
        '__utma': '30149280.857826835.1733372040.1740574756.1742305312.7',
        '__utmc': '30149280',
        '__utmz': '30149280.1742305312.7.5.utmcsr=search.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/movie',
        '__utmv': '30149280.22426',
        '__utmb': '30149280.8.10.1742305312',
    }

    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    }
    return requests.get(url=url, cookies=cookies, headers=headers).text


def get_movie_id(movie_name):
    url = f'https://search.douban.com/movie/subject_search?search_text={movie_name}'

    response_text = get_html(url)
    # 使用正则表达式提取 "id" 和 "title"
    pattern = r'"id":\s*(\d+).*?"title":\s*"([^"]+)"'

    matches = re.findall(pattern, response_text)

    # 将结果转换为字典
    result = {int(match[0]): match[1].encode().decode('unicode_escape') for match in matches}

    return result


def get_movie_review_by_url(url):
    comments_dict = []

    tree = etree.HTML(get_html(url))

    comment_list = tree.xpath('//div[@class="comment-item"]')
    if len(comment_list) == 0:
        return comments_dict

    for comment_div in comment_list:
        try:
            name = comment_div.xpath('.//span[@class="comment-info"]/a/text()')[0].strip()
        except:
            name = ''
        try:
            content = comment_div.xpath('.//p[@class="comment-content"]/span/text()')[0].strip()
        except:
            continue
        upvote = comment_div.xpath('.//span[@class="votes vote-count"]/text()')[0].strip()
        time = comment_div.xpath('.//span[@class="comment-time"]/@title')[0]
        try:
            location = comment_div.xpath('.//span[@class="comment-location"]/text()')[0].strip()
        except:
            location = ''

        try:
            star_attribute = comment_div.xpath('.//span[contains(@class,"rating")]/@class')[0]
            stars = re.search(r'\d+', star_attribute).group()[0]
        except:
            stars = 0

        comments_dict.append({
            'name': name,
            'content': content,
            'upvote': upvote,
            'time': time,
            # 'location': location,
            'stars': stars
        })

    return comments_dict


def get_movie_review(movie_id):
    comments_dict = []
    page = 0
    limit = 20
    while True:
        url = f'https://movie.douban.com/subject/{movie_id}/comments?start={page}&limit={limit}&&sort=new_score&status=P'
        print(url)

        tmp_comments_dict = get_movie_review_by_url(url)
        if len(tmp_comments_dict) == 0:
            break
        comments_dict.extend(tmp_comments_dict)

        page += limit
        t.sleep(random.uniform(1, 3))  # 随机等待时间是0.5秒和1秒之间的一个小数

    print("==================影评获取完毕===================")
    print(f'共获取{len(comments_dict)}条影评,影评文件已保存在桌面')
    return comments_dict


def save_movie_review(movie_name, comments_dict):
    dir_path = f'./data/{movie_name}'
    os.makedirs(dir_path, exist_ok=True)
    df = pd.DataFrame(comments_dict)  # 将字典转换为 DataFrame
    df.to_excel(os.path.join(dir_path, 'comments2.xlsx'), index=False)


def main():
    movie_name = input("请输入电影名称:")
    movie_id_dict = get_movie_id(movie_name)

    for index, movie_id in enumerate(movie_id_dict):
        print(f"{index + 1}. {movie_id_dict[movie_id]}")

    movie_index = int(input("请输入电影ID:"))
    movie_id = list(movie_id_dict.keys())[movie_index - 1]

    comments_dict = get_movie_review(movie_id)
    save_movie_review(movie_name, comments_dict)




if __name__ == '__main__':
    main()

本次爬虫部分介绍到这,后续还会更新本人负责的其他模块,欢迎各位一起讨论交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值