爬虫专题python爬虫

基于虎嗅网的文本挖掘(Python爬虫&LDA主题模型)

2018-12-11  本文已影响68人  伪文艺boy

一、分析背景

1、1 网站选取

关于虎嗅,虽然是小众的互联网媒体,没有像擅长于用户推荐的今日头条、专注于时政的澎湃新闻那样广为人知。但其有内涵、有质量,能看到最新的消息与某些观点的深入分析,包括微信公众号,大部分会订阅虎嗅的公众号。

再者,文本重点在于展现和学习文本挖掘的思路和整体框架。至于其载体是虎嗅还是澎湃,显得或许没有那么重要了。

1.2 分析目的

1、熟悉熟悉分析流程,尝试和学习一些有意思的东西;
2、展现数据之美,体现数据的奥妙和给人带来的强烈的视觉冲击;
3、基于文本挖掘,分析虎嗅网这家网站的运营方向,专注领域,不同文章的书写手法,受欢迎文章所具备的特点等。

1.3 使用到的数据分析工具

二、前期准备

2.1 Pyspider

2.1.1Pyspider简介

Pyspider是一个非常高效、简单的框架,而且提供了一个WebUI界面。你可以在WebUI界面里编写你的爬虫代码,管理爬虫状态,查看当前调用的任务。

2.1.2 Pyspider安装及配置

节省篇幅,话不多说,链接附上:[Pyspider安装及配置]https://blog.csdn.net/qq_42336565/article/details/80697482

2.2 MongoDB

2.2.1 MongoDB简介

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

2.2.2 MongoDB安装及配置

按照下面的教程来安装:
https://jingyan.baidu.com/article/a3f121e493e592fc9052bbfe.html 需要强调的一点是:第10步:

MongoDB安装.png

这一步你要是很任性地像安装其他软件一样,选择了自定义的安装路径,或者在这一步:


MongoDB安装.png

左下角,你勾上了Install MongoDB Compass,那你有可能就玩完了。。。接下来安装进程:


MongoDB安装.png

进度条可能会卡在70%左右,一直不变,这个问题折磨了好久,老泪纵横,最后还是乖乖地默认安装路径。MongoDB还真是傲娇得很呐!

三、数据获取及预处理

3.1数据爬取

鉴于虎嗅网主页是主编精挑细选出来的,很据代表性,能反映虎嗅网的整体状况,本文使用 Pyspider 抓取了来自[虎嗅网] https://www.huxiu.com/的主页文章。

3.1.1 使用PyCharm

照常,我们用PyCharm来做,检查虎嗅原网页:


HuXiu.png

设置服务器代理

def get_one_page(my_headers,url):
    randdom_header = random.choice(my_headers)
    req = urllib.request.Request(url)
    req.add_header("User-Agent", randdom_header)
    req.add_header("GET", url)
    response = urllib.request.urlopen(req)
    return  response
#代理服务器
my_headers = [
        "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14",
        "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)"
    ]

获取原网页

def get_href(html):
    pattern = re.compile('<div class="mod-b mod-art clearfix "'
                         '.*?"transition"  href="(.*?)"'
                         '.*?</div>', re.S)
    items =re.findall(pattern, html)
    return items

使用正则表达式,解析原网页

def parse_one_page(href):
    pattern = re.compile('<div class="article-wrap">'
                         '.*?class="t-h1">(.*?)</h1>'
                         '.*?article-time pull-left">(.*?)</span>'
                         '.*?article-share pull-left">(.*?)</span>'
                         '.*?article-pl pull-left">(.*?)</span>'
                       #  '.*?text-remarks.*?</p><p><br/></p><p>(.*?)<!--.*?认证-->'
                         '.*?author-name.*?<a href=".*?" target="_blank">(.*?)</a>'
                         '.*?author-one">(.*?)</div>'
                         '.*?author-article-pl.*?target="_blank">(.*?)</a></li>'
                         '.*?</div>', re.S)

将获得的参数值转化成键值对

items =re.findall(pattern, href)
  for item in items:
      yield {
          'title': item[0].strip(),
          'time': item[1],
          'share': item[2][2:],
          'recoment': item[3][2:],
       #   'content': re.compile(r'<[^>]+>',re.S).sub('',item[4]).strip(),
          'anthor': item[4].strip(),
          'intro': item[5],
          'passNum': item[6]
      }

循环遍历,抓取第一页所有文章

for i in range(len(url_html)):
        url_ord = "https://www.huxiu.com" + url_html[i]
        ord_text = get_one_page(my_headers, url_ord).read().decode('utf-8')
        for item in parse_one_page(ord_text):
            print(item)
            write_to_file(item)

保存到文件text.txt中

def write_to_file(content):
    with open('text.txt','a',encoding='utf-8') as f:
        f.write(json.dumps(content, ensure_ascii=False)+'\n')
        f.close()

爬取结果:

