基于自然语言处理的微博数据抓取与分析

1,471 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

都喝“秋天的第一杯奶茶”了吗?大家对秋天的第一杯奶茶都有什么看法呢?本次我准备以“奶茶”为关键词,抓取微博的帖子内容数据,进行数据分析。

python在浏览器数据抓取和数据分析方面十分便捷。本次利用python自动化抓取,通过微博的关键词搜索,抓取微博社交平台上的发帖数据,分析某一关键词下网民的观点及行为分布,进而达到对某一热点事件建立起科学、客观的数据分析的过程。

一、scrapy爬虫框架

本次抓取数据,主要利用的技术框架是python的开源库:scrapy。

scrapy是一个用于爬取网站、提取结构性数据性能很高的的库,我们尝试通过构造请求条件url来抓取我们想要的数据,进而分析。

scrapy主要分为五大结构:

  • Scrapy Engine(引擎):负责网络和数据调度
  • Scheduler(调度器):线程调度
  • Downloader(下载器):网络请求
  • Spider(爬虫):负责获取结构化的数据
  • Pipeline(管道):处理爬虫获取到的结构化的数据

构建项目可以参照scrapy官方文档,项目结构如下:

  • scrapy.cfg: scrapy的配置文件
  • weibo/items.py:项目中的item文件
  • weibo/pipelines.py:保存、处理解析数据的文件
  • weibo/settings.py:配置请求参数的文件
  • weibo/spiders/:放置spider代码的目录
  • weibo/utils:日期格式化、字符串拼接、地址转换等常用函数
  • weibo/analysis.py:处理、分析结果的文件

二、数据抓取过程

1.在item.py中定义需要抓取的数据模型

基于数据分析的角度以及抓取的便捷程度,主要抓取的数据是发帖人的信息(用户信息、发布工具),帖子的信息(视频、图片、文本内容、发帖时间、点赞数等等)

class WeiboItem(scrapy.Item):
    # define the fields for your item here like:
    id = scrapy.Field()
    bid = scrapy.Field()
    user_id = scrapy.Field()
    screen_name = scrapy.Field()
    text = scrapy.Field()
    article_url = scrapy.Field()
    location = scrapy.Field()
    at_users = scrapy.Field()
    topics = scrapy.Field()
    reposts_count = scrapy.Field()
    comments_count = scrapy.Field()
    attitudes_count = scrapy.Field()
    created_at = scrapy.Field()
    source = scrapy.Field()
    pics = scrapy.Field()
    video_url = scrapy.Field()
    retweet_id = scrapy.Field()

2.分析微博请求结构

进入(www.s.weibo.com) ,点击高级搜索,同时观察查询字符串,可以在url中拼接相关的查询条件。

image.png

def start_requests(self):
    start_date = datetime.strptime(self.start_date, '%Y-%m-%d')
    end_date = datetime.strptime(self.end_date,
                                 '%Y-%m-%d') + timedelta(days=1)
    start_str = start_date.strftime('%Y-%m-%d') + '-0'
    end_str = end_date.strftime('%Y-%m-%d') + '-0'
    for keyword in self.keyword_list:
        if not self.settings.get('REGION') or '全部' in self.settings.get(
                'REGION'):
            base_url = 'https://s.weibo.com/weibo?q=%s' % keyword
            url = base_url + self.weibo_type
            url += self.contain_type
            url += '&timescope=custom:{}:{}'.format(start_str, end_str)
            yield scrapy.Request(url=url,
                                 callback=self.parse,
                                 meta={
                                     'base_url': base_url,
                                     'keyword': keyword
                                 })
        else:
            for region in self.regions.values():
                base_url = (
                    'https://s.weibo.com/weibo?q={}&region=custom:{}:1000'
                ).format(keyword, region['code'])
                url = base_url + self.weibo_type
                url += self.contain_type
                url += '&timescope=custom:{}:{}'.format(start_str, end_str)
                # 获取一个省的搜索结果
                yield scrapy.Request(url=url,
                                     callback=self.parse,
                                     meta={
                                         'base_url': base_url,
                                         'keyword': keyword,
                                         'province': region
                                     })

3.根据页面结构获取数据:XPath库解析结构化的html数据

def get_at_users(self, selector):
    """获取微博中@的用户昵称"""
    a_list = selector.xpath('.//a')
    at_users = ''
    at_list = []
    for a in a_list:
        if len(unquote(a.xpath('@href').extract_first())) > 14 and len(
                a.xpath('string(.)').extract_first()) > 1:
            if unquote(a.xpath('@href').extract_first())[14:] == a.xpath(
                    'string(.)').extract_first()[1:]:
                at_user = a.xpath('string(.)').extract_first()[1:]
                if at_user not in at_list:
                    at_list.append(at_user)
    if at_list:
        at_users = ','.join(at_list)
    return at_users

