1. 程式人生 > >JB的Python之旅-每句話背後的情緒值

JB的Python之旅-每句話背後的情緒值

前言

每個人相處都有一套生活方式,跟女人也不一樣,不同的女人要用不同的邏輯思考,要琢磨不同語句背後的含義,生活,不容易;

image.png-47.7kB

對於情場小白而言,最擔心的就是女朋友不開心了,畢竟好不容易才從右手變成了實物,肯定是加倍愛惜的;

但,你真的瞭解每一句話的情緒分析嗎? 給一句話,能知道這句話的情緒佔比嗎?

情緒分析

情緒分析關鍵的是詞典,網上找了下,大連理工情感詞彙本體庫比較有名,那就試試看;

下載地址:

連結:https://pan.baidu.com/s/18PeWl-9EjZ7O5Rdfejzgig     
提取碼:qc3n 
複製程式碼

下載完後,看了下說明文件,瞭解下,詞庫裡面的格式是這樣的:

image.png-13.4kB

看起來好像不錯,就試試看~

解壓說,發現有這幾個檔案:

image.png-40.2kB

簡單介紹下2個py檔案:

這裡需要注意,使用evaluate.py時,有可能會報UnicodeEncodeError這個錯;

image.png-18.6kB

解決也很簡單,在原指令碼的第11行,指定encoding用utf-8即可:

with open('情感詞彙.csv', 'w',encoding='utf-8') as out_file:
複製程式碼

而執行evaluate.py時候,需要import docopy跟pandas兩個庫,自行安裝吧;

pip install docopy

pandas的話,之前聽說很多依賴,後來jb安裝個anaconda就好了(全家桶);
複製程式碼

docopt

安裝完兩個庫,因docopy這個庫沒了解過,因此上官網瞭解下:

docopt官網上的介紹是:

Command-line interface description language
docopt helps you:
define interface for your command-line app, and
automatically generate parser for it.
複製程式碼

從中可以看出docopt的兩個主要功能:

  • 定義互動引數
  • 解析引數資訊

再看下官網的例子:

Naval Fate.

Usage:
  naval_fate ship new <name>...
  naval_fate ship <name> move <x> <y> [--speed=<kn>]
  naval_fate ship shoot <x> <y>
  naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
  naval_fate -h | --help
  naval_fate --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.
複製程式碼

在這個例子中,Naval Fate是app名稱,naval_fate是命令列命令,shipnewmove這些是可選的執行命令(commands), xyname 這些是位置引數(positional arguments), -h--help--speed等這些是選項引數(options);

例子裡面用

  • "[]"描述可選元素(optional)
  • "()"描述必要元素(required)
  • "|" 描述互斥元素(mutually exclusive)
  • "..."描述重複元素(repeating)

這些引數,前面加上naval_fate就形成了可用的命令,這些命令在Usage中介紹;

Usage下面的Options裡羅列了選項(options)及其描述,它具體描述了

  • 選項是否有長/短形式,如-h, --help
  • 選項後面是否帶引數,如--speed=
  • 選項是否有預設值,如[default: 10]

Usage和options裡的內容就組成了幫助資訊,當用戶輸入-h或--help引數時,命令列就會輸出幫助資訊。

docopt會抽取幫助資訊裡的內容,然後對命令列傳入的引數進行解析。

例項

用例項來說明,建立一個test.py文件:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)
複製程式碼

執行命令:

python test.py ship new jb
複製程式碼

結果:

{'--drifting': False,
 '--help': False,
 '--moored': False,
 '--speed': '10',
 '--version': False,
 '<name>': ['jb'],
 '<x>': None,
 '<y>': None,
 'mine': False,
 'move': False,
 'new': True,
 'remove': False,
 'set': False,
 'ship': True,
 'shoot': False}
複製程式碼

然後再嘗試一個Usage裡沒有的命令:

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version
複製程式碼

小小結

  • docopt(doc)這個函式根據幫助文件的說明解析命令列引數,然後將結果返回為一個字典;
  • 當用戶使用不在Usage之內的命令,輸出幫助文件;
  • 要使用的時候,from docopt import docopt呼叫即可;
  • 必填引數,doc,其他4個是可選(helpversionargvoptions_first);

evaluate

看回evaluate.py這個檔案,頂部有這麼一段:

__doc__ = '''
Usage:
    emotion WORD

With Python:
    EmotionDict() --> init
    EmotionDict.evaluate(word) --> tuple(詞語(str), 情感分類(str), 強度(int), 極性(int)) or None
'''
複製程式碼

這裡面也專門告訴py怎麼用了,那新建個test.py試試:

from 情感詞彙.evaluate import EmotionDict

test = EmotionDict()
print(test.evaluate(word="戰禍"))
複製程式碼

直接執行,得到的輸出:

('戰禍', 'ND', 5, 2)
複製程式碼

對比了Excel的內容,內容是一樣的;

image.png-48.8kBimage.png-48.8kB

  • ND代表憎惡;
  • 強度有1、3、5、7、9,9是最高,5則說明一般;
  • 極性有4類,0代表中性,1代表褒義,2代表貶義,3帶邊兼有褒貶兩性;

其他的,請看說明.doc,都有說明;

因此,戰禍這個詞,用貶義的方式來表達內心的憎惡? 不知道為什麼,總感覺怪怪的;

試試一句話

一個詞就是上面的用法,那一段話呢?

就需要分詞了,中文分詞用的最多就是jieba庫,不瞭解的同學,請移步此處

某博直接找來一段話,結合分詞,一起看看:

分詞

seg_list = jieba.cut("帶著立場看比賽註定是痛苦的,倒不如好好品品比賽中每一個精彩的瞬間!",cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))
複製程式碼

輸出:

Default Mode: 帶/ 著/ 立場/ 看/ 比賽/ 註定/ 是/ 痛苦/ 的/ ,/ 倒不如/ 好好/ 品品/ 比賽/ 中/ 每/ 一個/ 精彩/ 的/ 瞬間/ !
複製程式碼

組合

seg_list = jieba.cut("帶著立場看比賽註定是痛苦的,倒不如好好品品比賽中每一個精彩的瞬間!",cut_all=False)
test = EmotionDict()
for i in seg_list:
    print(i)
    print(test.evaluate(word=i))
複製程式碼

輸出:

帶
None
著
None
立場
None
看
None
比賽
None
註定
None
是
None
痛苦
('痛苦', 'NB', 7, 0)
的
None
,
None
倒不如
None
好好
None
品品
None
比賽
None
中
None
每
None
一個
None
精彩
('精彩', 'PH', 7, 1)
的
None
瞬間
None
!
None
複製程式碼

標點符號沒做過濾,不太影響; 簡單看了下,那麼多個詞,只有精彩、痛苦是有返回內容的,也就說明,原來的詞庫遠遠不夠;

而且要把對應的PH、數字對應起來,還需要單獨寫一個轉換邏輯,還要過濾各種符號,這裡面還是有很多小細節做的,到這裡,效果實在太差了,主要是,詞庫內容太少了,很多詞語都沒有,壓根就沒辦法判斷;

看看別人的

既然不自己造輪子,那就看看別人的吧,這種語境分析,第一時間就想起BAT了,那就一起看看BAT吧;

某度

直接某度搜索某度自然語言處理,直接彈出某度AI開放平臺,點選後看下產品服務,選擇自然語言處理,就看到有提供情感傾向分析,同時也有對話情緒識別兩個服務,兩者應該是共同原理,就看前者了;

image.png-198.1kB

點選後,登入,直接點選api文件,翻到情感傾向分析介面;

介面描述

對包含主觀觀點資訊的文字進行情感極性類別(積極、消極、中性)的判斷,並給出相應的置信度。

請求說明

請求示例

URL引數

引數
access_token 通過API Key和Secret Key獲取的access_token,參考“Access Token獲取

Header如下

引數
Content-Type application/json

Body請求示例

{
    "text": "蘋果是一家偉大的公司" 
}
複製程式碼

請求引數

引數 型別 描述
text string 文字內容(GBK編碼),最大2048位元組

返回說明

返回引數 引數|說明|描述 --|--| log_id|uint64|請求唯一標識碼 sentiment|int|表示情感極性分類結果,0:負向,1:中性,2:正向 confidence|float|表示分類的置信度,取值範圍[0,1] positive_prob|float|表示屬於積極類別的概率 ,取值範圍[0,1] negative_prob|float|表示屬於消極類別的概率,取值範圍[0,1]

返回示例

{
    "text":"蘋果是一家偉大的公司",
    "items":[
        {
            "sentiment":2,    //表示情感極性分類結果
            "confidence":0.40, //表示分類的置信度
            "positive_prob":0.73, //表示屬於積極類別的概率
            "negative_prob":0.27  //表示屬於消極類別的概率
        }
    ]
}
複製程式碼

