前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >爬取24w+弹幕信息后,我果断去追剧了

爬取24w+弹幕信息后,我果断去追剧了

作者头像
数据STUDIO
发布2021-06-24 10:34:14
9670
发布2021-06-24 10:34:14
举报
文章被收录于专栏:数据STUDIO

数据获取是数据分析中的重要的一步,数据获取的途径多种多样,在这个信息爆炸的时代,数据获取的代价也是越来越小。尽管如此,仍有很多小伙伴们不清楚如何获取有用信息。本文以最近热播排行榜第一名的《流金岁月》为例子,手把手教你如何获取爱奇艺电视剧弹幕数据。

寻找弹幕信息

爱奇艺的弹幕数据是以.z形式的压缩文件存在,先通过以下步骤找到弹幕url, tvid列表,再获取压缩文件。利用工具对获取的压缩文件进行解压、处理、存储及分析。

众所周知,实行多页爬取,需要分析url规律,利用url规律循环请求并获取所需内容。

此弹幕文件url地址为 https://cmts.iqiyi.com/bullet/93/00/6024766870349300_300_1.z 其中tvid = 6024766870349300

url普适形式为 url = 'https://cmts.iqiyi.com/bullet/{}/{}/{}_300_{}.z' 其中第一个与第二个花括号内容是tvid 后3、4位,后1、2位。第三个花括号为tvid。第四个花括号为子文件序号,其不是一个无穷大的数,会根据不同的电视剧有不同的最大数。

获取弹幕文件

可以利用浏览器通过url直接请求,并获取结果。

输入网址可获取弹幕内容的压缩文件文件。

利用解压/压缩包zlib 对下载下来的压缩文件进行解压查看。

代码语言:javascript
复制
import zlib
from bs4 import BeautifulSoup
with open(r"C:\Users\HP\Downloads\6024766870349300_300_10.z", 'rb') as fin:
    content = fin.read()
btArr = bytearray(content)
xml=zlib.decompress(btArr).decode('utf-8')
bs = BeautifulSoup(xml,"xml")
bs

输出

充电时刻

zlib 官网链接[1]

  • zlib.compress可以压缩字符串或文件。
  • zlib.decompress 可以解压字符串或文件。

bytearray([source[, encoding[, errors]]])

  • 若 source 为整数,则返回一个长度为 source 的初始化数组;
  • 若 source 为字符串,则按照指定的 encoding 将字符串转换为字节序列;
  • 若 source 为可迭代类型,则元素必须为[0 ,255] 中的整数;
  • 若 source 为与 buffer 接口一致的对象,则此对象也可以被用于初始化 bytearray;
  • 若没有输入任何参数,默认就是初始化数组为0个元素。

BeautifulSoup网页解析器 借助网页的结构和属性来解析网页,如果还不清楚的小伙伴可以跳转《网络爬虫 | Beautiful Soup解析数据模块》充电。


因此只要获得tvid就能轻松获取该电视剧的弹幕文件数据。

代码语言:javascript
复制
import zlib
from bs4 import BeautifulSoup
import pandas as pd
import requests
def get_data(tv_name,tv_id):
    """
    获取每集的tvid
    :param tv_name: 集数,第1集、第2集...
    :param tv_id: 每集的tvid
    :return: DataFrame, 最终的数据
    """
    base_url = 'https://cmts.iqiyi.com/bullet/{}/{}/{}_300_{}.z'
    # 新建一个只有表头的DataFrame
    head_data = pd.DataFrame(columns=['uid','contentsId','contents','likeCount'])
    for i in range(1,20):
        url = base_url.format(tv_id[-4:-2],tv_id[-2:],tv_id,i)
        print(url)
        res = requests.get(url)
        if res.status_code == 200:
            btArr = bytearray(res.content) 
            xml=zlib.decompress(btArr).decode('utf-8') # 解压压缩文件
            bs = BeautifulSoup(xml,"xml") # BeautifulSoup网页解析
            data = pd.DataFrame(columns=['uid','contentsId','contents','likeCount'])
            data['uid'] = [i.text for i in bs.findAll('uid')]
            data['contentsId'] = [i.text for i in bs.findAll('contentId')]
            data['contents'] = [i.text for i in bs.findAll('content')]
            data['likeCount'] = [i.text for i in bs.findAll('likeCount')]
        else:
            break
        head_data = pd.concat([head_data,data],ignore_index = True)
    head_data['tv_name']= tv_name
    return head_data

获取tvid

上文已通过tvid获取到了弹幕文件数据,那么如何获取tvid又成了一个问题。莫急,我们继续分析。直接Ctrl + F 搜索 tvid

因此可以直接从返回结果中通过正则表达式获取tvid