{"title": "裁员凶猛", "time": "2018-12-10 20:35", "share": "3", "recoment": "2", "anthor": "华夏时报©", "intro": "", "passNum": "54篇文章"}
{"title": "新东方烹饪学校都要上市了,你竟然还看不起职校", "time": "2018-12-10 20:31", "share": "3", "recoment": "0", "anthor": "敲敲格", "intro": "空山无人", "passNum": "150篇文章"}
{"title": "互联网太可怕,我还是做回煤老板吧", "time": "2018-12-10 20:22", "share": "6", "recoment": "2", "anthor": "故事FM©", "intro": "", "passNum": "16篇文章"}
{"title": "《海王》,大型“海底捞”现场", "time": "2018-12-10 20:01", "share": "3", "recoment": "2", "anthor": "mrpuppybunny", "intro": "", "passNum": "316篇文章"}
{"title": "从成龙说起:这届中国男明星不行", "time": "2018-12-10 19:51", "share": "10", "recoment": "2", "anthor": "腾讯《大家》©", "intro": "精选大家文章,畅享阅读时光。", "passNum": "119篇文章"}
{"title": "互联网寒冬,不是深渊,而是阶梯", "time": "2018-12-10 19:36", "share": "14", "recoment": "2", "anthor": "瞎说职场©", "intro": "", "passNum": "8篇文章"}
{"title": "【虎嗅晚报】和山寨Supreme合作?三星:这是意大利Supreme", "time": "2018-12-10 19:29", "share": "1", "recoment": "0", "anthor": "敲敲格", "intro": "空山无人", "passNum": "150篇文章"}
{"title": "老干妈:不上市的底气与逻辑", "time": "2018-12-10 18:28", "share": "9", "recoment": "6", "anthor": "中国经济信息杂志©", "intro": "信息改变生存质量", "passNum": "5篇文章"}
{"title": "2018,一文看尽AI发展真相", "time": "2018-12-10 18:16", "share": "18", "recoment": "0", "anthor": "新智元", "intro": "人工智能全产业平台", "passNum": "52篇文章"}
{"title": "《任天堂明星大乱斗:特别版》:我们都爱“大杂烩”", "time": "2018-12-10 17:54", "share": "1", "recoment": "1", "anthor": "我不叫塞尔达", "intro": "", "passNum": "81篇文章"}
{"title": "罗玉凤不认命", "time": "2018-12-10 17:46", "share": "25", "recoment": "12", "anthor": "盖饭人物ThePeople©", "intro": "冷眼看人间,心如火焰。", "passNum": "3篇文章"}
{"title": "星巴克被骂上热搜,新会员体系把老用户都气哭了", "time": "2018-12-10 16:40", "share": "10", "recoment": "9", "anthor": "运营研究社", "intro": "", "passNum": "13篇文章"}
{"title": "雀巢紧急召回一批问题奶粉,可致婴儿恶心呕吐", "time": "2018-12-10 16:36", "share": "5", "recoment": "0", "anthor": "每日经济新闻©", "intro": "", "passNum": "78篇文章"}
{"title": "8012年了,我们为什么还沉迷于“捏脸”游戏?", "time": "2018-12-10 16:27", "share": "9", "recoment": "3", "anthor": "看理想©", "intro": "“看理想”诞生于知名出版品牌“...", "passNum": "57篇文章"}
{"title": "广州三重奏:认识中国“南方”的一个视角", "time": "2018-12-10 16:25", "share": "21", "recoment": "0", "anthor": "东方历史评论©", "intro": "《东方历史评论》杂志官方微信账...", "passNum": "3篇文章"}
{"title": "得了癌症,怎么告诉孩子", "time": "2018-12-10 16:00", "share": "19", "recoment": "1", "anthor": "谢熊猫君", "intro": "", "passNum": "7篇文章"}
{"title": "从校园到职场:什么是职场经验", "time": "2018-12-10 15:43", "share": "38", "recoment": "5", "anthor": "caoz的梦呓©", "intro": "", "passNum": "70篇文章"}
{"title": "我们不禁要问:这些怪物在科学的地图周围在做什么呢?", "time": "2018-12-10 15:10", "share": "15", "recoment": "1", "anthor": "一席©", "intro": "", "passNum": "37篇文章"}
{"title": "一边卖命,一边求生,300万中国底层现状", "time": "2018-12-10 14:36", "share": "31", "recoment": "6", "anthor": "一条©", "intro": "每天一条原创短视频,每天讲述一...", "passNum": "1篇文章"}
{"title": "像拍《海王》一样拍《西游记》,会是什么样?", "time": "2018-12-10 14:30", "share": "13", "recoment": "10", "anthor": "壹条电影©", "intro": "", "passNum": "4篇文章"}
{"title": "DC翻身作《海王》,其实是一部环保教育宣传片", "time": "2018-12-10 14:00", "share": "6", "recoment": "8", "anthor": "PingWest品玩©", "intro": "有品好玩的科技,一切与你有关", "passNum": "71篇文章"}
{"title": "平台&大媒体都在输血本地新闻,  这样的合作模式真的可持续吗?", "time": "2018-12-10 14:00", "share": "8", "recoment": "2", "anthor": "全媒派©", "intro": "", "passNum": "78篇文章"}

到目前为止,第一页的文章已经处理掉了,本以为一切自然一帆风顺的,万事皆大欢喜,不就多爬几页嘛,一个循环不就得了。事实证明我想得简单了。
当点击第一页下面这个“加载更多”时,发现其经过了JavaScript渲染。