def get_topics(self, selector):
    """获取参与的微博话题"""
    a_list = selector.xpath('.//a')
    topics = ''
    topic_list = []
    for a in a_list:
        text = a.xpath('string(.)').extract_first()
        if len(text) > 2 and text[0] == '#' and text[-1] == '#':
            if text[1:-1] not in topic_list:
                topic_list.append(text[1:-1])
    if topic_list:
        topics = ','.join(topic_list)
    return topics

4.持久化存储数据

写入csv文件,等待后期处理

class CsvPipeline(object):
    def process_item(self, item, spider):
        base_dir = '结果文件' + os.sep + item['keyword']
        if not os.path.isdir(base_dir):
            os.makedirs(base_dir)
        file_path = base_dir + os.sep + item['keyword'] + '.csv'
        if not os.path.isfile(file_path):
            is_first_write = 1
        else:
            is_first_write = 0
        if item:
            with open(file_path, 'a', encoding='utf-8-sig', newline='') as f:
                writer = csv.writer(f)
                if is_first_write:
                    header = [
                        'id', 'bid', 'user_id', '用户昵称', '微博正文', '头条文章url',
                        '发布位置', '艾特用户', '话题', '转发数', '评论数', '点赞数', '发布时间',
                        '发布工具', '微博图片url', '微博视频url', 'retweet_id'
                    ]
                    writer.writerow(header)
                writer.writerow(
                    [item['weibo'][key] for key in item['weibo'].keys()])
        return item

注意事项:

数据获取过程不宜过快,时间间隔设置为10s为宜

三、数据分析

通过步骤二,我们获取了大概1w条的数据,现在,基于获取的数据来进行数据分析。

本次我抓取的字段主要有以下:

'id', 'bid', 'user_id', '用户昵称', '微博正文', '头条文章url',
'发布位置', '艾特用户', '话题', '转发数', '评论数', '点赞数', '发布时间',
'发布工具', '微博图片url', '微博视频url', 'retweet_id'

我准备从以下三个角度分析:发帖时间、正文词频、情感趋向。

四、发帖时间

发帖时间分析主要用到了python的图形库matplotlib以及数据处理库pandas。通过爬取的数据统计微博某词条的发帖时间分布。

1.引入相关库

from pyecharts.charts import WordCloud
from pyecharts import options as opts
import matplotlib.pyplot as plt

2.读取数据

def timedistrubution():
    data = pd.read_csv('文件')
    # 为了不影响原数据,所以拷贝一份
    data_cy = data.copy()
    hour = data_cy['发布时间'].copy()
    for index in range(len(hour)):
        # temp[index]=temp[index][11:13]
        timeArray = time.strptime(hour[index], "%Y-%m-%d %H:%M")
        hour[index] = timeArray.tm_hour
    hour = hour.values.tolist()
    myset = set(hour)
    # 直方图x轴
    xdata = []
    ydata = []
    for item in myset:
        xdata.append(str(item)+'点')
        ydata.append(hour.count(item))
    # 条形图x轴需要是一个元组
    xdata = tuple(xdata)
    # 绘制直方图
    plt.bar(xdata, ydata)
    y_ticks = np.arange(0, 3000, 200)  # 原始数据有13个点,故此处为设置从0开始,间隔为1
    plt.yticks(y_ticks)
    # 添加x轴和y轴标签
    plt.xlabel('时间点')
    plt.ylabel('条数')
    # 添加标题
    plt.title('微博发布时间统计')
    # 显示图形
    plt.show()

3.生成图表

image.png

分析可知,网友们在22~23点发言最踊跃。

五、词频和词云

词频统计主要用到了中文分词jieba库以及pandas数据处理库,同时生成词云还用到了wordCloud库。

1.jieba库介绍

jieba是优秀的第三方库,由于中文文本之间每个汉字都是连续书写的,我们需要通过特定的手段来获得其中的每个词组,这种手段叫做分词,我们可以通过jieba库来完成这个过程。

2.取出停用词

停用词就是在文本中出现的一些需要过滤的、无实际意义的字符。停用词主要分为两类,一类是中文语言中的标点符号,另一类是中文自然语言中的关联词或副词(针对、非常、非徒、间或)等等。

image.png

3.统计词频