代码语言:javascript
复制
from requests_html import HTMLSession, UserAgent
from bs4 import BeautifulSoup
import re
def get_tvid(url):
    """
    获取每集的tvid
    :param url: 请求网址
    :return: str, 每集的tvid
    """
    session = HTMLSession()   #创建HTML会话对象
    user_agent = UserAgent().random  #创建随机请求头
    header = {"User-Agent": user_agent}
    res = session.get(url, headers=header)
    res.encoding='utf-8'
    bs = BeautifulSoup(res.text,"html.parser")
    pattern =re.compile(".*?tvid.*?(\d{16}).*?") # 定义正则表达式
    text_list = bs.find_all(text=pattern) # 通过正则表达式获取内容
    for t in range(len(text_list)):
        res_list = pattern.findall(text_list[t])
        if not res_list:
            pass
        else:
            tvid = res_list[0]
    return tvid
充电时刻

Requests-HTML模块requests增强版会模拟真实浏览器向URL发送网络请求。可跳转查看《requests 扩展 | Requests-HTML(增强版)》 正则匹配 具体使用方法可参见《网络爬虫 | 正则表达式》


由此可以获得tvid。因每一集都有一个tvid,有多少集电视剧就可以获取多少个tvid。那么问题又来了:获取tvid时,是通过url发送请求,从返回结果中获取。而每一集的url又该如何获取呢。

获取每集url

通过元素选择工具定位到集数选择信息。通过selenium模拟浏览器获取动态加载信息。

有小伙伴会说,可以直接直接从返回内容中获取此href网址啊,你可以自己动手尝试下。

云朵君尝试后得到的结果是href="javascript:void(0);" ,因此解决这一问题的方法之一是运用selenium模拟浏览器获取js动态加载信息。

代码语言:javascript
复制
def get_javascript0_links(url, class_name, class_name_father, sleep_time=0.02):
    """
    Selenium模拟用户点击爬取url
    :param url: 目标页面
    :param class_name: 模拟点击的类
    :param class_name_father: 模拟点击的类,此类为class_name的父类
    :param sleep_time: 留给页面后退的时间
    :return: list, 点击class为class_name进去的超链接
    """

    def wait(locator, timeout=15):
        """等到元素加载完成"""
        WebDriverWait(driver, timeout).until(EC.presence_of_element_located(locator))

    options = Options()
#     options.add_argument("--headless")  # 无界面,若你需要查看界面内容,可以将此行注释掉
    driver = webdriver.Chrome(options=options)
    driver.get(url)

    locator = (By.CLASS_NAME, class_name)
    wait(locator)
    element = driver.find_elements_by_class_name(class_name_father)
    elements = driver.find_elements_by_class_name(class_name)
    link = []
    linkNum = len(elements)
    for j in range(len(element)):
        wait(locator)
        driver.execute_script("arguments[0].click();", element[j]) # 模拟用户点击
        for i in range(linkNum):
            print(i)
            wait(locator)
            elements = driver.find_elements_by_class_name(class_name) # 再次获取元素,预防StaleElementReferenceException
            driver.execute_script("arguments[0].click();", elements[i]) # 模拟用户点击
            time.sleep(sleep_time)
            link.append(driver.current_url)
            time.sleep(sleep_time)
            driver.back()
    driver.quit()
    return link

if __name__ == "__main__":
    url = "https://www.iqiyi.com/v_1meaw5kgh3s.html"
    class_name = "qy-episode-num"
    link = get_javascript0_links(url, class_name, class_name_father="tab-bar")
    for i, _link in enumerate(link):
        print(i, _link)
充电时刻

selenium 这里还不熟悉的小伙伴们可以查看《网络爬虫 | selenium 爬取动态加载信息》充电哟


至此,所有关键步骤已经搞定了:先通过基础url获取每集电视剧的url;再通过url发送请求并从返回信息中获取tvid,最后通过tvid获取所需弹幕信息。

主函数

接下来通过主函数将所有步骤串起。

代码语言:javascript
复制
def main(sleep_second=0.02):
    url = "https://www.iqiyi.com/v_1meaw5kgh3s.html"
    class_name = "select-item"
    class_name_father = "bar-li"
    links = get_javascript0_links(url, class_name, class_name_father)
    head_data = pd.DataFrame(columns=['tv_name','uid','contentsId','contents','likeCount'])
    for num, link in enumerate(links):
        tv_name = f"第{num+1}集"
        tv_id = get_tvid(url=link)
        data = get_data(tv_name,tv_id)
        head_data = pd.concat([head_data,data],ignore_index = True)
        time.sleep(sleep_second)
    return head_data

获取到的数据结果如下:

代码语言:javascript
复制
>>> data = main()
>>> data.info()
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 246716 entries, 0 to 246715
Data columns (total 5 columns):
 #   Column      Non-Null Count   Dtype 
---  ------      --------------   ----- 
 0   tv_name     246716 non-null  object
 1   uid         246716 non-null  object
 2   contentsId  246716 non-null  object
 3   contents    246716 non-null  object
 4   likeCount   246716 non-null  object
dtypes: object(5)
memory usage: 9.4+ MB
"""
>>> data.sample(10)

词云图

  • 先分词

运用中文分词库jieba分词,并去除停用词。

代码语言:javascript
复制
def get_cut_words(content_series):
    """
    :param content_series: 需要分词的内容
    :return: list, 点击class为class_name进去的超链接
    """
    # 读入停用词表
    import jieba 
    stop_words = [] 
    with open("stop_words.txt", 'r', encoding='utf-8') as f:
        lines = f.readlines()
        for line in lines:
            stop_words.append(line.strip())
    # 添加关键词
    my_words = ['倪妮', '刘诗诗', '锁锁', '蒋三岁', '陈道明']      
    for i in my_words:
        jieba.add_word(i) 
    # 自定义停用词
    my_stop_words = ['哈哈哈','哈哈哈哈', '真的']    
    stop_words.extend(my_stop_words)               
    # 分词
    word_num = jieba.lcut(content_series.str.cat(sep='。'), cut_all=False)
    word_num_selected = [i for i in word_num if i not in stop_words and len(i)>=2] # 条件筛选
    
    return word_num_selected
  • 后画图

运用升级版词云图库stylecloud 可视化弹幕结果。

代码语言:javascript
复制
import stylecloud
from IPython.display import Image 
text1 = get_cut_words(content_series=data.contents)
stylecloud.gen_stylecloud(text=' '.join(text1), collocations=False,
                          font_path=r'‪C:\Windows\Fonts\msyh.ttc',
                          icon_name='fas fa-rocket',size=400,
                          output_name='流金岁月-词云.png')
Image(filename='流金岁月-词云.png')
充电时刻

stylecloud stylecloud是一位数据科学家 Max Woolf基于wordcloud优化改良而成的Python包。 stylecloud 具备以下特点:

  • 为词云提供(任意大小)的图标形状(通过 Font Awesome 5.11.2 获得);
  • 支持高级调色板(通过 palettable 实现);
  • 为上述调色板提供直接梯度;
  • 支持读取文本文件,或预生成的 CSV 文件(包含单词和数字);
  • 提供命令行接口

词云图蒙板 决定词云图的颜值之一是其输出形状,控制词云图输出形状的参数为 icon_name ,其直接使用Font Awesome这个现成的方案。在stylecloud \ static的文件夹中,有一个fontawesome.min.css文件包含了巨量的图标,你可以定期到官方网站去升级这个图标库。 参数 icon_name 的取值可以通过中文网站fontawesome[2]fontawesome新[3] 选取相应的代码。

配色方案 决定词云图的颜值另一个因素是其输出结果的配色,通过参数palette 来控制。其使用的高级调色板palettable,具体取值可以到专业的配色网站palettable[4]


至此,已完成爱奇艺视频弹幕文件获取,并简单可视化。从词云图中可以看出,大家对本据好感颇佳,都是喜欢漂亮的小姐姐刘诗诗、倪妮等等。

这24w+弹幕数据远不止这么些数分结果,在此就不做延伸。不多说了,去追剧了。


参考地址

[1]

zlib官网链接: https://docs.python.org/3/library/zlib.html#zlib.compress

[2]

fontawesome: https://fontawesome.dashgame.com/

[3]

fontawesome新: https://fa5.dashgame.com/#/%E5%9B%BE%E6%A0%87

[4]

palettable: https://jiffyclub.github.io/palettable/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据STUDIO 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 寻找弹幕信息
  • 获取弹幕文件
    • 充电时刻
    • 获取tvid
      • 充电时刻
      • 获取每集url
        • 充电时刻
        • 主函数
        • 词云图
          • 充电时刻
            • 参考地址
            相关产品与服务
            腾讯云图数据可视化
            腾讯云图数据可视化(Tencent Cloud Visualization) 是一站式数据可视化展示平台,旨在帮助用户快速通过可视化图表展示大量数据,低门槛快速打造出专业大屏数据展示。精心预设多种行业模板,极致展示数据魅力。采用拖拽式自由布局,全图形化编辑,快速可视化制作。腾讯云图数据可视化支持多种数据来源配置,支持数据实时同步更新,同时基于 Web 页面渲染,可灵活投屏多种屏幕终端。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档