HuXiu.png

分析该请求的方式和地址,包括参数,如下图所示:


HuXiu.png

得到以下信息:

  1. 页面请求地址为:https://www.huxiu.com/v2_action/article_list
  2. 请求方式:POST
  3. 请求参数比较重要的是一个叫做page的参数

3.1.2 使用PySpider爬取动态加载页面

on_start 函数内部编写循环事件,我们本次爬取2000页;

@every(minutes=24 * 60)
    def on_start(self):
        for page in range(1,2000):
            print("正在爬取第 {} 页".format(page))
            self.crawl('https://www.huxiu.com/v2_action/article_list', method="POST",data={"page":page},callback=self.parse_page,validate_cert=False)

页面生成完毕之后,开始调用parse_page 函数,用来解析 crawl() 方法爬取 URL 成功后返回的 Response 响应。

def parse_page(self, response):
        content = response.json["data"]
        doc = pq(content)
        lis = doc('.mod-art').items()
        data = [{
           'title': item('.msubstr-row2').text(),
           'url':'https://www.huxiu.com'+ str(item('.msubstr-row2').attr('href')),
           'name': item('.author-name').text(),
           'write_time':item('.time').text(),
           'comment':item('.icon-cmt+ em').text(),
           'favorites':item('.icon-fvr+ em').text(),
           'abstract':item('.mob-sub').text()
           } for item in lis ] 
        return data

最后,定义一个 on_result() 方法,该方法专门用来获取 return 的结果数据。这里用来接收上面 parse_page() 返回的 data 数据,在该方法可以将数据保存到 MongoDB 中。

def on_result(self, result):
        if result:
            self.save_to_mongo(result)
    
    def save_to_mongo(self, result):
        df = pd.DataFrame(result)
        content = json.loads(df.T.to_json()).values()
        if mongo_collection.insert_many(content):
            print('存储到mongodb成功')
            sleep = np.random.randint(1,5)
            time.sleep(sleep)

pyspider 以 URL的 MD5 值作为 唯一 ID 编号,ID 编号相同,就视为同一个任务, 不会再重复爬取。
GET 请求的分页URL 一般不同,所以 ID 编号会不同,能够爬取多页。
POST 请求的URL是相同的,爬取第一页之后,后面的页数便不会再爬取。
为了爬取第2页及之后,重新写下 ID 编号的生成方式,在 on_start() 方法前面添加下面代码:

def get_taskid(self,task):
        return md5string(task['url']+json.dumps(task['fetch'].get('data','')))

数据保存到 了MongoDB 中:


MongoDB.png

共计2000页, 28222篇文章。抓取 了 7 个字段信息:文章标题、作者、发文时间、评论数、收藏数、摘要和文章链接。

3.2 数据清洗

首先,我们需要从 MongoDB 中读取数据,并转换为 DataFrame。

client = pymongo.MongoClient(host='localhost', port=27017)
db = client['Huxiu']
collection = db['News']
# 将数据库数据转为dataFrame
data = pd.DataFrame(list(collection.find()))

下面我们看一下数据的行数和列数,整体情况及数据的前五行。

#查看行数和列数
print(data.shape)
#查看总体情况
print(data.info())

结果:

(28222, 8)
RangeIndex: 28222 entries, 0 to 28221
Data columns (total 8 columns):
_id           28222 non-null object
abstract      28222 non-null object
comment       28222 non-null object
favorites     28222 non-null object
name          28222 non-null object
title         28222 non-null object
url           28222 non-null object
write_time    28222 non-null object

可以看到数据的维度是 28222行 × 8 列。发现多了一列无用的 _id 需删除,同时 name 列有一些特殊符号,比如© 需删除。另外,数据格式全部为 Object 字符串格式,需要将 comment 和 favorites 两列更改为数值格式、 write_time 列更改为日期格式。

# 删除无用的_id列
data.drop(['_id'], axis=1, inplace=True)
# 删除特殊符号@
data['name'].replace('@','',inplace=True,regex=True)
data_duplicated = data.duplicated().value_counts()
# 将数据列改为数值列
data = data.apply(pd.to_numeric, errors='ignore')
# 修改时间,并转换为datetime格式
data['write_time'] = pd.to_datetime(data['write_time'])
data = data.reset_index(drop=True)

下面,我们看一下数据是否有重复,如果有,那么需要删除。

# 删除重复值
data = data.drop_duplicates(keep='first')

我们再增加两列数据,一列是文章标题长度列,一列是年份列,便于后面进行分析

# 增加标题长度列
data['title_length'] = data['title'].apply(len)
# 年份列
data['year'] = data['write_time'].dt.year

以上,就完成了基本的数据清洗处理过程,针对这 9 列数据开始进行分析。

四、数据统计分析

4.1 整体情况

先来看一下总体情况:

print(data.describe())

结果:

            comment     favorites  title_length          year
count  27236.000000  27236.000000  27236.000000  27236.000000
mean       9.030988     40.480761     23.010501   2016.382288
std       14.912655     52.381115      8.376050      1.516007
min        0.000000      0.000000      3.000000   2012.000000
25%        3.000000     12.000000     17.000000   2016.000000
50%        6.000000     24.000000     23.000000   2017.000000
75%       11.000000     48.000000     28.000000   2017.000000
max      914.000000    787.000000    124.000000   2018.000000

