1. 程式人生 > >我分析了虎嗅網5萬篇文章,發現這些祕密

我分析了虎嗅網5萬篇文章,發現這些祕密

本文轉自:https://mp.weixin.qq.com/s?__biz=MzAxMjUyNDQ5OA==&mid=2653558174&idx=1&sn=4f4c1b389a92bf43c4142736f908fddc&chksm=806e3b23b719b2358c1bc3cd59fde58276173605806354e4ad8c6e324721f6a8a15e9e2063ad&mpshare=1&scene=1&srcid=1204zKy587C8qNjEW5OgCfGU#rd


1. 分析背景

1.1. 為什麼選擇「虎嗅」

在眾多新媒體網站中,「虎嗅」網的文章內容和質量還算不錯。在「新榜」科技類公眾號排名中,它位居榜單第 3 名,還是比較受歡迎的。所以選擇爬取該網站的文章資訊,順便從中瞭解一下這幾年科技網際網路都出現了哪些熱點資訊。
在這裡插入圖片描述

「關於虎嗅」
虎嗅網創辦於 2012 年 5 月,是一個聚合優質創新資訊與人群的新媒體平臺。該平臺專注於貢獻原創、深度、犀利優質的商業資訊,圍繞創新創業的觀點進行剖析與交流。虎嗅網的核心,是關注網際網路及傳統產業的融合、明星公司的起落軌跡、產業潮汐的動力與趨勢。

1.2. 分析內容

分析虎嗅網 5 萬篇文章的基本情況,包括收藏數、評論數等

發掘最受歡迎和最不受歡迎的文章及作者

分析文章標題形式(長度、句式)與受歡迎程度之間的關係

展現近些年科技網際網路行業的熱門詞彙


1.3. 分析工具

Python 3.6

pyspider

MongoDB

Matplotlib

WordCloud

Jieba



2. 資料抓取

使用 pyspider 抓取了虎嗅網的主頁文章,文章抓取時期為 2012 年建站至 2018 年 11 月 1 日,共計約 5 萬篇文章。抓取 了 7 個欄位資訊:文章標題、作者、發文時間、評論數、收藏數、摘要和文章連結。

2.1. 目標網站分析

這是要爬取的 網頁介面,可以看到是通過 AJAX 載入的。
在這裡插入圖片描述


在這裡插入圖片描述
右鍵開啟開發者工具檢視翻頁規律,可以看到 URL 請求是 POST 型別,下拉到底部檢視 Form Data,表單需提交引數只有 3 項。經嘗試, 只提交 page 引數就能成功獲取頁面的資訊,其他兩項引數無關緊要,所以構造分頁爬取非常簡單。

huxiu_hash_code: 39bcd9c3fe9bc69a6b682343ee3f024a
page: 4
last_dateline: 1541123160

接著,切換選項卡到 Preview 和 Response 檢視網頁內容,可以看到資料都位於 data 欄位裡。total_page 為 2004,表示一共有 2004 頁的文章內容,每一頁有 25 篇文章,總共約 5 萬篇,也就是我們要爬取的數量。
在這裡插入圖片描述
以上,我們就找到了所需內容,接下來可以開始構造爬蟲,整個爬取思路比較簡單。

2.2. pyspider 介紹

和之前文章不同的是,這裡我們使用一種新的工具來進行爬取,叫做:pyspider 框架。由國人 binux 大神開發,GitHub Star 數超過 12 K,足以證明它的知名度。可以說,學習爬蟲不能不會使用這個框架。

網上關於這個框架的介紹和實操案例非常多,這裡僅簡單介紹一下。

我們之前的爬蟲都是在 Sublime 、PyCharm 這種 IDE 視窗中執行的,整個爬取過程可以說是處在黑箱中,內部執行的些細節並不太清楚。而 pyspider 一大亮點就在於提供了一個視覺化的 WebUI 介面,能夠清楚地檢視爬蟲的執行情況。
在這裡插入圖片描述
pyspider 的架構主要分為 Scheduler(排程器)、Fetcher(抓取器)、Processer(處理器)三個部分。Monitor(監控器)對整個爬取過程進行監控,Result Worker(結果處理器)處理最後抓取的結果。
在這裡插入圖片描述
該框架比較容易上手,網頁右邊是程式碼區,先定義類(Class)然後在裡面新增爬蟲的各種方法(也可以稱為函式),執行的過程會在左上方顯示,左下方則是輸出結果的區域。

