庆祝祖国母亲73岁生日!
引
今年国庆来临之时,中国新闻网在知乎组织了国庆话题问答,问题是“2022 年 10 月 1 日是中华人民共和国 73 岁生日,近年来我国取得的哪些成就让你感到自豪?”。
看着来自五湖四海的答案,作为一个数据分析萌新,我突发奇想:为何不将大家的回答汇总,绘制一幅词云图呢?
说干就干。
完整代码见文末
1.数据获取
最初我本想通过抓包拿到知乎API,可能是因为自己JS逆向技术水平太低,折腾了一两天仍然没弄懂。无奈之下,我选择通过selenium模拟访问知乎网页版来收集数据。
selenium的下载安装我就不再赘述了。我们的目标是尽可能多的获取所有回答的文字信息,因此网页图片请求可以暂时禁用,以减少内存占用,代码如下:
chrome_options = webdriver.ChromeOptions() prefs = {'profile.managed_default_content_settings.images': 2} chrome_options.add_experimental_option('prefs', prefs) driver = webdriver.Chrome(options=chrome_options) driver.get("https://www.zhihu.com/question/556882527") sleep(3)
借助F12开发者工具,我们可以定位所有回答的网页元素xpath路径,使用for循环依次定位,再通过lxml库收集元素节点下的所有文字信息。
这里需要注意的是,许多回答文字中包含超链接,这对我们的词云没有太大意义,于是我们可以通过正则匹配将其替换成空格来去除,正则表达式如下:
https?://[a-zA-Z0-9\./_#&%\?_=-]+
所有已获得的数据全部以文本形式存入txt文件中,每一个回答占一行。一些回答可能仅包括视频,可能仅占一个空行。
在实际操作中会发现,知乎使用的是ajax动态加载回答内容,一些回答文字可能并未加载,因此我们需要写一个异常捕获,当检测到目标元素不存在时,就把页面下拉到最底部并等待1秒。为了更加保险,以上过程可以写一个3次循环,代码如下:
driver.find_element(By.XPATH, "/html/body/div[5]/div/div/div/div[2]/button").click() format_ = "/html/body/div[1]/div/main/div/div/div[3]/div[1]/div/div/div/div/div/div[2]/div/" file = open('answer_text.txt', mode='w', encoding='utf-8') for i in range(1, 1000): # 计划获取1000条数据,实际数会更小 for _ in range(3): try: answer = driver.find_element(By.XPATH, format_ + f"div[{i}]/div/div/div[2]/span[1]/div/span") content = etree.HTML(answer.get_attribute('innerHTML')).xpath('string(.)') # 获取节点下所有文字信息 content = re.sub(r'https?://[a-zA-Z0-9\./_#&%\?_=-]+', ' ', content) # 将超链接替换为空格 file.write(content+'\n') if i % 10 == 0: print(i) # 实时显示已获取回答数 except NoSuchElementException as e: driver.execute_script("window.scrollTo(0,document.body.scrollHeight)") # 滑动至页面最底部 sleep(1) except StaleElementReferenceException as e: driver.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(1) break
最终获取到五百多条回答,共64201字。
2.绘制词云
使用jieba库完成中文分词:
# 读取文本 with open("answer_text.txt", encoding="utf-8") as f: s = f.read() ls = jieba.lcut(s) # 生成分词列表 text = ' '.join(ls) # 连接成字符串
诸如“我”“了”“也”等停用词对最终词云无价值,故我们应该剔除这些词和字符。我使用的是来源网络的中文停用词库,最后使用wordcloud库生成词云:
stopwords = [line.strip() for line in open('stop_words.txt', 'r', encoding='utf-8').readlines()] # 去掉不需要显示的词 wc = wordcloud.WordCloud(font_path="msyh.ttc", width=1000, height=700, margin=2, mask=np.array(Image.open(r"D:\Drivers\Download\china_map.png")), # 这里把中国地图作为词云的轮廓图 background_color='white', max_words=100, stopwords=set(stopwords)) wc.generate(text) # 加载词云文本 wc.to_file("知乎词云.png") # 保存词云文件
最终词云如下:
尾
我最初的那份感动,大概源于初中时阅读《红星照耀中国》(又名《西行漫记》)吧。凭着对红军的敬佩,我开始更加深入地了解历史。遥想高中,更是一发不可收拾。
团员、积极分子……你问我下一步在哪?我没怎么想。未来,不论何时、不论何地,为人民服务始终是最高尚的事业的标准。
回望这73年沉重的历史,我们站起来已有73年了……千言万语,欲说不出。看了所有人的回答后,不得感慨道:
人民万岁!
完整代码
知乎回答.py:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException from time import sleep from lxml import etree import re chrome_options = webdriver.ChromeOptions() prefs = {'profile.managed_default_content_settings.images': 2} chrome_options.add_experimental_option('prefs', prefs) driver = webdriver.Chrome(options=chrome_options) driver.get("https://www.zhihu.com/question/556882527") sleep(3) driver.find_element(By.XPATH, "/html/body/div[5]/div/div/div/div[2]/button").click() format_ = "/html/body/div[1]/div/main/div/div/div[3]/div[1]/div/div/div/div/div/div[2]/div/" file = open('answer_text.txt', mode='w', encoding='utf-8') for i in range(1, 1000): for _ in range(3): try: answer = driver.find_element(By.XPATH, format_ + f"div[{i}]/div/div/div[2]/span[1]/div/span") content = etree.HTML(answer.get_attribute('innerHTML')).xpath('string(.)') content = re.sub(r'https?://[a-zA-Z0-9\./_#&%\?_=-]+', ' ', content) file.write(content+'\n') if i % 10 == 0: print(i) except NoSuchElementException as e: driver.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(1) except StaleElementReferenceException as e: driver.execute_script("window.scrollTo(0,document.body.scrollHeight)") sleep(1) break driver.quit() file.close()
词云生成.py:
import jieba import wordcloud from PIL import Image import numpy as np # 读取文本 with open("answer_text.txt", encoding="utf-8") as f: s = f.read() ls = jieba.lcut(s) # 生成分词列表 text = ' '.join(ls) # 连接成字符串 stopwords = [line.strip() for line in open('stop_words.txt', 'r', encoding='utf-8').readlines()] # 去掉不需要显示的词 wc = wordcloud.WordCloud(font_path="msyh.ttc", width=1000, height=700, margin=2, mask=np.array(Image.open(r"D:\Drivers\Download\china_map.png")), background_color='white', max_words=100, stopwords=set(stopwords)) wc.generate(text) # 加载词云文本 wc.to_file("知乎词云.png") # 保存词云文件