使用了 data.describe() 方法对数值型变量进行统计分析。从上面可以简要得出以下几个结论:

print(data['name'].describe())
print(data['write_time'].describe())

结果:

count     27236
unique     3334
top          虎嗅
freq       2289
count                   27236
unique                   1390
top       2017-04-25 00:00:00
freq                       44
first     2012-06-27 00:00:00
last      2018-10-20 00:00:00

unique 表示唯一值数量,top 表示出现次数最多的变量,freq 表示该变量出现的次数,所以可以简单得出以下几个结论:

4.2 虎嗅网文章发布数量变化

def analysis1(data):

    data.set_index(data['write_time'], inplace=True)
    data = data.resample('Q').count()['name'] # 以季度汇总
    data = data.to_period('Q')

    # 创建x,y轴标签
    x = np.arange(0, len(data), 1)
    axl.plot(x, data.values,
        color = color_line,
        marker = 'o', markersize = 4
        )
    axl.set_xticks(x) # 设置x轴标签为自然数序列
    axl.set_xticklabels(data.index) # 更改x轴标签值为年份
    plt.xticks(rotation=90) # 旋转90度,不至于太拥挤

    for x,y in zip(x,data.values):
        plt.text(x,y + 10, '%.0f' %y,ha = 'center', color = colors, fontsize=fontsize_text)
    # 设置标题及横纵坐标轴标题
    plt.title('虎嗅网文章数量发布变化(2012-2018)', color = colors, fontsize=fontsize_title)
    plt.xlabel('时期')
    plt.ylabel('文章(篇)')
    plt.tight_layout() # 自动控制空白边缘
    plt.savefig('虎嗅网文章数量发布变化.png', dip=200)
    plt.show()

结果:


虎嗅网文章发布数量变化.png

可以看到 ,以季度为时间尺度的 6 年间,12年-15年发文数量比较稳定,大概在400篇左右。但2016 年之后文章开始增加到 2000 篇以上,可能跟虎嗅网于2015年2月上市有关。首尾两个季度日期不全,所以数量比较少。

4.3 文章收藏量TOP10

几万篇文章里,到底哪些文章写得比较好或者比较火?

top = data.sort_values('favorites', ascending=False)
    top.index=(range(1,len(top.index)+1))
    print(top[:10][['title','favorites','comment']])

结果:

                             title  favorites  comment
1                        货币如水,覆水难收        787       39
2                            自杀经济学        781      119
3   2016年已经起飞的5只黑天鹅,都在罗振宇这份跨年演讲全文里        774       39
4               真正强大的商业分析能力是怎样炼成的?        747       18
5                        藏在县城的万亿生意        718       35
6                           腾讯没有梦想        707       32
7               段永平连答53问,核心是“不为清单”        706       27
8                          王健林的滑铁卢        703       92
9                           7-11不死        691       17
10            游戏策划人士:为什么我的儿子不沉迷游戏?        644       33

发现两个有意思的地方:第一,文章标题都比较短小精炼。第二,文章收藏量虽然比较高,但评论数都不多,猜测这是因为——大家都喜欢做伸手党?

4.4 历年TOP3文章收藏比较

在了解文章的总体排名之后,我们来看看历年的文章排名是怎样的。这里,每年选取了收藏量最多的 3 篇文章。

def analysis2(data):
    def topn(data):
        top = data.sort_values('favorites', ascending=False)
        return top[:3]

    data = data.groupby(by=['year']).apply(topn)
    print(data[['title', 'favorites']])

    # 增加每年top123列,列依次值为1、2、3
    data['add'] = 1 # 辅助
    data['top'] = data.groupby(by='year')['add'].cumsum()

    data_reshape = data.pivot_table(index='year', columns='top', values='favorites').reset_index()
    print(data_reshape)
    data_reshape.plot(
        y = [1,2,3],
        kind = 'bar',
        width = 0.3,
        color = ['#1362A3', '#3297EA', '#8EC6F5']
        )
    # 添加x轴标签
    years = data['year'].unique()
    plt.xticks(list(range(7)), years)
    plt.xlabel('Year')
    plt.ylabel('文章收藏数量')
    plt.title('历年TOP3文章收藏比较', color = colors, fontsize = fontsize_title)
    plt.tight_layout()
    plt.savefig('历年TOP3文章收藏比较.png', dpi=200)
    plt.show()

结果:


历年TOP3文章收藏比较.png

可以看到,文章收藏量是逐年递增的。

year                                                title                 favorites
2012 3199                                   怎么做难做的本地生活服务?        106
     2236                                     经营微博的十个经典案例        101
     3449                          《大数据时代》,一场生活、工作与思维的大变革         91
2013 3795                                情色网站启示:请尊重你不懂的领域        246
     235                                周鸿祎的一部分,由他读的这些书构成        245
     4071   如果你不想老被思考的陷阱绊倒,建议熟读《清醒思考的艺术:你最好让别人去犯的52种思维错误》
      221