官方主頁:http://docs.pyspider.org/en/latest/

安裝好該框架並大概瞭解用法後,下面我們可以就開始爬取了。

2.3. 抓取資料

CMD 命令視窗執行:pyspider all 命令,然後瀏覽器輸入:http://localhost:5000/ 就可以啟動 pyspider 。

點選 Create 新建一個專案,Project Name 命名為:huxiu,因為要爬取的 URL 是 POST 型別,所以這裡可以先不填寫,之後可以在程式碼中新增,再次點選 Creat 便完成了該專案的新建。
在這裡插入圖片描述
新專案建立好後會自動生成一部分模板程式碼,我們只需在此基礎上進行修改和完善,然後就可以執行爬蟲專案了。現在,簡單梳理下程式碼編寫步驟。
在這裡插入圖片描述

from pyspider.libs.base_handler import *
class Handler(BaseHandler):
    crawl_config:{
        "headers":{
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest'
            }
    }
    def on_start(self):
        for page in range(2,3): # 先迴圈1頁
            print('正在爬取第 %s 頁' % page)
            self.crawl('https://www.huxiu.com/v2_action/article_list',method='POST',data={'page':page}, callback=self.index_page)

這裡,首先定義了一個 Handler 主類,整個爬蟲專案都主要在該類下完成。 接著,可以將爬蟲基本的一些基本配置,比如 Headers、代理等設定寫在下面的 crawl_config 屬性中。

如果你還沒有習慣從函式(def)轉換到類(Class)的程式碼寫法,那麼需要先了解一下類的相關知識,之後我也會單獨用一篇文章介紹一下。

下面的 on_start() 方法是程式的入口,也就是說程式啟動後會首先從這裡開始執行。首先,我們將要爬取的 URL傳入 crawl() 方法,同時將 URL 修改成虎嗅網的:https://www.huxiu.com/v2_action/article_list。

由於 URL 是 POST 請求,所以我們還需要增加兩個引數:method 和 data。method 表示 HTTP 請求方式,預設是 GET,這裡我們需要設定為 POST;data 是 POST 請求表單引數,只需要新增一個 page 引數即可。

接著,通過 callback 引數定義一個 index_page() 方法,用來解析 crawl() 方法爬取 URL 成功後返回的 Response 響應。在後面的 index_page() 方法中,可以使用 PyQuery 提取響應中的所需內容。具體提取方法如下:

import json
from pyquery import PyQuery as pq
def index_page(self, response):
        content = response.json['data']
        # 注意,在sublime中,json後面需要新增(),pyspider 中則不用
        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 ]   # 列表生成式結果返回每頁提取出25條字典資訊構成的list
        print(data)
        return data

這裡,網頁返回的 Response 是 json 格式,待提取的資訊存放在其中的 data 鍵值中,由一段 HTML 程式碼構成。我們可以使用 response.json[‘data’] 獲取該 HTML 資訊,接著使用 PyQuery 搭配 CSS 語法提取出文章標題、連結、作者等所需資訊。這裡使用了列表生成式,能夠精簡程式碼並且轉換為方便的 list 格式,便於後續儲存到 MongoDB 中。

我們輸出並檢視一下第 2 頁的提取結果:

# 由25個 dict 構成的 list
[{'title': '想要長生不老?殺死體內的“殭屍細胞”吧', 'url': 'https://www.huxiu.com/article/270086.html', 'name': '造就Talk', 'write_time': '19小時前', 'comment': '4', 'favorites': '28', 'abstract': '如果有了最終療法,也不應該是每天都需要接受治療'}, 
 {'title': '日本步入下流社會,我們還在買買買', 'url': 'https://www.huxiu.com/article/270112.html', 'name': '騰訊《大家》©', 'write_time': '20小時前', 'comment': '13', 'favorites': '142', 'abstract': '我買,故我在'}
...
]

可以看到,成功得到所需資料,然後就可以儲存了,可以選擇輸出為 CSV、MySQL、MongoDB 等方式,這裡我們選擇儲存到 MongoDB 中。

import pandas as pd
import pymongo
import time
import numpy as np
5client = pymongo.MongoClient('localhost',27017)
db = client.Huxiu
mongo_collection = db.huxiu_news

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