Access Token獲取

Access Token獲取是通過API Key和Secret Key來獲取的,那這兩個怎麼獲取?

還記得情感傾向分析的主頁嗎?有個立即使用的按鈕,要去建立應用;

點選建立應用,輸入應用名稱、描述,然後點選檢視應用詳情,這裡面的API Key 跟Secret Key需要使用到;

image.png-27.3kB

來到access token獲取網址,按照要求試試,官方給的是py2,用py3重弄下,程式碼如下:

import requests

url = 'https://aip.baidubce.com/oauth/2.0/token'

headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.32 Safari/537.36",
                "Content-Type":"application/json"
}

params = {
    "grant_type":"client_credentials",
    "client_id":你的API Key,
    "client_secret":你的Secret Key

}
response = requests.post(url,headers=headers,params=params)
text = response.json().get("access_token")
print(text)
複製程式碼

對應的結果就是access_token的值啦;

爽一把

這裡遇到個坑,按照官方文件操作,用requests庫,無論怎麼調,最終都會報下面這個錯;

{'log_id': 3838837857684473751, 'error_code': 282004, 'error_msg': 'invalid parameter(s)'}
複製程式碼

最後網上找了好久,改用urllib庫就好了,一臉懵逼。。貼程式碼:

import json
import urllib

# 獲取情緒內容
access_token=你的access_token值
url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify?access_token='+access_token

headers={'Content-Type':'application/json'}

post_data = {"text":"帶著立場看比賽註定是痛苦的,倒不如好好品品比賽中每一個精彩的瞬間!"}
data=json.dumps(post_data).encode('GBK')

request = urllib.request.Request(url, data)
response = urllib.request.urlopen(request)
content = response.read()
content_str = str(content, encoding="gbk")
print(content_str)
複製程式碼

輸出:

{"log_id": 830621152984506211, "text": "帶著立場看比賽註定是痛苦的,倒不如好好品品比賽中每一個精彩的瞬間!", "items": [{"positive_prob": 0.521441, "confidence": 0.571177, "negative_prob": 0.478559, "sentiment": 1}]}
複製程式碼

根據官網文件,上面4個欄位含義如下:

  • sentiment,表示情感極性分類結果,官方沒具體說明,猜測是跟上面一樣,即0代表中性,1代表褒義,2代表貶義,3帶邊兼有褒貶兩性;
  • confidence,表示分類的置信度;
  • positive_prob,表示屬於積極類別的概率
  • negative_prob,表示屬於消極類別的概率

按照上面的結果,那這句話應該是屬於中性詞,偏積極;

某訊

直接找,會發現文智自然語言處理,產品介紹文件在這裡,API指南在這裡,官方提供demo,py的demo在這裡

download程式碼,github上說需要安全憑證,點選登入獲取;

然後還要安裝對應的依賴庫,提供2種方式任君選擇:

$ pip install qcloudapi-sdk-python

或者下載原始碼安裝
$ git clone https://github.com/QcloudApi/qcloudapi-sdk-python
複製程式碼

cd qcloudapi-sdk-python python setup.py install

然後開啟tests下的demo.py,修改模組、介面名、介面引數即可;

#!/usr/bin/python
# -*- coding: utf-8 -*-

# 引入雲API入口模組
from QcloudApi.qcloudapi import QcloudApi

'''
module: 設定需要載入的模組
'''
module = 'wenzhi'

'''
action: 對應介面的介面名,請參考wiki文件上對應介面的介面名
'''
action = 'TextSentiment'

'''
config: 雲API的公共引數
'''
config = {
    'Region': 'ap-guangzhou',
    'secretId': 'AKIDmmuRdgSV8sjR0eokVh2159Kp2OiyPHPQ',
    'secretKey': 'DNS9h6aBFLYo2BAEBPePI3d3IMGzb7ml',
}

# 介面引數
action_params = {
    "content":"帶著立場看比賽註定是痛苦的,倒不如好好品品比賽中每一個精彩的瞬間!"
}

try:
    service = QcloudApi(module, config)
    print(service.generateUrl(action, action_params))
    print(service.call(action, action_params))
except Exception as e:
    import traceback
    print('traceback.format_exc():\n%s' % traceback.format_exc())
複製程式碼

輸出:

b'{"code":0,"message":"","codeDesc":"Success","positive":0.58672362565994,"negative":0.41327640414238}'
複製程式碼
  • positive,積極
  • negative,負面

對了,某訊沒有免費的體驗,jb剛好是新人領了個免費禮包,如果不是新手,就要自己充錢的,很X訊;

image.png-24kB

某裡

點選這裡-情感分析,登入,點選開通,然後來到控制檯;

點選基礎版,api除錯:

image.png-20.4kB

選擇api,這裡關於情感分析的,只有電商類的,其他都跟情感沒啥關係:

image.png-52.6kB

按照要求,輸入你的access key跟secret,點選除錯即可:

image.png-44.8kB

關於響應的講解,點選這裡,可以看到polarity的引數值,因此,例子是負面的,很合理;

image.png-22.1kB

剩下的,就是購買了,270起,感興趣的瞭解下,使用的話,就到這裡了;

阿里提供線上除錯,比較方便,但是型別太少了,而且不夠詳細,結果就是正面、負面、中性3選1,一旦哪天有Bug,就慘了;

定時爬取微博

這個章節不想講述太多內容,之前思路都有講過,只是把程式碼結合下而已,詳情請參考下面兩篇文章:

JB的Python之旅-豆瓣自動頂貼功能
JB的Python之旅-爬蟲篇-新浪微博內容爬取

上面3個平臺的結果很明顯,只能用某度,畢竟,免費嘛;

push到微信用的是server醬,直接貼程式碼:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import re
from json import JSONDecodeError
import time
import requests
from apscheduler.schedulers.blocking import BlockingScheduler
import json
import urllib


wb_url = "https://m.weibo.cn/profile/info?uid=你要關注的微博使用者id"
server_url = "http://sc.ftqq.com/你的server醬.send"

# 獲取情緒內容
access_token='你的百度access_token值'
bd_url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify?access_token='+access_token

wb_headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/隨便,一般是你要關注的微博使用者id",
    "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) "
                  "Version/9.0 Mobile/13B143 Safari/601.1",
}

wb_params = {
    "text": "{text}",
    "desp": "{desp}"
}

statuses_id = ""
scheduler = BlockingScheduler()
page_size = 10


def get_time():
    """
    獲取當前時間
    """
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


def push_wx(text=None, desp=None):
    """
    推送訊息到微信
    :param text: 標題
    :param desp: 內容
    :return:
    """
    wb_params['text'] = text
    wb_params['desp'] = desp

    response = requests.get(server_url, params=wb_params)
    json_data = response.json()

    if json_data['errno'] == 0:
        print(get_time() + " 推送成功。")
    else:
        print(json_data)
        print("{0} 推送失敗:{1} \n {2}".format(get_time(), json_data['errno'], json_data['errmsg']))


def filter_emoji(text, replace=""):
    """
    過濾Emoji表情
    :param text: 原文
    :param replace: 將Emoji替換為此內容
    :return: 過濾後的內容
    """

    try:
        co = re.compile(u'[\U00010000-\U0010ffff]')
    except re.error:
        co = re.compile(u'[\uD800-\uDBFF][\uDC00-\uDFFF]')
    return co.sub(replace, text)


def get_desp(user, statuse):
    """
    獲取微博內容
    """

    global text;
    global nick_name;

    # 個人資訊
    avatar = user['profile_image_url']  # 頭像
    nick_name = user['screen_name']  # 暱稱
    follow_count = user['follow_count']  # 關注
    followers_count = user['followers_count']  # 粉絲
    description = user['description']  # 個性簽名

    # 微博資訊
    image = ""
    created_at = statuse['created_at']  # 時間
    source = statuse['source']  # 傳送微博的裝置


    # 微博內容
    if 'raw_text' in statuse:
        print(statuse)
        text = statuse['raw_text']
    else:
        text = statuse['text']

    text = filter_emoji(text, "[emoji]")

    # 獲取圖片
    if 'pics' in statuse:
        pics = statuse['pics']
        for pic in pics:
            image += "![]({0})\n\n".format(pic['url'])

    return "![]({0})\n\n### {1}\n\n關注:{2} and 粉絲:{3}\n\n簽名:{4}\n\n傳送時間:{5}\n\n裝置:{6}\n\n微博內容:\n\n{7}\n\n{8}" \
        .format(avatar, nick_name, follow_count, followers_count, description, created_at, source, text, image)