2014 660                           阿里巴巴怎么看O2O对商业生态的破局与重构?        443
     2413     别等中文译本了,Peter Thiel《Zero to One》中的13条逆向创业观点        308
     2563                              这是迄今还原得最完整的——“雷军系”        278
2015 4107                   看了这套PPT,你就知道房地产商将要如何玩转社区O2O的啦        452
     3298                           不要再徒手创业了,这些好用的工具软件请拿走        372
     4373                      非常赞的文章!告诉你一个你知其然却不知其所以然的硅谷        357
2016 418                       蝗虫般的刷客大军:手握千万手机号,分秒间薅干一家平台        554
     12618                            准CEO必读的这20本书,你读过几本?        548
     17225                        运营简史:一文读懂互联网运营的20年发展与演变        505
2017 21898                 2016年已经起飞的5只黑天鹅,都在罗振宇这份跨年演讲全文里        774
     144                               真正强大的商业分析能力是怎样炼成的?        747
     11471                                        王健林的滑铁卢        703
2018 19686                                      货币如水,覆水难收        787
     19230                                          自杀经济学        781
     16362                                      藏在县城的万亿生意        718

可以看到标题起地都蛮有水准的。关于标题的重要性,有这样通俗的说法:「一篇好文章,标题占一半」,一个好的标题可以大大增强文章的传播力和吸引力。文章标题虽只有短短数十字,但要想起好,里面也是很有很多技巧的。

4.5 发文数量最多的TOP20作者

上面,我们从收藏量指标进行了分析,下面,我们关注一下发布文章的作者(个人/媒体)。前面提到发文最多的是虎嗅官方,有一万多篇文章,这里我们筛除官媒,看看还有哪些比较高产的作者。

def analysis3(data):
    data = data.groupby(data['name'])['title'].count()
    data = data.sort_values(ascending=False)
    print(data)

    # pandas 直接绘制,invert_yaxis()颠倒顺序
    data[1:21].plot(kind='barh',color=color_line).invert_yaxis()

    for y,x in enumerate(list(data[1:21].values)):
        plt.text(x+12,y+0.2,'%s' %round(x,1),ha='center',color=colors)
    plt.xlabel('文章数量')
    plt.ylabel('作者')
    plt.title('发文数量最多的TOP20作者', color = colors, fontsize=fontsize_title)
    
    plt.tight_layout()
    plt.savefig('发文数量最多的TOP20作者.png',dpi=200)
    plt.show()

结果:


发文数量最多的TOP20作者.png

可以看到,前 20 名作者的发文量差距都不太大。发文比较多的有「娱乐资本论」、「发条橙子」、「界面」、「新浪科技」这类媒体号;也有虎嗅官网团队的作者:张博文、周超臣等;还有部分独立作者:假装FBI等。可以尝试关注一下这些高产作者。

4.6 文章评论数与收藏量的关系

def analysis6(data):
    plt.scatter(data['favorites'], data['comment'], s=8, color='#1362A3')
    plt.xlabel('文章收藏量')
    plt.ylabel('文章评论数')
    plt.title('文章评论数与收藏量关系', color = colors, fontsize=fontsize_title)
    plt.tight_layout()
    plt.savefig('文章评论数与收藏量关系.png', dpi=200)
    plt.show()

结果:


文章评论数与收藏量的关系.png

可以看到,大多数点都位于左下角,意味着这些文章收藏量和评论数都比较低。但也存在少部分位于上方和右侧的异常值,表明这些文章呈现 「多评论、少收藏」或者「少评论、多收藏」的特点。

4.7 文章收藏量与标题长度关系

def analysis7(data):
    plt.scatter(
        x=data['favorites'],
        y=data['title_length'],
        s=8,
        )
    plt.xlabel('文章收藏量')
    plt.ylabel('文章标题长度')
    plt.title('文章收藏量和标题长度关系', color = colors, fontsize=fontsize_title)
    plt.tight_layout()
    plt.savefig('文章收藏量和标题长度关系.png', dpi=200)
    plt.show()

结果:


文章收藏量与标题长度关系.png

大致可以看出两点现象:

4.8 三分之一以上文章的标题喜欢使用问号

def analysis10(data):
    data1 = data[data['title'].str.contains("(.*\?.*)|(.*\?.*)")]
    data2 = data[data['title'].str.contains("(.*\!.*)|(.*\!.*)")]

    # 带有问号的标题数量
    quantity1 = data1.shape[0]
    # 带有叹号的标题数量
    quantity2 = data2.shape[0]
    # 剩余数量
    quantity = data.shape[0] - data1.shape[0] - data2.shape[0]

    sizes = [quantity2,quantity1,quantity]
    labels = [u'叹号标题',u'问号标题',u'陈述性标题']
    colors_pie = ['#1362A3','#3297EA','#8EC6F5'] #每块颜色定义
    explode = [0,0.05,0]
    plt.pie(
        sizes,
        autopct='%.1f%%',
        labels= labels,
        colors =colors_pie,
        shadow = False, #无阴影设置
        startangle =90, #逆时针起始角度设置
        explode = explode,
        # textprops={'fontsize': 14, 'color': 'w'} # 设置文字颜色
        textprops={'fontsize': 12, 'color': 'w'} # 设置文字颜色
        )
    plt.title('一半以上的文章的标题喜欢用问号',color=colors,fontsize=fontsize_title)

    plt.axis('equal')

    plt.axis('off')
    plt.legend(loc = 'upper right')
    plt.tight_layout()  # 自动控制空白边缘,以全部显示x轴名称
    plt.savefig('title问号.png',dpi=200)
    plt.show()