上面,定義了一個 on_result() 方法,該方法專門用來獲取 return 的結果資料。這裡用來接收上面 index_page() 返回的 data 資料,在該方法裡再定義一個儲存到 MongoDB 的方法就可以儲存到 MongoDB 中。

下面,我們來測試一下整個爬取和儲存過程。點選左上角的 run 就可以順利執行單個網頁的抓取、解析和儲存,結果如下:

在這裡插入圖片描述

上面完成了單頁面的爬取,接下來,我們需要爬取全部 2000 餘頁內容。

需要修改兩個地方,首先在 on_start() 方法中將 for 迴圈頁數 3 改為 2002。改好以後,如果我們直接點選 run ,會發現還是隻能爬取第 2 頁的結果。

這是因為,pyspider 以 URL的 MD5 值作為 唯一 ID 編號,ID 編號相同的話就視為同一個任務,便不會再重複爬取。由於 GET 請求的 分頁URL 通常是有差異的,所以 ID 編號會不同,也就自然能夠爬取多頁。但這裡 POST 請求的分頁 URL 是相同的,所以爬完第 2 頁,後面的頁數便不會再爬取。

那有沒有解決辦法呢? 當然是有的,我們需要重新寫下 ID 編號的生成方式,方法很簡單,在 on_start() 方法前面新增下面 2 行程式碼即可:

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

這樣,我們再點選 run 就能夠順利爬取 2000 頁的結果了,我這裡一共抓取了 49,996 條結果,耗時 2 小時左右完成。
在這裡插入圖片描述
以上,就完成了資料的獲取。有了資料我們就可以著手分析,不過這之前還需簡單地進行一下資料的清洗、處理。

3. 資料清洗處理

首先,我們需要從 MongoDB 中讀取資料,並轉換為 DataFram

client = pymongo.MongoClient(host='localhost', port=27017)
db = client['Huxiu']
collection = db['huxiu_news']
# 將資料庫資料轉為DataFrame
data = pd.DataFrame(list(collection.find()))

下面我們看一下資料的總體情況,可以看到資料的維度是 49996 行 × 8 列。發現多了一列無用的 _id 需刪除,同時 name 列有一些特殊符號,比如© 需刪除。另外,資料格式全部為 Object 字串格式,需要將 comment 和 favorites 兩列更改為數值格式、 write_time 列更改為日期格式。

print(data.shape)  # 檢視行數和列數
print(data.info()) # 檢視總體情況
print(data.head()) # 輸出前5行

# 結果:
(49996, 8)
Data columns (total 8 columns):
_id           49996 non-null object
abstract      49996 non-null object
comment       49996 non-null object
favorites     49996 non-null object
name          49996 non-null object
title         49996 non-null object
url           49996 non-null object
write_time    49996 non-null object
dtypes: object(8)

    _id abstract    comment favorites   name    title   url write_time
0    5bdc2   “在你們看到… 22  50  普象工業設計小站©   看了蘋果屌   https://    10小時前
1    5bdc2   中國”綠卡”號稱“世界最難拿” 9   16  經濟觀察報©  遞交材料厚   https://    10小時前
2    5bdc2   鮮衣怒馬少年時 2   13  小馬宋 金庸小說陪   https://    11小時前
3    5bdc2   預告還是預警? 3   10  Cuba Libre  阿里即將發   https://    11小時前
4    5bdc2   庫克:咋回事? 2   3   Cuba Libre  【虎嗅早報   https://    11小時前

程式碼實現如下:

# 刪除無用_id列
data.drop(['_id'],axis=1,inplace=True)
# 替換掉特殊字元©
data['name'].replace('©','',inplace=True,regex=True)
# 字元更改為數值
data = data.apply(pd.to_numeric,errors='ignore')
# 更該日期格式
data['write_time'] = data['write_time'].replace('.*前','2018-10-31',regex=True) 
# 為了方便,將write_time列,包含幾小時前和幾天前的行,都替換為10月31日最後1天。
data['write_time'] = pd.to_datetime(data['write_time'])

下面,我們看一下資料是否有重複,如果有,那麼需要刪除。

# 判斷整行是否有重複值
print(any(data.duplicated()))
# 顯示True,表明有重複值,進一步提取出重複值數量
data_duplicated = data.duplicated().value_counts()
print(data_duplicated) # 顯示2 True ,表明有2個重複值
# 刪除重複值
data = data.drop_duplicates(keep='first')
# 刪除部分行後,index中斷,需重新設定index
data = data.reset_index(drop=True)
#結果:
True 
False    49994
True         2

