Python生成词云--豆瓣电影短评(初学菜鸡版)
目录1.主要涉及的库
-
主要页面处理
selenium
-
数据处理,输出、读取CSV
pandas、numpy
-
对所有数据进行分词处理
jieba
-
处理图片,生成词云图
wordcloud、PIL、matplotlib
-
多线程爬取数据
threading
2.获取数据
-
大致思路
-
手动进入影片的短评页面,并查看URL为:
-
多次进行换页,查看URL的变化规律,如第二页:
https://movie.douban.com/subject/4202982/comments?start=20&limit=20&sort=new_score&status=P
-
可得知主要需要以下信息
subject后的数字为影片ID,start=为“此页从第几条数据开始”(每页20条,第二页start=20实则为第21条开始)
所以我们需要获取的信息为影片ID
-
-
获取影片ID
在豆瓣电影页面,按名称搜索后,结果列表的第一个即为我们需要的影片
其实总觉得这样不严谨,但多次尝试后,发现只要影片名称输入正确,暂时均为列表第一个结果
毕竟是“菜鸡版”,先凑合着用吧 ╮(╯▽╰)╭
此时我们可以直接通过selenium获取到ID,相关代码:
# 搜索结果页 url_search = 'https://search.douban.com/movie/subject_search?search_text=' + name + '&cat=1002' driver.get(url_search) driver.implicitly_wait(5) url = driver.find_element_by_xpath("//a[@class='title-text']").get_attribute('href') mid = url.split('/')[-2] name_display = driver.find_element_by_xpath("//a[@class='title-text']").text
name:脚本执行时,通过手动输入的影片名称,用于拼接搜索结果页的URL
url:本影片的页面URL
mid:影片ID,通过斜杠'/'分割URL字符串,并取倒数第二个结果
name_display:影片在系统的显示名称
-
定义一个列表,把所需用到短评URL先保存起来。
也可以每获取一页数据,再拼接出下一页的URL,先保存到列表只是我的个人习惯
相关代码:
# 定义短评URL列表 url_comment_page_list = [] # 目前豆瓣即使登录,也只能显示最多500条(25页) for page_num in range(0, 520, 20): url_comment_temp = 'https://movie.douban.com/subject/' + movie_id + '/comments?start=' + str(page_num) + '&limit=20&sort=new_score&status=P' url_comment_page_list.append(url_comment_temp)
-
逐页获取短评数据,并插入到pandas的DataFrame。
所需获取的参数,可根据个人需要而修改。
实际上短评ID、用户名、用户域名,都是因为我程序(除了短评,还有长篇影评的菜鸡版)是涉及sqlite3数据库的,我的库表需要有相关的保存和关联。
在主函数部分定义一个DataFrame:
# 创建DataFrame data_total = pd.DataFrame(columns=('短评内容', '短评ID', '用户名', '用户域名'))
相关代码:
备注:
这段代码,我是偷懒把最后的多线程版直接贴出来了……其实单线程就够了,只需做如下修改:
1.前两行修改,只需通过for循环遍历短评url列表:
for page in range url_comment_page_list: driver.get(page)
2.最后几行,删除多线程的同步锁相关代码:
thread_lock.acquire() thread_lock.release()
for page in range(list_num, list_num+5): driver.get(url_comment_page_list[page]) sleep(1) # 获取comment列表的内容 for comment_info in driver.find_elements_by_xpath("//div[@class='comment-item']"): # 获取用户名 user_name = comment_info.find_element_by_xpath(".//div/a").get_attribute('title') # 获取用户域名 url_user = comment_info.find_element_by_xpath(".//div/a").get_attribute('href') user_link = url_user.split('/')[-2] # 获取comment ID comment_id = comment_info.get_attribute('data-cid') # 获取comment内容 comment_content = comment_info.find_element_by_xpath(".//div[@class='comment']/p").text global data_total # 插入数据到DataFrame,每次只允许一个线程操作数据 thread_lock.acquire() data_total = data_total.append(pd.DataFrame({'短评内容': [comment_content], '短评ID': [comment_id], '用户名': [user_name], '用户域名': [user_link]}), ignore_index=True) thread_lock.release()
-
将DataFrame的数据输出到CSV文件,可使用to_csv()方法
相关代码:
# 导出到CSV comment_total = data_total csv_path = 'comment_source/' + movie_name_display + '短评.csv' comment_total.to_csv(csv_path, index_label='序号')
3.生成词云图
-
读取CSV内容。
前四行代码,是因为我通过数据库保存了影片显示名称、ID,所以通过ID查询名称,再利用名称拼接CSV文件路径。
如果不使用数据库,其实完全可以在“file_path=”这行开始,直接定义CSV文件路径。
最后两行的【进行分词】【生成词云图】函数,会在第二第三步提到。
movie_id = str(input('请输入影片ID:')) # 查询对应影片的显示名称 sql_search_movie_name = "SELECT movie_name FROM table_movie_info WHERE movie_id = ?" movie_name_display_temp = cur_now.execute(sql_search_movie_name, (movie_id,)) movie_name_display = movie_name_display_temp.fetchone()[0] file_path = 'comment_source/' + movie_name_display + '短评.csv' str_uncut = pd.read_csv(file_path, usecols=[1], skiprows=[0]) str_uncut = str_uncut.to_string(index=False) # 进行分词 str_source = get_str_csv() # 生成词云图 create_wordcloud(str_source)
-
通过jieba库进行分词
对字符串str_uncut进行分词的话,只需要使用方法jieba.cut(str_uncut)即可。
def get_str_csv(): # 自定义词库 for to_del_temp in common_methods.to_del_list_total: jieba.del_word(to_del_temp) # 对string分词 cut_str = " ".join(jieba.cut(str_uncut)) return cut_str
上述代码使用到jieba库的自定义词库相关方法,可使用add_word()、del_word()、suggest_freq()进行动态调整词库,即只在本次执行有效。
这次我只使用了del_word(),去排除一些语法相关的词语,例如连接词。还可以考虑把影片里的角色名称排除,不然人名的词频一般都比较高。
至于我为什么要分这么多个list,最后再拼起来……是因为太长了我觉得看得不顺眼,顺便按词语分个类而已……Σ(っ °Д °;)っ
而且这个自定义词库,还有很大的调整空间。
我是写在另一个py文件common_methods里的,所以前面调用时是common_methods.to_del_list_total
# 自定义排除词库 to_del_list_1 = ('虽然', '但是', '还是', '因为', '所以', '不但', '而且') to_del_list_2 = ('一个', '可以', '这个', '那个', '这样', '那样', '这么', '那么', '不是', '只是', '就是', '真的', '没有') to_del_list_3 = ('非常', '知道', '其实') to_del_list_total = to_del_list_1 + to_del_list_2 + to_del_list_3
-
生成词云图
# 生成词云图片 def create_wordcloud(jieba_source): # 定义背景图、字体路径 path_img = '输入你的图片路径' font_path = '输入你的字体路径' background_img = np.array(Image.open(path_img)) # mask参数=图片背景,必须要写上,另外有mask参数再设定宽高是无效的 wordcloud = WordCloud(font_path=font_path, background_color='white', mask=background_img).generate(jieba_source) # 生成颜色值 image_colors = ImageColorGenerator(background_img) plt.imshow(wordcloud.recolor(color_func=image_colors), interpolation='bilinear') plt.axis('off') plt.show()
-
执行结果