结果:


三分之一以上文章的标题喜欢使用问号.png

五、绘制词云图

我们通过绘制2013-2018年词云图来说明来探究每年的热词和热点事件。其中,分词函数用的是王师兄推荐的jieba分词。

def analysis9(data):
    jieba.load_userdict("userdict.txt")
    
    text=''
    for i in data['title'].values:
    # for i in data[data.year == 2018]['title'].values:
        # 替换无用字符
        symbol_to_replace = '[!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
        # data['name'].str.replace(symbol_to_replace,'',inplace=True,regex=True)
        i = re.sub(symbol_to_replace,'',i)
        # print(i)
        text+=' '.join(jieba.cut(i,cut_all=False))

    # text = jieba.del_word('如何')
    d = path.dirname(__file__) if "__file__" in locals() else os.getcwd()

    background_Image = np.array(Image.open(path.join(d, "tiger.jpg")))
    # background_Image = plt.imread('./tiger.jpg')

    font_path = 'C:\Windows\Fonts\simhei.ttf'  # 思源黑,黑体simhei.ttf
    # 添加stopswords
    stopwords = set()
    # 先运行对text进行词频统计再排序,再选择要增加的停用词
    stopwords.update(['如何','怎么','一个','什么','为什么','还是','我们','为何','可能','不是','没有','哪些','成为','可以','背后','到底','就是','这么','不要','怎样','为了','能否','你们','还有','这样','这个','真的','那些'])

    wc = WordCloud(
        # background_color = '#3F3F3F',
        # background_color = 'white',
        background_color = 'black',
        font_path = font_path,
        mask = background_Image,
        stopwords = stopwords,
        max_words = 200,
        # width = 1000,height=600,
        margin =2,
        max_font_size = 100,
        random_state = 42,
        scale = 2,
        # colormap = 'viridis'
    )
    wc.generate_from_text(text)

    process_word = WordCloud.process_text(wc, text)
    # 下面是字典排序
    sort = sorted(process_word.items(),key=lambda e:e[1],reverse=True) # sort为list
    print(sort[:50])  # 输出前词频最高的前50个,然后筛选出不需要的stopwords,添加到前面的stopwords.update()方法中
    img_colors = ImageColorGenerator(background_Image)
    wc.recolor(color_func=img_colors)  # 颜色跟随图片颜色

    plt.imshow(wc,interpolation='bilinear')
    plt.axis('off')
    plt.tight_layout()  # 自动控制空白边缘,以全部显示x轴名称
    plt.savefig('huxiu5.png',dpi=200)
    plt.show()

结果:
2018年:


2018.png

2017年:


2017.png
2016年:
2016.png
2015年:
2015.png

2014年:


2014.png
2013年:
2013.png

可以看到每年的关键词都有一些相同之处,但也不同的地方:

六、文本挖掘(主题模型)

6.1 理论

在上面,我们用到了词云图,词云图是根据词在文档中出现的频数来决定和绘制的,这很直观地显示出了每年最火、最热、使用率最高的词。但是,词云图并不能分析和描绘文档和文档之间的相互关系,更不能探究文字间的潜在语义信息,而LDA很多地弥补和改善了诸多缺点。

主题模型的具体理论是很复杂的,我们暂且可以把他当做一个黑箱。了解参数,实现算法,得到我们想要的东西。主题模型在计算机科学和数据科学的学术讲座中,讲者在介绍到LDA时,都往往会把原理这部分直接跳过去。就像我们不需要把汽车发动机原理搞清楚就可以开车一样,我们同样可以使用LDA主题词模型来提取主题词。当然,你也可以参考这篇文章:[通俗理解LDA主题模型]https://blog.csdn.net/yhao2014/article/details/51098037

6.2 实现

在实现主题模型之前,本文利用R语言中的segmentCN中文分词函数之后去除了停止词。当然这里同样可以使用Python中的jieba去除停用词。

停用词是指在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为Stop Words(停用词)。这些停用词都是人工输入、非自动化生成的,生成后的停用词会形成一个停用词表。

本文用到的停用词库中含有1959个常用停止词,
这里给出云盘链接,需要者自取:https://pan.baidu.com/s/1j7hxe6uFP_rUiHNxsKnkqw
提取码:au37