然後,我們再增加兩列資料,一列是文章標題長度列,一列是年份列,便於後面進行分析。

data['title_length'] = data['title'].apply(len)
data['year'] = data['write_time'].dt.year
Data columns (total 9 columns):
abstract        49994 non-null object
comment         49994 non-null int64
favorites       49994 non-null int64
name            49994 non-null object
title           49994 non-null object
url             49994 non-null object
write_time      49994 non-null datetime64[ns]
title_length    49994 non-null int64
year            49994 non-null int64

以上,就完成了基本的資料清洗處理過程,針對這 9 列資料可以開始進行分析了。

4. 描述性資料分析

通常,資料分析主要分為四類: 「描述型分析」、「診斷型分析」「預測型分析」「規範型分析」。「描述型分析」是用來概括、表述事物整體狀況以及事物間關聯、類屬關係的統計方法,是這四類中最為常見的資料分析型別。通過統計處理可以簡潔地用幾個統計值來表示一組資料地集中性(如平均值、中位數和眾數等)和離散型(反映資料的波動性大小,如方差、標準差等)。

這裡,我們主要進行描述性分析,資料主要為數值型資料(包括離散型變數和連續型變數)和文字資料。

4.1. 總體情況

先來看一下總體情況。

print(data.describe())
             comment     favorites  title_length 
count  49994.000000  49994.000000  49994.000000  
mean      10.860203     34.081810     22.775333  
std       24.085969     48.276213      9.540142  
min        0.000000      0.000000      1.000000  
25%        3.000000      9.000000     17.000000  
50%        6.000000     19.000000     22.000000  
75%       12.000000     40.000000     28.000000  
max     2376.000000   1113.000000    224.000000  

這裡,使用了 data.describe() 方法對數值型變數進行統計分析。從上面可以簡要得出以下幾個結論:

  1. 讀者的評論和收藏熱情都不算太高。大部分文章(75 %)的評論數量為十幾條,收藏數量不過幾十個。這和一些微信大 V 公眾號動輒百萬級閱讀、數萬級評論和收藏量相比,虎嗅網的確相對小眾一些。不過也正是因為小眾,也才深得部分人的喜歡。

  2. 評論數最多的文章有 2376 條,收藏數最多的文章有 1113 個收藏量,說明還是有一些潛在的比較火或者質量比較好的文章。

  3. 最長的文章標題長達 224 個字,大部分文章標題長度在 20 來個字左右,所以 標題最好不要太長或過短。

對於非數值型變數(name、write_time),使用 describe() 方法會產生另外一種彙總統計。

print(data['name'].describe())
print(data['write_time'].describe())
# 結果:
count     49994
unique     3162
top          虎嗅
freq      10513
Name: name, dtype: object
count                   49994
unique                   2397
top       2014-07-10 00:00:00
freq                      274
first     2012-04-03 00:00:00
last      2018-10-31 00:00:00

unique 表示唯一值數量,top 表示出現次數最多的變數,freq 表示該變量出現的次數,所以可以簡單得出以下幾個結論:

  1. 在文章來源方面,3162 個作者貢獻了這 5 萬篇文章,其中自家官網「虎嗅」寫的數量最多,超過了 1 萬篇,這也很自然。

  2. 在文章發表時間方面,最早的一篇文章來自於 2012年 4 月 3 日。 6 年多時間,發文數最多的 1 天 是 2014 年 7 月 10 日,一共發了 274 篇文章。

4.2. 不同時期文章釋出的數量變化

在這裡插入圖片描述
可以看到 ,以季度為時間尺度的 6 年間,前幾年發文數量比較穩定,大概在1750 篇左右,個別季度數量激增到 2000 篇以上。2016 年之後文章開始增加到 2000 篇以上,可能跟網站知名度提升有關。首尾兩個季度日期不全,所以數量比較少。

具體程式碼實現如下:

def analysis1(data):
    # # 彙總統計
    # print(data.describe())
    # print(data['name'].describe())
    # print(data['write_time'].describe())

    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)
    ax1.plot(x,data.values, #x、y座標
        color = color_line , #折線圖顏色為紅色
        marker = 'o',markersize = 4 #標記形狀、大小設定
        )
    ax1.set_xticks(x) # 設定x軸標籤為自然數序列
    ax1.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 )
        # '%.0f' %y 設定標籤格式不帶小數
    # 設定標題及橫縱座標軸標題
    plt.title('虎嗅網文章數量釋出變化(2012-2018)',color = colors,fontsize=fontsize_title)
    plt.xlabel('時期')
    plt.ylabel('文章(篇)')
    plt.tight_layout()  # 自動控制空白邊緣
    plt.savefig('虎嗅網文章數量釋出變化.png',dpi=200)
    plt.show()

4.3. 文章收藏量 TOP10

接下來,到了我們比較關心的問題:幾萬篇文章裡,到底哪些文章寫得比較好或者比較火?
在這裡插入圖片描述
此處選取了「favorites」(收藏數量)作為衡量標準。畢竟,一般好的文章,我們都會有收藏的習慣。

第一名「讀完這10本書,你就能站在智商鄙視鏈的頂端了 」以 1113 次收藏位居第一,並且遙遙領先於後者,看來大家都懷有「想早日攀上人生巔峰,一覽眾人小」的想法啊。開啟這篇文章的連結,文中提到了這幾本書:《思考,快與慢》、《思考的技術》、《麥肯錫入職第一課:讓職場新人一生受用的邏輯思考力》等。一本都沒看過,看來這輩子是很難登上人生巔峰了。

發現兩個有意思的地方:
第一,文章標題都比較短小精煉。
第二,文章收藏量雖然比較高,但評論數都不多,猜測這是因為 大家都喜歡做伸手黨?

4.4. 歷年文章收藏量 TOP3

在瞭解文章的總體排名之後,我們來看看歷年的文章排名是怎樣的。這裡,每年選取了收藏量最多的 3 篇文章。
在這裡插入圖片描述
在這裡插入圖片描述
可以看到,文章收藏量基本是逐年遞增的,但 2015 年的 3 篇文章的收藏量卻是最高的,包攬了總排名的前 3 名,不知道這一年的文章有什麼特別之處。

以上只羅列了一小部分文章的標題,可以看到標題起地都蠻有水準的。關於標題的重要性,有這樣通俗的說法:「一篇好文章,標題佔一半」,一個好的標題可以大大增強文章的傳播力和吸引力。文章標題雖只有短短數十字,但要想起好,裡面也是很有很多技巧的。

好在,這裡提供了 5 萬個標題可供參考。如需,可以在公眾號後臺回覆「虎嗅」得到這份 CSV 檔案。

程式碼實現如下:

def analysis2(data):
    # # 總收藏排名
    # top = data.sort_values(['favorites'],ascending = False)
    # # 收藏前10
    # top.index = (range(1,len(top.index)+1)) # 重置index,並從1開始編號
    # print(top[:10][['title','favorites','comment']])

    # 按年份排名
    # # 增加一列年份列
    # data['year'] = data['write_time'].dt.year
    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)  # ok
    data_reshape.plot(
        # x='year',
        y=[1,2,3],
        kind='bar',
        width=0.3,
        color=['#1362A3','#3297EA','#8EC6F5']  # 設定不同的顏色
        # title='虎嗅網歷年收藏數最多的3篇文章'
        )
    plt.xlabel('Year')
    plt.ylabel('文章收藏數量')
    plt.title('歷年 TOP3 文章收藏量比較',color = colors,fontsize=fontsize_title)
    plt.tight_layout()  # 自動控制空白邊緣,以全部顯示x軸名稱
    # plt.savefig('歷年 Top3 文章收藏量比較.png',dpi=200)
    plt.show()

4.4.1. 最高產作者 TOP20

上面,我們從收藏量指標進行了分析,下面,我們關注一下發布文章的作者(個人/媒體)。前面提到發文最多的是虎嗅官方,有一萬多篇文章,這裡我們篩除官媒,看看還有哪些比較高產的作者。
在這裡插入圖片描述
可以看到,前 20 名作者的發文量差距都不太大。發文比較多的有「娛樂資本論」、「Eastland」、「發條橙子」這類媒體號;也有虎嗅官網團隊的作者:發條橙子、周超臣、張博文等;還有部分獨立作者:假裝FBI、孫永傑等。可以嘗試關注一下這些高產作者。

程式碼實現如下:

def analysis3(data):
    data = data.groupby(