# Day5- 综合大作业

**第一步：爱奇艺《青春有你2》评论数据爬取**(参考链接：<https://www.iqiyi.com/v\\_19ryfkiv8w.html#curid=15068699100\\_9f9bab7e0d1e30c494622af777f4ba39>)

* 爬取任意一期正片视频下评论
* 评论条数不少于1000条

**第二步：词频统计并可视化展示**

* 数据预处理：清理清洗评论中特殊字符（如：@#￥%、emoji表情符）,清洗后结果存储为txt文档
* 中文分词：添加新增词（如：青你、奥利给、冲鸭），去除停用词（如：哦、因此、不然、也好、但是）
* 统计top10高频词
* 可视化展示高频词

**第三步：绘制词云**

* 根据词频生成词云
* 可选项-添加背景图片，根据背景图片轮廓生成词云

**第四步：结合PaddleHub，对评论进行内容审核**

这个题目整体难度并不大，已经预先给定了接口的名称，相应的实现也比较简单，唯一的缺点可能是函数`doc string`的不完善，让我对某些函数的作用有点云里雾里，估计也是赶工出来的，辛苦了。

这个题整体的思路是先使用request爬取爱奇艺视频的评论，在去除特殊字符后使用中文jieba分词对其进行分词，再使用`matplotlib`进行绘图和使用词云`wordcloud`进行词云的绘制，最后使用和谐词检测预训练模型`porn-lstm`对评论中的和谐词进行推断。

\
需要的配置和准备
--------

* 中文分词需要jieba
* 词云绘制需要wordcloud
* 可视化展示中需要的中文字体
* 网上公开资源中找一个中文停用词表
* 根据分词结果自己制作新增词表
* 准备一张词云背景图（附加项，不做要求，可用hub抠图实现）
* paddlehub配置

```python
# !pip install jieba -i https://pypi.tuna.tsinghua.edu.cn/simple -t /home/aistudio/external-libraries
# !pip install wordcloud -i https://pypi.tuna.tsinghua.edu.cn/simple -t /home/aistudio/external-libraries
# !pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple -t /home/aistudio/external-libraries

import sys
sys.path.append('/home/aistudio/external-libraries')
```

```bash
# Linux系统默认字体文件路径
# !ls /usr/share/fonts/
# 查看系统可用的ttf格式中文字体
# !fc-list :lang=zh | grep ".ttf"
```

```bash
# !wget https://mydueros.cdn.bcebos.com/font/simhei.ttf # 下载中文字体
# !wget https://github.com/StellarCN/scp_zh/raw/master/fonts/SimHei.ttf
# 创建字体目录fonts
# !mkdir .fonts
# # 复制字体文件到该路径
# !cp simhei.ttf .fonts/
# !cp simhei.ttf /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/mpl-data/fonts/ttf/
```

```bash
#安装模型
# !hub install porn_detection_lstm==1.1.0
# !pip install --upgrade paddlehub -t /home/aistudio/external-libraries -i 
```

```python
from __future__ import print_function
import json
import re #正则匹配
import time #时间处理模块
import jieba #中文分词
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
from PIL import Image
from wordcloud import WordCloud  #绘制词云模块
import paddlehub as hub
import requests
```

```python
#请求爱奇艺评论接口，返回response信息
def getMovieinfo(url):
    '''
    请求爱奇艺评论接口，返回response信息
    参数  url: 评论的url
    :return: response信息
    '''
    headers = { 
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
    }
    session=requests.Session()
    response=session.get(url,headers=headers)
    assert response.status_code==200,"网页返回状态码异常"
    return response.text


#解析json数据，获取评论
def saveMovieInfoToFile(lastId,arr):
    '''
    解析json数据，获取评论
    参数  lastId:最后一条评论ID  arr:存放文本的list
    :return: 新的lastId
    '''
    url='https://sns-comment.iqiyi.com/v3/comment/get_comments.action?agent_type=118&agent_version=9.11.5&authcookie=null&business_type=17&content_id=15068699100&hot_size=0&last_id='+str(lastId)
    resJson=json.loads(getMovieinfo(url))
    comments=resJson['data']['comments']
    arr+=[comment['content'] for comment in comments if 'content' in comment]
    # 返回最后一项的id
    return comments[-1]['id']
```

这里使用的`session.get()`是为了让我们多次的请求都保持同一个`cookie`，和`user_agent`一样在某种程度上让服务器认为我们是浏览器，防止被反爬虫（当然，假如你访问频次过快，还是会被ban ip的）。

```python
headers = { 
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
response=session.get(url,headers=headers)
```

注意我们在[Day2-《青春有你2》选手信息爬取](/paddle7/day2-qing-chun-you-ni-2-xuan-shou-xin-xi-pa-qu.md)中所说的request是http静态库，无法渲染JS，而用chrome审查元素可以发现，评论都是通过JavaScript动态生成的。那怎么办呢？在不引入新的库的前提下我们可以通过逆向请求接口的方式来完成爬取。我们在爱奇艺的页面中下拉滚轮时会通过JS加载新的评论，而这些评论是通过下拉滚轮时请求网络相应的接口，将接口返回的JSON信息通过JavaScript动态渲染到浏览器上完成的动态加载。因此我们可以直接通过接口获取相应的JSON信息，而不需要再管JS的渲染。

通过观察可以看到`lastId`是我们接口请求的关键，我们一开始可以把`lastid`置0获得最初的评论，然后将返回的Json信息中最后一条评论的`lastId`返回，以此迭代以获得新的评论。

```python
#去除文本中特殊字符
def clear_special_char(content):
    '''
    正则处理特殊字符
    参数 content:原文本
    return: 清除后的文本
    '''
    # 只保留中英文字符和数字
    return re.findall(r'[\u4e00-\u9fff^A-Za-z0-9]+', content)
```

```python
def fenci(text):
    '''
    利用jieba进行分词
    参数 text:需要分词的句子或文本
    return：分词结果
    '''
    
    seg=jieba.lcut(text,cut_all=False)
    return seg
```

```python
def stopwordslist(file_path):
    '''
    创建停用词表
    参数 file_path:停用词文本路径
    return：停用词list
    '''
    with open(file_path, 'r',encoding='UTF-8') as f:
        words = f.read().splitlines()
    return words
```

这里的停用词表对应的文本路径中应该要依据最后的词频图和词云的效果，添加相应的停用词使得其显示效果更好。我在其加入了几个明星的名字颠倒的词汇（我也不知道为什么会在评论里出现这种词，莫非是我跟不上时代了吗= =）。

```python
def movestopwords(sentence,stopwords,counts):
    '''
    去除停用词,统计词频
    参数 sentence:分词后的句子 stopwords:停用词list counts: 词频统计结果
    return：None
    '''

    for word in sentence:
        if word not in stopwords and len(word)!=1:
            counts[word]=counts.get(word,0)+1
    return None
```

```python
def drawcounts(counts,num):
    '''
    绘制词频统计表
    参数 counts: 词频统计结果 num:绘制topN
    return：none
    '''
    # 由于python的dict是使用hash实现的无序字典而非红黑树实现的有序字典 因此需要sort
    counts=sorted(counts.items(), key=lambda item: item[1])
    # 将top N个元组映射到一个list中
    word_fre=list(map(list, zip(*counts[-num:][::-1])))
    matplotlib.rcParams['font.sans-serif']=['SimHei']
    matplotlib.rcParams['axes.unicode_minus']=False
    plt.bar(word_fre[0],word_fre[1])
    plt.title('词频统计结果')
    plt.show()
    return None


```

```python
def drawcloud(word_f):
    '''
    根据词频绘制词云图
    参数 word_f:统计出的词频结果
    return：none
    '''
    
    mask=np.array(Image.open('Miku.png'))
    wc=WordCloud(background_color='white',mask=mask,max_words=150,font_path='simhei.ttf',min_font_size=10,max_font_size=100,width=400,relative_scaling=0.3)
    wc.fit_words(word_f)
    wc.to_file('pic.png')
    return None
```

```python
def text_detection(file_path):
    '''
    使用hub对评论进行内容分析
    return：分析结果
    '''
    porn_res=[]
    with open(file_path,'r') as f:
        porn=hub.Module(name='porn_detection_lstm')
        # 不需要detect重复的句子 使用集合去重
        comments={x.strip() for x in f if len(x.strip())!=1}
        res=porn.detection(data={'text':list(comments)},use_gpu=True,batch_size=50)
        for index,item in enumerate(res):
            if item['porn_detection_key']=='porn':
                porn_res.append(item['text']+":"+str(item['porn_probs']))
    return porn_res
```

这里一开始`text_detection()`的运行速度太慢了，后面调试发现是判断了多句重复的句子，而重复的句子的和谐度都是一样的，所以用Python的集合`set()`进行去重再转为列表`list`进行和谐度判断，果然快了很多。

```python
#评论是多分页的，得多次请求爱奇艺的评论接口才能获取多页评论,有些评论含有表情、特殊字符之类的
#num 是页数，一页10条评论，假如爬取1000条评论，设置num=100
if __name__ == "__main__":

    stopwords_file='baidu_stopwords.txt'
    clear_comments_file='comments.txt'
    num=100
    use_text=1

    arr=[]
    counts={}
    lastId=0
    for i in range(num):
        lastId=saveMovieInfoToFile(lastId,arr)
        time.sleep(0.5)
    # 评论写入到txt中 使用with可以不需要在意文件的关闭
        with open(clear_comments_file,'a') as f:
            for comment in arr:
                clear_comment=clear_special_char(comment)
                for sentence in clear_comment:
                    if sentence.strip()!='':
                        try:
                            f.write(sentence+'\n')
                        except Exception as e:
                            print('有特殊字符')
    print(len(arr))
    for comment in arr:
        movestopwords(fenci(comment),stopwordslist(stopwords_file),counts)
    drawcounts(counts,10)
    drawcloud(counts)

    porn_all=text_detection(clear_comments_file)
    # 打印和谐信息
    for porn_mes in porn_all:
        print(porn_mes)
```

这里也是强烈建议大家使用<br>

```python
with open(clear_comments_file,'a') as f:
```

而不是`open()`，这可以避免忘记关闭文件导致的IO错误。

运行结果如下：

```
2977
```

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M6-2mjSeWmltxkdBbII%2Fuploads%2FkHGoSKcKbbSvZdsFKG2I%2Ffile.png?alt=media)