def start_task():
    # print("執行查詢任務")
    response = requests.get(wb_url, headers=wb_headers)

    try:
        json_data = response.json()
    except JSONDecodeError as e:
        print(get_time() + " Json解析異常, 跳過此次迴圈:" + str(e))
        return

    state = json_data['ok']

    if state != 1:
        push_wx(get_time() + " 你的女朋友又掛啦,狀態碼:" + str(state) + ",快去看看吧。", "")
        scheduler.remove_job('wb')
        return

    data = json_data['data']
    user = data['user']
    statuses = data['statuses']

    size = len(statuses)

    if size < page_size:
        print(get_time() + " 返回資料不正確,跳過本次迴圈。 size:" + str(size))
        return

    first_statuse = statuses[0]
    new_id = first_statuse['id']

    global statuses_id

    if new_id != statuses_id:
        print(get_time() + " 有新微博! id-> " + new_id)

        # 獲取微博資訊
        desp = get_desp(user, first_statuse)
        title = "女神更新微博啦"

        release_text = SentimentAnalysis()
        push_wx(title, release_text+desp + "\n\n[微博原文](https://m.weibo.cn/profile/2105667905)")

        statuses_id = new_id


def SentimentAnalysis():
    post_data = {"text": text}
    data = json.dumps(post_data).encode('GBK')

    request = urllib.request.Request(bd_url, data)
    response = urllib.request.urlopen(request)
    content = response.read()
    content_str = str(content, encoding="gbk")
    data = json.loads(content_str)

    # 積極、消極、可信度的概率
    positive_prob = '%.2f%%' % (data["items"][0]["positive_prob"] * 100)
    negative_prob = '%.2f%%' % (data["items"][0]["negative_prob"] * 100)
    confidence = '%.2f%%' % (data["items"][0]["confidence"] * 100)
    sentiment = data["items"][0]["sentiment"]

    if (positive_prob > negative_prob):
        prob = positive_prob

    elif (positive_prob < negative_prob):
        prob = negative_prob
    else:
        prob = positive_prob

    if (sentiment == 0 ):
        prob_text = "負面"
    elif (sentiment == 1 ):
        prob_text = "中性"
    elif (sentiment == 2):
        prob_text = "正向"


    analysis_text = "你女神博主:"+nick_name + ",釋出了情緒值為"+prob+",疑似是"+prob_text+"情緒的微博,快來看看吧,可信度:"+confidence+",微博原文是:"+text
    return analysis_text


if __name__ == '__main__':
    print(get_time() + " 騷年,噩夢來襲!")
    scheduler.add_job(start_task, "interval", seconds=6, id="wb")
    scheduler.start()
複製程式碼

程式碼不能直接用,要手動輸入幾個值,微博使用者id、某度access_token、server醬,完;

效果圖

image.png-25.5kB
百分號 %不知道為什麼被過濾了,正常是XX%這樣的格式,但是看著懂就好了,不糾結;

通過上面的推送資訊,資訊最大化,也得出對應的情緒值,但是,女人說的話,要視不同場景而決定其含義;

比如吵架時的分手,其實就是要你哄,要你抱;
比如成家後的不要,是不捨得,偷偷買吧;
複製程式碼

而這種含義,不結合語境是沒法判斷的;

而女人的心思,別猜,買/哄/舔就對了;

對了,前提是得有個男/女朋友,不然,還是買點護膚品慰勞下自己的右手吧;

小結

本文主要介紹了情緒分析的內容,有手動統計,也有利用BAT平臺的介面,出了某度有免費介面提供外,其他都要收費,而且不低,用來除錯或者內部用用,用某度的挺好的,量多可能會收費,但沒找到具體文件,不糾結了;

同時學習了Py的docopt模組,會抽取幫助資訊裡的內容,然後對命令列傳入的引數進行解析;

而在試用BAT平臺時,會發現呼叫介面都需要安全憑證/授權校驗,目的還是為了安全性,這塊是值得學習的,回想下,內部介面是否無需校驗就可直接呼叫?是否會被第三方利用的可能?

最好,祝有女朋友的,幸福美滿,避開所有障礙,早日拉埋天窗; 沒女朋友的,學會聊天,保持自信,別太死板,最重要是有上進心,陽光活力,換位思考,如果你是女生,你會喜歡自己嗎?

最後,謝謝大家!

1-140R3154U8.jpg-9kB