## 一、数据预处理(初始数据整理)
## 1.4读取资料库
setwd("G:\\课程\\研一\\回归分析")
#clipboard指的是HuXiu.txt用notepad打开后复制,防止中文乱码
csv <- read.table("clipboard",header=T, stringsAsFactors=F,quote = "",encoding="utf-8")
mystopwords<-unlist(read.table("StopWords.txt",stringsAsFactors=F,quote = ""))
###解决乱码问题
head(csv)
dim(csv)
colnames(csv)<-c("text")
## 1.5.数据预处理(中文分词、stopword处理)
#install.packages("tm",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("Rcpp",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("slam",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("xml2",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("rJava",lib="G:\\R语言\\R语言学习\\安装包")
library(xml2,lib="G:\\R语言\\R语言学习\\安装包")
library(Rcpp,lib="G:\\R语言\\R语言学习\\安装包")
library(slam,lib="G:\\R语言\\R语言学习\\安装包")
library(NLP,lib="G:\\R语言\\R语言学习\\安装包")
library(tm,lib="G:\\R语言\\R语言学习\\安装包")
#只有RJava配置成功了,Rwordseg安装才可能成功,前者是后者的依赖包
#install.packages("rJava",lib="G:\\R语言\\R语言学习\\安装包")
library(rJava,lib="G:\\R语言\\R语言学习\\安装包")
#手动下载安装包Rwordseg,然后本地安装
library(Rwordseg,lib="G:\\R语言\\R语言学习\\安装包")
#(1)移除数字函数
removeNumbers = function(x) { ret = gsub("[0-90123456789]","",x) }
#(2)segmentCN分词函数
#中文分词,也可以考虑使用 rmmseg4j、rsmartcn 
wordsegment<- function(x) { 
  library(Rwordseg) 
  segmentCN(x)
} 
#(3)去除停止词函数 
removeStopWords = function(x,words) {     
  ret = character(0) 
  index <- 1 
  it_max <- length(x) 
  while (index <= it_max) { 
    if (length(words[words==x[index]]) <1) ret <- c(ret,x[index]) 
    index <- index +1 
  } 
  ret 
} 
#(1)移除数字
sample.words <- lapply(data[,1], removeNumbers) 
dim(as.matrix(sample.words))
head(sample.words)
#(2)中文分词
sample.words <- lapply(sample.words, wordsegment) 
dim(as.matrix(sample.words))
sample.words[1:6]
#(3)移除停止词
#先处理中文分词,再处理 stopwords,防止全局替换丢失信息,
#下面这句运行时间较长 
sample.words <- lapply(sample.words, removeStopWords, mystopwords) 
dim(as.matrix(sample.words))
sample.words<-as.matrix(sample.words)
head(sample.words)
text<-sample.words[,1]
colnames(sample.words)<-c("text")
write.csv(as.matrix(sample.words),"delateddata.txt")
write.csv(as.matrix(sample.words),"delateddata.csv")

去掉停止词后的结果为下:


image.png

接着,我们继续使用Jupyter来进行LDA模型;
在Jupyter Notebook中新建一个Python 3笔记本,起名为LDA。


image.png

导入文件处理包os

import os
os.chdir('G:\\课程\\研一\\回归分析')  # 打印当前工作目录

为了处理表格数据,我们依然使用数据框工具Pandas。先调用它。

import pandas as pd

读入我们的数据文件,clipboard指的是delateddata.txt用notepad打开复制,为防止乱码,注意参数encoding设置为utf-8;

df=pd.read_clipboard()

查看数据的前几行,

df.head()

结果为下:


image.png

查看数据维度

df.shape
image.png

导入jieba分词包

import jieba

我们此次需要处理的,不是单一文本数据,而是28197条文本数据,因此我们需要把这项工作并行化。这就需要首先编写一个函数,处理单一文本的分词。

def chinese_word_cut(mytext):
    return " ".join(jieba.cut(mytext))

有了这个函数之后,我们就可以不断调用它来批量处理数据框里面的全部文本(正文)信息了。你当然可以自己写个循环来做这项工作。但这里我们使用更为高效的apply函数。

df["title_cutted"] = df.title.apply(chinese_word_cut)

执行完毕之后,我们需要查看一下,文本是否已经被正确分词。

df.title_cutted.head()

结果为下:


image.png

接下来我们对这些本文做向量化,所谓文本向量化,指的就是形成一个28197(文档个数)*n(文本中所有词的数量)的0-1矩阵,特定词在这个文档出现记为1,否则为0。若选取所有词的话,这必然是一个很大的矩阵,因此在之前的操作中,本文从所有的词中选取了1000关键词。

from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

处理的文本里面有大量的词汇。我们不希望处理所有词汇。因为一来处理时间太长,二来那些很不常用的词汇对我们的主题抽取意义不大。所以这里做了个限定,只从文本中提取1000个最重要的特征关键词,然后停止。

n_features = 1000

下面我们开始关键词提取和向量转换过程:

tf_vectorizer = CountVectorizer(strip_accents = 'unicode',
                                max_features=n_features,
                                stop_words='english',
                                max_df = 0.5,
                                min_df = 10)

tf = tf_vectorizer.fit_transform(df.title_cutted)

现在,开始我们的LDA模型,先导入软件包

from sklearn.decomposition import LatentDirichletAllocation

至于主题数量,到底确定几个主题,这个确实不能事知道的,我们可以一步步不断地优化和观察各种数量的主题分类,取其最优。也可制定具体的量化标准,确定几个主题是最佳,附上参考链接:https://blog.csdn.net/lwhsyit/article/details/82750218
此文在17点中,阐述了如何寻找最佳主题数量,并制定了量化标准,像下图这样,由于时间关系,不做具体说明:

LDA.jpg
我们暂定5个分类:
n_topics = 5
lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
                                learning_method='online',
                                learning_offset=50.,
                                random_state=0)
lda.fit(tf)

最后一句运行时间较长,
主题没有一个确定的名称,而是用一系列关键词刻画的。我们定义以下的函数,把每个主题里面的前若干个关键词显示出来:

def print_top_words(model, feature_names, n_top_words):
    for topic_idx, topic in enumerate(model.components_):
        print("Topic #%d:" % topic_idx)
        print(" ".join([feature_names[i]
                        for i in topic.argsort()[:-n_top_words - 1:-1]]))
    print()

定义好函数之后,我们暂定每个主题输出前20个关键词。

n_top_words = 20

以下命令会帮助我们依次输出每个主题的关键词表:

tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)

执行结果为下:

Topic #0:
电影 产品 科技 融资 ai 直播 公司 媒体 嗅评 经济 驾驶 员工 创新 回应 区块 票房 创始人 故事 网易 美团
Topic #1:
微信 视频 小米 华为 乐视 数据 上市 人工智能 营销 社交 巨头 头条 共享 支付宝 支付 北京 单车 雷军 bat 医疗
Topic #2:
苹果 创业 百度 未来 手机 ceo 晚报 世界 背后 内容 发布 平台 微软 vr 特斯拉 消费 生意 技术 流量 小米
Topic #3:
中国 早报 亿美元 市场 游戏 谷歌 美国 滴滴 企业 汽车 iphone 用户 资本 估值 三星 uber 全球 特朗普 贾跃亭 赚钱
Topic #4:
互联网 阿里 公司 投资 电商 京东 收购 时代 金融 马云 亚马逊 亿元 品牌 创业者 行业 广告 智能 机会 日本 万达

接下来,来点有趣的,使其变得可视化:

import pyLDAvis
import pyLDAvis.sklearn
pyLDAvis.enable_notebook()
pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)

以上代码需要一段时间,耐心等待:
图的左侧,用圆圈代表不同的主题,圆圈的大小代表了每个主题分别包含文章的数量。

图的右侧,列出了最重要(频率最高)的30个关键词列表。注意当你没有把鼠标悬停在任何主题之上的时候,这30个关键词代表全部文本中提取到的30个最重要关键词。

如果你把鼠标悬停在2号上面:

image.png

右侧的关键词列表会立即发生变化,红色展示了每个关键词在当前主题下的频率。

以上是认为设定主题数为5的情况。可如果我们把主题数量设定为10呢?(如果你要继续运行代码,需要点一下Kernel下面的Interrupt按钮)


n_topics = 10
lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
                                learning_method='online',
                                learning_offset=50.,
                                random_state=0)
lda.fit(tf)
print_top_words(lda, tf_feature_names, n_top_words)
pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)

程序输出给我们10个主题下最重要的20个关键词:

Topic #0:
中国 阿里 滴滴 乐视 金融 亚马逊 特朗普 广告 头条 日本 流量 音乐 体育 离职 ofo 万科 报告 成立 影业 版权
Topic #1:
苹果 早报 亿美元 汽车 晚报 人工智能 亿元 技术 微软 创业者 巨头 智能 资本 vr 上市 生意 一场 bat 印度 电视
Topic #2:
谷歌 数据 融资 支付 北京 硅谷 事件 医疗 财报 计划 蚂蚁 价值 影响 程序 大战 生活 亿美金 泡沫 调查 解读
Topic #3:
美国 科技 企业 背后 媒体 嗅评 uber 零售 区块 雷军 增长 荣耀 ip 设计 聊聊 好莱坞 罗永浩 拯救 年轻人 真相
Topic #4:
小米 手机 微信 产品 iphone 特斯拉 发布 三星 模式 消费 美团 马斯克 人类 告诉 升级 明星 时间 逻辑 危机 马化
Topic #5:
创业 投资 电商 京东 收购 时代 共享 内容 品牌 平台 机会 万达 网络 创始人 单车 ipo 成功 产业 融资 城市
Topic #6:
视频 电影 世界 经济 驾驶 赚钱 创新 行业 商业 故事 app 网易 机器人 转型 自动 付费 国产 上海 焦虑 网站
Topic #7:
游戏 直播 马云 社交 支付宝 oo facebook 领域 投资人 改变 微博 盈利 股东 项目 入股 合作 独角兽 平台 揭秘 尴尬
Topic #8:
公司 互联网 百度 华为 估值 早报 贾跃亭 回应 票房 市值 员工 刘强 风口 李彦宏 业务 联想 文化 香港 运营商 上市
Topic #9:
市场 未来 ceo 中国 ai 用户 营销 全球 股价 体验 淘宝 监管 银行 十年 发布会 小时 特币 无人机 案例 粉丝

你会发现当主题设定为10的时候,会有一些抱团现象出现。

image.png

本文分别绘制了主题数量为4—10的LDA图,经过分析,选取分类数量为5,可以分析:

七、总结

八、参考文献

注:文本旨在抛砖引玉,如有不同意见,欢迎商榷。

Written By LXP

上一篇下一篇

猜你喜欢

热点阅读