From 1cd862fdc67a44714678ad50500aaa02ba75aa8c Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Thu, 25 Feb 2016 01:31:41 -0500 Subject: [PATCH 1/7] .gitignore and message scraper initialized --- .gitignore | 1 + message_scrape.py | 0 2 files changed, 1 insertion(+) create mode 100644 .gitignore create mode 100644 message_scrape.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2211df6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/message_scrape.py b/message_scrape.py new file mode 100644 index 0000000..e69de29 From 50c463a9910a4bb1edc24c6f130147722c457124 Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Thu, 25 Feb 2016 11:49:14 -0500 Subject: [PATCH 2/7] functionalizing, adding docstrings --- message_scrape.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/message_scrape.py b/message_scrape.py index e69de29..9d2fa12 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -0,0 +1,39 @@ +""" +This project takes a formatted Facebook message dump, messages.htm, scrapes it +for the most frequent words in a message thread I specified, and then +returns a word cloud of the most popular words in the thread. +""" +from bs4 import BeautifulSoup +from wordcloud import WordCloud +import string + +# read in messages.htm +bs = BeautifulSoup(open('messages.htm'), 'lxml') +# get to the correct thread +def get_msgs(beautifulsoup): + content = bs.body.contents[1].div.contents[70] + # get all the messages without any of the metadata + tagged_msgs = content.contents[2::2] + # strip p tags + msgs = [str(m)[3:-4] for m in tagged_msgs] + # make them into a string + return " ".join(msgs) + +def word_cloud(text): + """ + This function makes a word_cloud object and + """ + wc = WordCloud() + wc.generate(text) + +#assemble a frequency list +words = dict() +for raw in str_msgs.split(): + word = raw.rstrip(string.punctuation).lstrip(string.punctuation).lower() + words[word] = words.get(word, 0) + 1 + +output = [(words[word], word) for word in words.keys()] +output.sort(reverse=True) + +# print(str(len(output)) + " different fucking words.") +# print("\n".join([str(e) for e in output])) From 70f1975d2c8a64287763a2231ee1d4c99f42ec54 Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Thu, 25 Feb 2016 12:47:57 -0500 Subject: [PATCH 3/7] functionally decomposed nicely, command-line interface implemented, search still broken --- message_scrape.py | 77 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/message_scrape.py b/message_scrape.py index 9d2fa12..0109358 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -6,14 +6,25 @@ from bs4 import BeautifulSoup from wordcloud import WordCloud import string +import sys -# read in messages.htm -bs = BeautifulSoup(open('messages.htm'), 'lxml') -# get to the correct thread -def get_msgs(beautifulsoup): - content = bs.body.contents[1].div.contents[70] +def get_msgs(filename, threadname): + """ + This function takes a filename object that is the entire message dump + and returns the block of messages with the thread being searched for + """ + # read in file + bs = BeautifulSoup(open(filename), 'lxml') + # get to the correct thread + block = bs.body.contents[1].div.contents[70] + return block + +def strip_msgs(msg_block): + """ + Strips all the messages out of the block of text and removes their p tags + """ # get all the messages without any of the metadata - tagged_msgs = content.contents[2::2] + tagged_msgs = msg_block.contents[2::2] # strip p tags msgs = [str(m)[3:-4] for m in tagged_msgs] # make them into a string @@ -21,19 +32,53 @@ def get_msgs(beautifulsoup): def word_cloud(text): """ - This function makes a word_cloud object and + This function makes a wordcloud object and attempts to generate a word cloud + using the collected messages. """ wc = WordCloud() wc.generate(text) -#assemble a frequency list -words = dict() -for raw in str_msgs.split(): - word = raw.rstrip(string.punctuation).lstrip(string.punctuation).lower() - words[word] = words.get(word, 0) + 1 +def get_freq(text): + """ + This function makes a frequency dictionary of all of the words it's given + """ + words = dict() + for raw in text.split(): + word = raw.rstrip(string.punctuation).lstrip(string.punctuation).lower() + words[word] = words.get(word, 0) + 1 + return words + +def decorate_sort(dictionary): + """ + This function takes a dictionary and sorts it, returning it as an ordered + list of tuples of frequency-word pairs + """ + output = [(dictionary[word], word) for word in dictionary.keys()] + output.sort(reverse=True) + return output + +if __name__ == '__main__': + #prompt user for input + print 'How many words to display: ' + words = int(raw_input()) + print 'What thread to look for: ' + thread = raw_input() + + #Extract message data + block = get_msgs('messages.htm', thread) + text = strip_msgs(block) + + #try to make a word cloud + try: + word_cloud(text) + except ImportError as e: + print e + print "Probably a libfreetype error again" -output = [(words[word], word) for word in words.keys()] -output.sort(reverse=True) + # Generate sorted frequency list + freq = get_freq(text) + freq = decorate_sort(freq) -# print(str(len(output)) + " different fucking words.") -# print("\n".join([str(e) for e in output])) + print 'The {} most used words in our chat are: '.format(words) + string_freq = [str(e) for e in freq] + print "\n".join(string_freq[:words]) From fc04d2f01ff23d056a05f437a62c14bd3e4ad431 Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Thu, 25 Feb 2016 13:08:38 -0500 Subject: [PATCH 4/7] no searching, but command line interface fixed --- message_scrape.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/message_scrape.py b/message_scrape.py index 0109358..a119a9f 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -8,6 +8,7 @@ import string import sys + def get_msgs(filename, threadname): """ This function takes a filename object that is the entire message dump @@ -43,10 +44,12 @@ def get_freq(text): This function makes a frequency dictionary of all of the words it's given """ words = dict() + total_words = 0 for raw in text.split(): - word = raw.rstrip(string.punctuation).lstrip(string.punctuation).lower() - words[word] = words.get(word, 0) + 1 - return words + total_words += 1 + word = raw.rstrip(string.punctuation).lstrip(string.punctuation).lower() + words[word] = words.get(word, 0) + 1 + return words, total_words def decorate_sort(dictionary): """ @@ -76,9 +79,10 @@ def decorate_sort(dictionary): print "Probably a libfreetype error again" # Generate sorted frequency list - freq = get_freq(text) + freq, total = get_freq(text) freq = decorate_sort(freq) - print 'The {} most used words in our chat are: '.format(words) + print 'Of {} total words in the chat,'.format(total) + print 'the {} most used words in our chat are: '.format(words) string_freq = [str(e) for e in freq] print "\n".join(string_freq[:words]) From 9b0d30d6ef55cc4a1f579f96bb0a8a5a26dd2768 Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Thu, 25 Feb 2016 13:13:33 -0500 Subject: [PATCH 5/7] Added reflection --- Text_Mining_Reflection_AHOPPE.pdf | Bin 0 -> 40761 bytes message_scrape.py | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 Text_Mining_Reflection_AHOPPE.pdf diff --git a/Text_Mining_Reflection_AHOPPE.pdf b/Text_Mining_Reflection_AHOPPE.pdf new file mode 100644 index 0000000000000000000000000000000000000000..05b613b09f7d4e524ed2fe84f27aa5f21a21f7f0 GIT binary patch literal 40761 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 VYXWgS6yaD59L**srmCyP_Fw7n3kLuI literal 0 HcmV?d00001 diff --git a/message_scrape.py b/message_scrape.py index a119a9f..8910e15 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -2,6 +2,9 @@ This project takes a formatted Facebook message dump, messages.htm, scrapes it for the most frequent words in a message thread I specified, and then returns a word cloud of the most popular words in the thread. + +I opted out of unit tests for BeautifulSoup things because the way it does data +types is difficult and confusing. """ from bs4 import BeautifulSoup from wordcloud import WordCloud From 8d81d23ccc46ca4cac283a457561a2be16eaafc6 Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Mon, 18 Apr 2016 19:25:45 -0400 Subject: [PATCH 6/7] changed output file a long time ago --- message_scrape.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/message_scrape.py b/message_scrape.py index 8910e15..e82178c 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -4,7 +4,7 @@ returns a word cloud of the most popular words in the thread. I opted out of unit tests for BeautifulSoup things because the way it does data -types is difficult and confusing. +types is difficult and confusing. """ from bs4 import BeautifulSoup from wordcloud import WordCloud @@ -41,6 +41,7 @@ def word_cloud(text): """ wc = WordCloud() wc.generate(text) + wc.to_file('test.png') def get_freq(text): """ From 3ea0c02b0c129a02c2f56ef47771e5f91f38117d Mon Sep 17 00:00:00 2001 From: Alexander Hoppe Date: Tue, 19 Apr 2016 09:27:17 -0400 Subject: [PATCH 7/7] added markov synthesis chat --- markov_chat.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++ message_scrape.py | 13 ++++++++++ 2 files changed, 77 insertions(+) create mode 100644 markov_chat.py diff --git a/markov_chat.py b/markov_chat.py new file mode 100644 index 0000000..f89e5ac --- /dev/null +++ b/markov_chat.py @@ -0,0 +1,64 @@ +""" This program is an extension of the Text Mining mini project I did on the +text in our Facebook group chat. It synthesizes a semi-interactive group chat +exchange """ +import message_scrape as ms +import random +import pickle +import datetime + +def start_chat(trained_dict): + print 'Welcome to the 2015 Comrades Markov chat!\nPress ENTER to get started!' + while True: + raw_input() + print create_msg(trained_dict) + +def train(message_block): + """This method will use a dictionary full of lists to capture every token + that could come after any given token in a set""" + tokens = message_block.split() + td = {token:[] for token in tokens} + for i in range(len(tokens) - 1): + td[tokens[i]].append(tokens[i+1]) + return td + + +def create_msg(t_dict): + """this function creates a message by adding on messages from the training set + until it reaches a newline character""" + _run = True + msg = '' + last = '' + nxt = '' + while _run: + if not last: + #pick a random value from a random key + nxt = random.choice(t_dict[random.choice(t_dict.keys())]) + else: + #pick a random value from the last key + nxt = random.choice(t_dict[last]) + if nxt == '|': + _run = False + nxt = '\n' + #update step + msg += nxt + " " + last = nxt + + if msg == "\n ": + msg = ':thumbs-up:\n' + return str(datetime.datetime.now().time()) +' > '+ msg + +if __name__ == '__main__': + #check if we've already trained this thing + try: + f = open('t_dict.txt', 'r+') + t_dict = pickle.load(f) + except IOError: + f = open('t_dict.txt', 'w') + #scrape the messages + block = ms.get_msgs('messages.htm', 'DEFAULT') + text = ms.strip_markov(block) + #train the data structure + t_dict = train(text) + pickle.dump(t_dict, f) + #start the chatbot + start_chat(t_dict) diff --git a/message_scrape.py b/message_scrape.py index e82178c..3ca3679 100644 --- a/message_scrape.py +++ b/message_scrape.py @@ -34,6 +34,17 @@ def strip_msgs(msg_block): # make them into a string return " ".join(msgs) +def strip_markov(msg_block): + """ + Strips all the messages out of the block of text and removes their p tags, but leaves them as a list + """ + # get all the messages without any of the metadata + tagged_msgs = msg_block.contents[2::2] + # strip p tags + msgs = [str(m)[3:-4] for m in tagged_msgs] + # add a special designator for End of Message + return " |\n".join(msgs) + def word_cloud(text): """ This function makes a wordcloud object and attempts to generate a word cloud @@ -75,6 +86,8 @@ def decorate_sort(dictionary): block = get_msgs('messages.htm', thread) text = strip_msgs(block) + strip_markov(block) + #try to make a word cloud try: word_cloud(text)