```
[2020-04-28 12:19:05,318] [    INFO] - Installing porn_detection_lstm module
[2020-04-28 12:19:05,320] [    INFO] - Module porn_detection_lstm already installed in /home/aistudio/.paddlehub/modules/porn_detection_lstm
```

```
你们四个要加油啊啊啊啊啊:0.6599
加油看好你哟:0.9992
看好你哟:0.9981
加油啊啊啊啊啊啊啊啊啊:0.5932
爱你哟:0.974
好好一个选秀节目愣是拍出了连续剧的感jio:0.6033
喻言棒棒:0.8105
啊啊啊啊妈妈爱你嗷嗷嗷嗷嗷:0.5749
她的舞蹈也是棒棒的:0.9864
怎么没有了呢:0.7035
你很棒:0.7683
看好你呢:0.9453
爱你呦:0.9964
我觉得你真的很棒:0.6048
美女:0.9938
什么狗屁节目:0.9428
加油啊啊啊啊啊啊:0.5409
超甜美的姐姐加油啊啊啊啊:0.6127
虞书欣棒棒哒:0.6694
许佳琪宝贝棒棒的:0.9817
因为我看了他演的两部电视剧:0.7992
我很喜欢ysx:0.8244
兔子姐姐我真的超级喜欢你呜呜呜呜呜:0.8929
美女加油:0.7494
加油啊啊啊啊啊啊啊啊啊啊啊啊啊:0.6497
```

```python
display(Image.open('pic.png')) #显示生成的词云图像
```

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M6-2mjSeWmltxkdBbII%2Fuploads%2Fa2d9P3IjPMw0HP5DvphY%2Ffile.png?alt=media)

词云这里使用的原图是miku，可以看到效果还是可以滴：

![](/files/-M6-gOsyx8eM6FKUkVPR)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mr-et.gitbook.io/paddle7/day5-zong-he-da-zuo-ye.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