def wordcloud():
    data = pd.read_csv('文件.csv')
    # 为了不影响原数据,所以拷贝一份
    data_cy = data.copy()
    temp = data_cy['微博正文']
    # 1.拼接所有文本
    string_data = ''
    for i in data_cy['微博正文']:
        string_data += str(i)
    # 2.文本预处理,去除各种标点符号,不然统计词频时会统计进去
    # 定义正则表达式匹配模式,其中的|代表或
    pattern = re.compile(u'\t|\n| |;|.|。|:|:.|-|:|\d|;|、|,|)|(|?|"')
    # 将符合模式的字符去除,re.sub代表替换,把符合pattern的替换为空
    string_data = re.sub(pattern, '', string_data)

    # 3.文本分词
    seg_list_exact = jieba.cut(string_data, cut_all=False)  # 精确模式分词
    object_list = []

    # 读取过滤词表
    with open('./remove_words.txt', 'r', encoding="utf-8") as fp:
        remove_words = fp.read().split()

    # 循环读出每个分词
    for word in seg_list_exact:
        # 看每个分词是否在常用词表中或结果是否为空或\xa0不间断空白符,如果不是再追加
        if word not in remove_words and word != ' ' and word != '\xa0':
            object_list.append(word)  # 分词追加到列表

    # 5.进行词频统计,使用pyecharts生成词云
    # 词频统计
    word_counts = collections.Counter(object_list)  # 对分词做词频统计
    word_counts_top_hist = word_counts.most_common(22)
    word_counts_top = word_counts.most_common(102)# 获取前100最高频的词
    # 需要去除关键词
    del word_counts_top_hist[0]
    del word_counts_top_hist[0]

    plt.rcParams['font.size'] = 8
    # 直方图x轴
    xdata = []
    ydata = []
    for item in word_counts_top_hist:
        xdata.append(item[0])
        ydata.append(item[1])
    # 条形图x轴需要是一个元组
    xdata = tuple(xdata)
    # 绘制直方图
    plt.barh(xdata, ydata)
    plt.xlabel('频数')
    plt.ylabel('词条')
    # 添加标题
    plt.title('微博词条统计图')
    # 显示图形
    plt.show()

    # 绘图
    # https://gallery.pyecharts.org/#/WordCloud/wordcloud_custom_mask_image
    # 去pyecharts官网找模板代码复制出来修改
    c = (
        WordCloud()
            .add("", word_counts_top)  # 根据词频最高的词
            .render("wordcloud.html")  # 生成页面
    )

4.生成词云和柱状图

本图为“奶茶”关键词搜索下网友发言中出现最多的词语统计直方图: image.png

本图为根据词频生成的词云图:(词语字体越大说明出现的频率越高)

image.png

五、情感分析

1.自然语言处理介绍

主要用到了百度AI开放平台的api和相关库。python本身有基于情感分析的库SnowNlp,但是需要本地大量数据的大量训练,效果不如百度AI开放平台的好。

情感分析(sentiment analysis),即自动判定文本中观点持有者对某一话题所表现出的态度或情绪倾向性的过程、技术和方法。例如对文本或句子的褒贬性做出判断。

百度AI具备的能力是针对通用场景下带有主观描述的中文文本,自动判断该文本的情感极性类别并给出相应的置信度,情感极性分为积极、消极、中性,所以我们通过自动请求AI平台api的方法,即可做出我们的分析,可以参照如下配置(ai.baidu.com/forum/topic…

训练集采用百度提供的即可。

2.调用百度AI开放平台的api

endpoint = 'https://aip.baidubce.com'
ak = ''
sk = ''
config = bce_client_configuration.BceClientConfiguration(credentials=bce_credentials.BceCredentials(ak, sk),
                                                 endpoint=endpoint)
client = ApiCenterClient(config)
data = pd.read_csv('文件.csv')
positive = []
negative = []
confidence = []
for i in data['微博正文']:
    # result.append(client.demo(i).items)
    res = client.demo(i).items

    if res is not None:
        res1 = (str(res[0]).split(","))
        positive.append(res1[0].replace("{", "").split(":")[1])
        confidence.append(res1[1].split(":")[1])
        negative.append(res1[2].replace("}", "").split(":")[1])
    else:
        positive.append("0")
        negative.append("0")
        confidence.append("0")
df = pd.DataFrame({'正向情感概率': positive, '负向情感概率': negative, '置信度': confidence})
df.to_csv('../结果文件/结果/结果.csv', index=False)

3.写入数据

image.png

可以看出,在本词条下,网友更多的是积极的、向上的、正面的态度。

六、总结与不足

本次通过构建url,使用scrapy自动化爬虫框架的方式抓取了某个时间段微博上的发帖内容,并且从三个维度分析了获得的文本数据。总体来说较为顺利,但是存在以下不足之处:

1.数据样本太少

数据总量只有1万多条,不足以建立起全面、客观的分析。

2.分析维度不够全面

算力有限,分析能力也尚欠缺,只是挖掘了最简单、浅层的数据.很多数据可以进行二次分析。

3.数据细粒度不够,仍存在大量的脏数据

4.自然语言情感分析耗时太长,调用算法可优化

致谢及参考文档:

参考代码:github.com/dataabc/wei…

参考文档:www.osgeo.cn/scrapy/intr…