Spaces:
Runtime error
Runtime error
Merge branch 'develop' into 37-test-twitter-scraper
Browse files- .idea/politweet.iml +1 -2
- README.md +8 -2
- data/twitterdata.csv +19 -0
- requirements.txt +1 -0
- textclassifier/TextClassifier.py +261 -5
- twitterscraper/TwitterScraper.py +5 -7
- twitterscraper/jimmieakesson.csv +33 -0
.idea/politweet.iml
CHANGED
@@ -3,9 +3,8 @@
|
|
3 |
<component name="NewModuleRootManager">
|
4 |
<content url="file://$MODULE_DIR$">
|
5 |
<excludeFolder url="file://$MODULE_DIR$/politweet-environment" />
|
6 |
-
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
7 |
</content>
|
8 |
-
<orderEntry type="
|
9 |
<orderEntry type="sourceFolder" forTests="false" />
|
10 |
</component>
|
11 |
<component name="PyNamespacePackagesService">
|
|
|
3 |
<component name="NewModuleRootManager">
|
4 |
<content url="file://$MODULE_DIR$">
|
5 |
<excludeFolder url="file://$MODULE_DIR$/politweet-environment" />
|
|
|
6 |
</content>
|
7 |
+
<orderEntry type="inheritedJdk" />
|
8 |
<orderEntry type="sourceFolder" forTests="false" />
|
9 |
</component>
|
10 |
<component name="PyNamespacePackagesService">
|
README.md
CHANGED
@@ -20,8 +20,14 @@ För att få alla dependencies:
|
|
20 |
|
21 |
1. skapa en virtual environment: https://docs.python.org/3/library/venv.html
|
22 |
2. Aktivera din virtual environment
|
23 |
-
|
24 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
|
27 |
|
|
|
20 |
|
21 |
1. skapa en virtual environment: https://docs.python.org/3/library/venv.html
|
22 |
2. Aktivera din virtual environment
|
23 |
+
3. gå till projektets root path och skriv i terminalen:
|
24 |
+
$ pip install -r requirements.txt
|
25 |
+
4. I vissa fall funkar det inte att installera twint för Ubuntu. Efter att ha ställt in allt funkade det efter att ha kört "sudo apt-get install build- essential" i terminalen.
|
26 |
+
5. För att använda openai behövs en auktoriserings-token. Detta skapas genom att skapa en '.env' fil i projektets root path.
|
27 |
+
6. Skriv in följande i den filen:
|
28 |
+
OPENAI_AUTHTOKEN=din open-ai token
|
29 |
+
7. Nu borde TextClassifier kunna använda openai, givet att du har timmar att lägga till din token.
|
30 |
+
|
31 |
|
32 |
|
33 |
|
data/twitterdata.csv
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
id,tweet,date,user_id,username,urls,nlikes,nreplies,nretweets
|
2 |
+
1208816689720283138,God jul https://t.co/AC6HzzxJI1,2019-12-22 19:28:52,95972673,jimmieakesson,[],3090,247,270
|
3 |
+
1206517009451814915, https://t.co/SPIXCl1y7O,2019-12-16 11:10:45,95972673,jimmieakesson,[],1554,69,97
|
4 |
+
1204392974718767105,Hej @Jonas_Gardell Läste din artikel i @Expressen vad sägs om att ses och snacka istället för att prata förbi varandra i media? Jag tror på samtalet och jag är övertygad om att jag kan lära mig något av dig. Kanske är det ömsesidigt?,2019-12-10 14:30:36,95972673,jimmieakesson,[],5207,299,634
|
5 |
+
1202639801205317633,"""Sverigedemokraterna räds inte ett extraval om misstroendeförklaringen mot arbetsmarknadsminister Eva Nordmark (S) landar i att hela regeringen avgår. – Vi är redo för nyval så klart, säger SD-ledaren Jimmie Åkesson."" https://t.co/RxRfwKVruK",2019-12-05 18:24:07,95972673,jimmieakesson,['https://www.sydsvenskan.se/2019-12-05/sd-redo-for-extraval?redirected=1'],1209,51,144
|
6 |
+
1202231839424950272,Intervju med Jimmie Åkesson med anledning av dagens möte med Ulf Kristersson. https://t.co/AOGYLUEwxC,2019-12-04 15:23:01,95972673,jimmieakesson,['https://www.svt.se/nyheter/inrikes/det-har-diskuterade-akesson-och-kristersson'],380,13,43
|
7 |
+
1202200851189846022,"Mötet var givande, konstruktivt och bådar gott inför framtida samarbeten i ett nytt politiskt landskap. / Jimmie",2019-12-04 13:19:53,95972673,jimmieakesson,[],626,13,22
|
8 |
+
1202200819354996738,"Men också hur den framtida invandringspolitiken och energipolitiken bör utformas. Även om Sverigedemokraterna och Moderaterna är två olika partier, finns det också många beröringspunkter i en rad olika sakfrågor.",2019-12-04 13:19:45,95972673,jimmieakesson,[],455,5,23
|
9 |
+
1202199793688293376,Idag besökte jag och min gruppledare Ulf Kristerssons arbetsrum för ett första direkt möte med Ulf Kristersson och Moderaternas gruppledare. På mötet diskuterades bland annat hur vi ska komma tillrätta med den kriminaliteten i vårt land som regeringen helt har misslyckats med. https://t.co/APmt4WSrUK,2019-12-04 13:15:41,95972673,jimmieakesson,[],1801,91,148
|
10 |
+
1202185719965458432,"Konstruktiv dag. Här i möte med vår nya gruppledare i riksdagen, @VingeHenrik . Stay tuned... #svpol https://t.co/LMYmKIS3ya",2019-12-04 12:19:45,95972673,jimmieakesson,[],370,14,28
|
11 |
+
1199417558291533824,Ännu ett institut bekräftar det nya läget 💪 https://t.co/9WuWsdRWGt,2019-11-26 21:00:04,95972673,jimmieakesson,[],1586,74,128
|
12 |
+
1198144200283475968,Kommuner som går på knäna. Skjutningar och sprängningar var och varannan dag. En eskalerande otrygghet som steg för steg bryter ned vårt land. Har Sverige problem? Ja. Är S-regeringen med sina stödpartier kapabla att lösa dessa problem? Nej. https://t.co/7WWRwbs1Ds,2019-11-23 08:40:12,95972673,jimmieakesson,['https://www.aftonbladet.se/debatt/a/kJPRKj/massinvandringen-har-slagit-sonder-nationen'],1937,122,292
|
13 |
+
1197790554609852416,Utvecklingen S vs SD under 2000-talet. Det närmar sig... 💪 https://t.co/MNV5oGJktQ,2019-11-22 09:14:56,95972673,jimmieakesson,[],1243,43,120
|
14 |
+
1197481419561472000,"Vi vill avsätta alla ministrar som går att avsätta i den här regeringen, så det är mycket sannolikt att vi hakar på en sådan eventuellt misstroendeomröstning. https://t.co/PHj6XipNGH",2019-11-21 12:46:33,95972673,jimmieakesson,['https://www.di.se/nyheter/sd-hakar-pa-v-om-af-hotar-avsatta-s-ministern/'],1419,86,128
|
15 |
+
1197467328105062400,Många intervjuer inför partiets Landsdagar. Några återstår innan jag rullar mot Örebro. https://t.co/hXYMhcVDAG,2019-11-21 11:50:33,95972673,jimmieakesson,[],360,16,16
|
16 |
+
1195769542456356864,Expressen inför nästa veckas Landsdagar med SD. https://t.co/vsQj18Xwaw,2019-11-16 19:24:09,95972673,jimmieakesson,[],349,21,24
|
17 |
+
1194553636946350080,Morgan Johansson måste avgå. #pldebatt #svpol https://t.co/QsVAhqvaou,2019-11-13 10:52:35,95972673,jimmieakesson,[],1713,47,203
|
18 |
+
1194528503284346881,Idag begär jag att riksdagen avsätter justitieminister Morgan Johansson. #svpol https://t.co/tL703x5eYQ,2019-11-13 09:12:43,95972673,jimmieakesson,[],1844,80,140
|
19 |
+
1194495733858222080,Åtta år senare och Morgan kämpar vidare... 🦸♂️ https://t.co/iCWrEwhgHP,2019-11-13 07:02:30,95972673,jimmieakesson,[],1769,87,271
|
requirements.txt
CHANGED
@@ -37,6 +37,7 @@ pycparser==2.21
|
|
37 |
pyparsing==3.0.9
|
38 |
PySocks==1.7.1
|
39 |
python-dateutil==2.8.2
|
|
|
40 |
python-socks==2.0.3
|
41 |
pytz==2022.1
|
42 |
regex==2022.6.2
|
|
|
37 |
pyparsing==3.0.9
|
38 |
PySocks==1.7.1
|
39 |
python-dateutil==2.8.2
|
40 |
+
python-dotenv==0.20.0
|
41 |
python-socks==2.0.3
|
42 |
pytz==2022.1
|
43 |
regex==2022.6.2
|
textclassifier/TextClassifier.py
CHANGED
@@ -1,11 +1,24 @@
|
|
1 |
import openai
|
|
|
2 |
import regex as re
|
3 |
from twitterscraper import TwitterScraper
|
4 |
from datetime import date
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
|
7 |
class TextClassifier:
|
8 |
-
def __init__(self, model_name="text-davinci-002", from_date='2022-01-01', to_date=str(date.today()),
|
|
|
|
|
|
|
9 |
"""
|
10 |
Initializes the TextClassifier.
|
11 |
:param model_name: name of the model from openai.
|
@@ -13,20 +26,263 @@ class TextClassifier:
|
|
13 |
:param to_date: string of the format 'YYYY-MM-DD'.
|
14 |
:param num_tweets: integer value of the maximum number of tweets to be scraped.
|
15 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
self.model_name = model_name
|
18 |
-
self.
|
19 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
def classify_sentiment(self, text: str):
|
22 |
"""
|
23 |
Classifies the sentiment of a text.
|
24 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
"""
|
28 |
Classifies the topics of a text.
|
|
|
29 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
def __repr__(self):
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import openai
|
2 |
+
import csv
|
3 |
import regex as re
|
4 |
from twitterscraper import TwitterScraper
|
5 |
from datetime import date
|
6 |
+
import os
|
7 |
+
from dotenv import find_dotenv, load_dotenv
|
8 |
+
|
9 |
+
# Set one directory up into ROOT_PATH
|
10 |
+
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
11 |
+
|
12 |
+
dotenv_path = find_dotenv()
|
13 |
+
load_dotenv(dotenv_path)
|
14 |
+
OPENAI_AUTHTOKEN = os.environ.get("OPENAI_AUTHTOKEN")
|
15 |
|
16 |
|
17 |
class TextClassifier:
|
18 |
+
def __init__(self, model_name="text-davinci-002", from_date='2022-01-01', to_date=str(date.today()),
|
19 |
+
|
20 |
+
user_name='jimmieakesson',
|
21 |
+
num_tweets=20, ):
|
22 |
"""
|
23 |
Initializes the TextClassifier.
|
24 |
:param model_name: name of the model from openai.
|
|
|
26 |
:param to_date: string of the format 'YYYY-MM-DD'.
|
27 |
:param num_tweets: integer value of the maximum number of tweets to be scraped.
|
28 |
"""
|
29 |
+
# Make sure to_date is later than from_date
|
30 |
+
assert from_date < to_date, "from_date must be earlier than to_date"
|
31 |
+
# Make sure the dates are in the correct format
|
32 |
+
assert re.match(r'^\d{4}-\d{2}-\d{2}$', from_date) is not None, "from_date must be in the format YYYY-MM-DD"
|
33 |
+
# Make sure user_name is not empty
|
34 |
+
assert user_name is not None, "user_name cannot be empty"
|
35 |
+
# Make sure num_tweets is a positive integer
|
36 |
+
assert num_tweets > 0, "num_tweets must be a positive integer"
|
37 |
|
38 |
self.model_name = model_name
|
39 |
+
self.from_date = from_date
|
40 |
+
self.to_date = to_date
|
41 |
+
self.num_tweets = num_tweets
|
42 |
+
self.user_name = user_name
|
43 |
+
self.ts = TwitterScraper.TwitterScraper(from_date, to_date, num_tweets)
|
44 |
+
self.df = self.ts.scrape_by_user(user_name)
|
45 |
+
# self.api_key = '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'
|
46 |
+
openai.api_key = OPENAI_AUTHTOKEN
|
47 |
+
|
48 |
+
def classify_topic_and_sentiment(self):
|
49 |
+
self.classify_topic_of_tweets()
|
50 |
+
self.classify_sentiment_of_tweets()
|
51 |
+
|
52 |
+
# save the dataframe to a csv file
|
53 |
+
|
54 |
+
|
55 |
+
@staticmethod
|
56 |
+
def cleanup_sentiment_results(classification_unclean):
|
57 |
+
"""
|
58 |
+
Cleans up the results of the sentiment classification.
|
59 |
+
:param classification_unclean: string of the classification result.
|
60 |
+
:return: cleaned up string.
|
61 |
+
"""
|
62 |
+
classification_clean = classification_unclean.replace('\n\n', "")
|
63 |
+
classification_clean = classification_clean.replace('\n', "")
|
64 |
+
if classification_clean.startswith(" "):
|
65 |
+
classification_clean = classification_clean.replace(" ", "")
|
66 |
+
|
67 |
+
return classification_clean
|
68 |
|
69 |
def classify_sentiment(self, text: str):
|
70 |
"""
|
71 |
Classifies the sentiment of a text.
|
72 |
"""
|
73 |
+
assert isinstance(text, str)
|
74 |
+
|
75 |
+
prompt_string = "Classify one sentiment for this tweet:\n \""
|
76 |
+
prompt_string += text
|
77 |
+
prompt_string += "\" \nFor example:\nSupport,\nOpposition,\nCriticism,\nPraise,\nDisagreement," \
|
78 |
+
"\nAgreement,\nSkepticism,\nAdmiration,\nAnecdotes,\nJokes,\nMemes,\nSarcasm,\nSatire," \
|
79 |
+
"\nQuestions,\nStatements,\nOpinions,\nPredictions.\nSENTIMENT="
|
80 |
+
|
81 |
+
response = openai.Completion.create(
|
82 |
+
model=self.model_name,
|
83 |
+
prompt=prompt_string,
|
84 |
+
temperature=0.0,
|
85 |
+
max_tokens=256,
|
86 |
+
top_p=1,
|
87 |
+
frequency_penalty=0,
|
88 |
+
presence_penalty=0,
|
89 |
+
logprobs=5
|
90 |
+
)
|
91 |
+
classification_unclean = response.choices[0]['text']
|
92 |
+
classification_clean = self.cleanup_sentiment_results(classification_unclean)
|
93 |
+
|
94 |
+
return classification_clean.lower()
|
95 |
+
|
96 |
+
def classify_sentiment_of_tweets(self):
|
97 |
+
"""
|
98 |
+
Classifies the sentiment of a user's tweets.
|
99 |
+
"""
|
100 |
+
df_sentiment = self.df.copy()
|
101 |
+
|
102 |
+
df_sentiment['sentiment'] = df_sentiment['tweet'].apply(self.classify_sentiment)
|
103 |
+
self.df = df_sentiment
|
104 |
+
return self.df
|
105 |
|
106 |
+
def analyze_sentiment(self, text: str, sentiment: str):
|
107 |
+
# TODO: fix prompt before running this method
|
108 |
+
"""
|
109 |
+
Analyzes the sentiment of a text using OpenAI.
|
110 |
+
:param text: string of the tweet text.
|
111 |
+
:param sentiment:
|
112 |
+
:return:
|
113 |
+
"""
|
114 |
+
# assert 1 == 2, "Måste fixa prompt innan denna metod körs"
|
115 |
+
prompt_string = "Who is the TARGET of this "
|
116 |
+
prompt_string += sentiment
|
117 |
+
prompt_string += " TWEET?\\nTWEET=\""
|
118 |
+
prompt_string += text
|
119 |
+
prompt_string += "\"\\n.TARGET should consist of less than 5 words.\\nTARGET="
|
120 |
+
|
121 |
+
response = openai.Completion.create(
|
122 |
+
model=self.model_name,
|
123 |
+
prompt=prompt_string,
|
124 |
+
temperature=0,
|
125 |
+
max_tokens=256,
|
126 |
+
top_p=1,
|
127 |
+
frequency_penalty=0,
|
128 |
+
presence_penalty=0
|
129 |
+
)
|
130 |
+
|
131 |
+
analyzed_sentiment = response.choices[0]['text']
|
132 |
+
# Remove spaces at the start/end of the response
|
133 |
+
if analyzed_sentiment.startswith(' '):
|
134 |
+
analyzed_sentiment = analyzed_sentiment[1:]
|
135 |
+
if analyzed_sentiment.endswith(' '):
|
136 |
+
analyzed_sentiment = analyzed_sentiment[:-1]
|
137 |
+
|
138 |
+
# Sometimes GPT-3 gives faulty results, so a simple filter is introduced
|
139 |
+
# If the prediction is bad
|
140 |
+
# -> set target value to N/A (not applicable)
|
141 |
+
if len(analyzed_sentiment) > 50:
|
142 |
+
analyzed_sentiment = "N/A"
|
143 |
+
|
144 |
+
# An attempt to merge target responses that should be the same
|
145 |
+
analyzed_sentiment = re.sub("\(", "", analyzed_sentiment)
|
146 |
+
analyzed_sentiment = re.sub("\)", "", analyzed_sentiment)
|
147 |
+
|
148 |
+
s_list = ["s", "the swedish social democratic party"]
|
149 |
+
m_list = ["m", "the swedish moderate party", "the moderate party"]
|
150 |
+
mp_list = ["mp", "the swedish green party"]
|
151 |
+
|
152 |
+
if analyzed_sentiment.lower() == "v":
|
153 |
+
analyzed_sentiment = "Vänsterpartiet"
|
154 |
+
elif analyzed_sentiment.lower() == "mp":
|
155 |
+
analyzed_sentiment = "Miljöpartiet"
|
156 |
+
elif analyzed_sentiment.lower() in s_list:
|
157 |
+
analyzed_sentiment = "Socialdemokraterna"
|
158 |
+
elif analyzed_sentiment.lower() == "c":
|
159 |
+
analyzed_sentiment = "Centerpartiet"
|
160 |
+
elif analyzed_sentiment.lower() == "l":
|
161 |
+
analyzed_sentiment = "Liberalerna"
|
162 |
+
elif analyzed_sentiment.lower() == "kd":
|
163 |
+
analyzed_sentiment = "Kristdemokraterna"
|
164 |
+
elif analyzed_sentiment.lower() in m_list:
|
165 |
+
analyzed_sentiment = "Moderaterna"
|
166 |
+
elif analyzed_sentiment.lower() == "sd":
|
167 |
+
analyzed_sentiment = "Sverigedemokraterna"
|
168 |
+
elif analyzed_sentiment.lower() == "the swedish government":
|
169 |
+
analyzed_sentiment = "Regeringen"
|
170 |
+
|
171 |
+
return analyzed_sentiment
|
172 |
+
|
173 |
+
def analyze_sentiment_of_tweets(self):
|
174 |
+
"""
|
175 |
+
Analyzes the sentiment of a user's tweets.
|
176 |
+
"""
|
177 |
+
# check if 'sentiment' column exists, raise exception if not
|
178 |
+
assert 'sentiment' in self.df.columns, \
|
179 |
+
"'sentiment' column does not exist. Please run classify_sentiment_of_tweets first."
|
180 |
+
|
181 |
+
df_sentiment = self.df.copy()
|
182 |
+
df_sentiment['target'] = df_sentiment.apply(lambda row: self.analyze_sentiment(row['tweet'], row['sentiment']),
|
183 |
+
axis=1)
|
184 |
+
self.df = df_sentiment
|
185 |
+
return self.df
|
186 |
+
|
187 |
+
def classify_topic(self, text: str):
|
188 |
"""
|
189 |
Classifies the topics of a text.
|
190 |
+
:param text: string of the tweet text.
|
191 |
"""
|
192 |
+
assert isinstance(text, str)
|
193 |
+
|
194 |
+
prompt_string = "Classify one topic for this tweet:\n \""
|
195 |
+
prompt_string += text
|
196 |
+
prompt_string += "\" \nFor example:\nEconomy,\nEnvironment,\nHealth,\nPolitics,\nScience,\nSports,\nTechnology," \
|
197 |
+
"\nTransportation,\nWorld.\nTOPIC="
|
198 |
+
|
199 |
+
response = openai.Completion.create(
|
200 |
+
model=self.model_name,
|
201 |
+
prompt=prompt_string,
|
202 |
+
temperature=0,
|
203 |
+
max_tokens=892,
|
204 |
+
top_p=1,
|
205 |
+
frequency_penalty=0,
|
206 |
+
presence_penalty=0,
|
207 |
+
)
|
208 |
+
classification_unclean = response.choices[0]['text']
|
209 |
+
classification_clean = self.cleanup_topic_results(classification_unclean)
|
210 |
+
|
211 |
+
return classification_clean.lower()
|
212 |
+
|
213 |
+
def classify_topics_of_tweets(self):
|
214 |
+
"""
|
215 |
+
Classifies the topics of a user's tweets.
|
216 |
+
"""
|
217 |
+
df_topic = self.df
|
218 |
+
df_topic['topic'] = df_topic['tweet'].apply(self.classify_topic)
|
219 |
+
return df_topic
|
220 |
+
|
221 |
+
@staticmethod
|
222 |
+
def cleanup_topic_results(prediction_dict, text):
|
223 |
+
new_item = text.replace("\n", " ")
|
224 |
+
new_item = new_item.replace(" ", " ")
|
225 |
+
return new_item
|
226 |
+
|
227 |
+
def df_to_csv(self, filename="{}/data/twitterdata.csv".format(ROOT_PATH)):
|
228 |
+
"""
|
229 |
+
Writes pandas df to csv file. If it already exists, it appends. If not, it creates. It also removes duplicates.
|
230 |
+
:param filename:
|
231 |
+
:return:
|
232 |
+
"""
|
233 |
+
if not os.path.exists(filename):
|
234 |
+
self.df.to_csv(filename, index=False)
|
235 |
+
else:
|
236 |
+
self.df.to_csv(filename, mode='a', header=False, index=False)
|
237 |
+
|
238 |
+
self.remove_duplicates_from_csv(filename)
|
239 |
+
|
240 |
+
@staticmethod
|
241 |
+
def remove_duplicates_from_csv(filename="{}/data/twitterdata.csv".format(ROOT_PATH)):
|
242 |
+
"""
|
243 |
+
Removes duplicates from csv file.
|
244 |
+
:param filename: filename of csv file
|
245 |
+
:return: None
|
246 |
+
"""
|
247 |
+
with open(filename, 'r') as f:
|
248 |
+
lines = f.readlines()
|
249 |
+
with open(filename, 'w') as f:
|
250 |
+
for line in lines:
|
251 |
+
if line not in lines[lines.index(line) + 1:]:
|
252 |
+
f.write(line)
|
253 |
+
|
254 |
+
def remove_already_classified_tweets(self, filename="{}/data/twitterdata.csv".format(ROOT_PATH)):
|
255 |
+
"""
|
256 |
+
Removes tweets that have already been classified.
|
257 |
+
:param filename: filename of csv file
|
258 |
+
:return: None
|
259 |
+
"""
|
260 |
+
df = self.df
|
261 |
+
df = df[df['sentiment'].isnull()]
|
262 |
+
self.df = df
|
263 |
+
self.df_to_csv(filename)
|
264 |
|
265 |
def __repr__(self):
|
266 |
+
"""
|
267 |
+
Gives a string that describes which user is classified
|
268 |
+
:return:
|
269 |
+
"""
|
270 |
+
return "Classifier for user: " + self.user_name + " with model: " + self.model_name + "."
|
271 |
+
|
272 |
+
if __name__ == "__main__":
|
273 |
+
tc = TextClassifier(from_date="2022-01-01", to_date="2022-05-31", user_name='jimmieakesson', num_tweets=20)
|
274 |
+
tc.remove_duplicates_from_csv()
|
275 |
+
# import pandas as pd
|
276 |
+
# from datetime import datetime
|
277 |
+
# import os
|
278 |
+
# # show all columns
|
279 |
+
# pd.set_option('display.max_columns', None)
|
280 |
+
#
|
281 |
+
# tc = TextClassifier(from_date="2019-01-01", to_date="2019-05-31", user_name='jimmieakesson', num_tweets=20)
|
282 |
+
# tc.classify_sentiment_of_tweets()
|
283 |
+
# # df = tc.analyze_sentiment_of_tweets()
|
284 |
+
# # print(df)
|
285 |
+
# df = tc.classify_topics_of_tweets()
|
286 |
+
# print(df)
|
287 |
+
# # save to csv in a folder under politweet with timestamp in name
|
288 |
+
# df.to_csv(f"{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}_tweets.csv")
|
twitterscraper/TwitterScraper.py
CHANGED
@@ -11,6 +11,7 @@ class TwitterScraper(object):
|
|
11 |
input: user, from_date, to_date, num_tweets
|
12 |
output: dict
|
13 |
"""
|
|
|
14 |
def __init__(self, from_date="2022-07-01", to_date=str(date.today()), num_tweets=20):
|
15 |
# TODO: add a check to make sure that the dates are in the correct format.
|
16 |
# TODO: add a check to make sure that the number of tweets is a positive number.
|
@@ -69,7 +70,7 @@ class TwitterScraper(object):
|
|
69 |
tweets_info = tweet_and_replies_info.drop(labels=indx_replies, axis=0)
|
70 |
# drop removes the columns which its index specified by
|
71 |
# indx_replies. axis=0 if we want to delete rows.
|
72 |
-
#print(len(tweets['tweet']), " of them are Tweets")
|
73 |
return tweets_info
|
74 |
|
75 |
def __get_tweets__from_twint__(self):
|
@@ -110,15 +111,12 @@ class TwitterScraper(object):
|
|
110 |
def __repr__(self):
|
111 |
return "TwitterScraper(from_date={}, to_date={}, num_tweets={})".format(self.from_date, self.to_date,
|
112 |
self.num_tweets)
|
|
|
|
|
|
|
113 |
if __name__ == "__main__":
|
114 |
sc = TwitterScraper(from_date="2022-05-01", to_date="2022-07-31", num_tweets=40)
|
115 |
dc = sc.scrape_by_user("jimmieakesson")
|
116 |
print(dc.head())
|
117 |
print(dc.shape)
|
118 |
print(dc.columns)
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
11 |
input: user, from_date, to_date, num_tweets
|
12 |
output: dict
|
13 |
"""
|
14 |
+
|
15 |
def __init__(self, from_date="2022-07-01", to_date=str(date.today()), num_tweets=20):
|
16 |
# TODO: add a check to make sure that the dates are in the correct format.
|
17 |
# TODO: add a check to make sure that the number of tweets is a positive number.
|
|
|
70 |
tweets_info = tweet_and_replies_info.drop(labels=indx_replies, axis=0)
|
71 |
# drop removes the columns which its index specified by
|
72 |
# indx_replies. axis=0 if we want to delete rows.
|
73 |
+
# print(len(tweets['tweet']), " of them are Tweets")
|
74 |
return tweets_info
|
75 |
|
76 |
def __get_tweets__from_twint__(self):
|
|
|
111 |
def __repr__(self):
|
112 |
return "TwitterScraper(from_date={}, to_date={}, num_tweets={})".format(self.from_date, self.to_date,
|
113 |
self.num_tweets)
|
114 |
+
|
115 |
+
|
116 |
+
|
117 |
if __name__ == "__main__":
|
118 |
sc = TwitterScraper(from_date="2022-05-01", to_date="2022-07-31", num_tweets=40)
|
119 |
dc = sc.scrape_by_user("jimmieakesson")
|
120 |
print(dc.head())
|
121 |
print(dc.shape)
|
122 |
print(dc.columns)
|
|
|
|
|
|
|
|
|
|
|
|
twitterscraper/jimmieakesson.csv
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
,id,tweet,date,user_id,username,urls,nlikes,nreplies,nretweets
|
2 |
+
0,1546801285021814784,"Sverigedemokraterna kommer även fortsättningsvis att vara den tyngsta rösten för kraftigt sänkta priser på både bränsle och el. Alla vi miljontals människor som bor utanför storstädernas kollektivtrafik och cykelbanor måste kunna leva. Sänk bränslepriserna, nu! https://t.co/OiPb6WdaZG",2022-07-12 12:18:59,95972673,jimmieakesson,[],1074,63,103
|
3 |
+
1,1544748873767424001,Fruktansvärt att nås av beskedet att kvinnan som attackerades i Visby har avlidit. Mina tankar finns hos hennes familj ikväll.,2022-07-06 20:23:26,95972673,jimmieakesson,[],3482,178,143
|
4 |
+
2,1538948369611210764,"@annieloof Nej, jag håller med. Tänk mer som Mathias Andersson (SD). https://t.co/gSqQDz5N8z",2022-06-20 20:14:18,95972673,jimmieakesson,[],1511,89,115
|
5 |
+
3,1537770920621879297,"Man kan ha synpunkter på en sådan lösning, men den är naturligtvis att föredra framför frigående våldsverkare som fortsätter misshandla sina offer i väntan på fängelse.",2022-06-17 14:15:32,95972673,jimmieakesson,[],694,17,41
|
6 |
+
4,1537770809225273344,Är det ont om plats på anstalterna så får man sänka standarden rejält för att få rum med fler interner per kvadratmeter.,2022-06-17 14:15:05,95972673,jimmieakesson,[],812,26,57
|
7 |
+
5,1537770713368735744,"Döms man för brott, särskilt våldsbrott, ska man vara inlåst från det att domen faller tills straffet är avtjänat. Allt annat är vansinne.",2022-06-17 14:14:43,95972673,jimmieakesson,[],1015,26,85
|
8 |
+
6,1537770657823576066,"Platsbrist? Jaha, vad spelar det för roll? Det gör mig förbannad och bestört att lösningen på problemet med överfulla fängelser verkar vara att dömda våldsbrottslingar får röra sig fritt i samhället istället för att sitta inlåsta. https://t.co/QDi9rM3kMC",2022-06-17 14:14:29,95972673,jimmieakesson,['https://sverigesradio.se/artikel/domda-kvinnomisshandlare-kan-fortsatta-valdet-mot-samma-kvinna-innan-fangelset'],1157,86,132
|
9 |
+
7,1534230353094885383,"Det är ytterst beklagligt att Magdalena Andersson saboterar Sveriges Natoansökan genom att kohandla med en marxistisk vilde som inte bryr sig ett smack om Sverige, utan istället fullt ut företräder utländska intressen. Svekfullt, maktfullkomligt och ansvarslöst.",2022-06-07 19:46:35,95972673,jimmieakesson,[],6118,544,556
|
10 |
+
8,1533881878553538560,"Glöm att Sverigedemokraterna kommer att lova regeringen frikort att missköta sig för att de gör sig av med en, förvisso exceptionellt usel, minister. https://t.co/3yVuUQU42o",2022-06-06 20:41:52,95972673,jimmieakesson,['https://live.aftonbladet.se/supernytt/news/m-och-l-foeljer-kds-erbjudande-till-magdalena-andersson.CqhNSVhf9'],1079,97,77
|
11 |
+
9,1532625870824808448,"Det är bra att väljarna får denna tydliga varudeklaration, en röst på Centern är en röst på Socialdemokraterna.",2022-06-03 09:30:57,95972673,jimmieakesson,[],1255,57,96
|
12 |
+
10,1532625804223516672,Centern ❤️ Sossarna Ännu en gång är det Annie Lööf som kommer till Morgan Johanssons räddning. För vilken gång i ordningen vet jag inte. Det är tydligt att Centern blivit det vänsterparti som krafter inom partiet arbetat för att det ska bli.,2022-06-03 09:30:41,95972673,jimmieakesson,[],2512,211,188
|
13 |
+
11,1532341074127163392,"Därför valde Sverigedemokraterna idag att, under dagens frågestund i kammaren, väcka frågan om misstroende gentemot Morgan Johansson. Sverige förtjänar bättre.",2022-06-02 14:39:16,95972673,jimmieakesson,[],790,32,34
|
14 |
+
12,1532340946456694785,"Morgan Johanssons misslyckanden kan inte få fortgå en enda dag mer än nödvändigt och det är vår inställning att oavsett valets utfall, så bör han förtidspensioneras som Sveriges justitie- och inrikesminister.",2022-06-02 14:38:45,95972673,jimmieakesson,[],659,9,40
|
15 |
+
13,1532340845042622465,"De enda som kan vara nöjda med regeringens arbete är de kriminella, de som mördar, skadar och hotar. De hoppas just nu på att även kommande mandatperiod ska innebära ytterligare fyra år med Socialdemokratisk saft- och bullepolitik.",2022-06-02 14:38:21,95972673,jimmieakesson,[],435,15,35
|
16 |
+
14,1532340768681209858,Tusentals föräldrar och syskon har förlorat någon närstående och Morgan Johanssons misslyckanden är förmodligen ett av de mest tragiska kapitlen i svensk historia.,2022-06-02 14:38:03,95972673,jimmieakesson,[],406,7,21
|
17 |
+
15,1532340666751242240,Morgan Johansson måste avgå som minister. Otryggheten biter sig fast och gängkriminaliteten är allt annat än knäckt. Antalet skjutningar ökar och sätter skräck i varje del av vårt land. Sverige har förvandlats till ett gangsterland.,2022-06-02 14:37:39,95972673,jimmieakesson,[],3064,172,231
|
18 |
+
16,1523345947165483008, https://t.co/64aJSb1G58,2022-05-08 18:55:50,95972673,jimmieakesson,['https://www.dn.se/debatt/sa-vill-m-sd-kd-och-l-ge-alla-pensionarer-mer-i-planboken/'],122,9,11
|
19 |
+
17,1523345873769357312,"För oss Sverigedemokrater är detta en prioriterad fråga om både rättvisa och moral. Svenskar som på olika sätt har varit med och byggt det här landet, förtjänar en värdig ålderdom.",2022-05-08 18:55:33,95972673,jimmieakesson,[],294,11,14
|
20 |
+
18,1523345831629180928,"I en tid när priserna stiger kraftigt på just de varor och tjänster som är svårast att avvara – mat, värme och bränsle - är det nödvändigt att förbättra de svenska pensionärernas situation.",2022-05-08 18:55:23,95972673,jimmieakesson,[],214,5,8
|
21 |
+
19,1523345750255538176,"Största pensionssatsningen sedan pensionssystemet infördes. Vi kan idag presentera en överenskommelse med Moderaterna, Kristdemokraterna och Liberalerna om att göra den största förstärkningen av pensionärernas ekonomi sedan det nuvarande pensionssystemet infördes. https://t.co/tENrQDFy9o",2022-05-08 18:55:03,95972673,jimmieakesson,[],706,57,73
|
22 |
+
20,1522508592376602625,Varför har ens SR alla dessa invandrarredaktioner? Ska du bo i Sverige - lär dig svenska.,2022-05-06 11:28:29,95972673,jimmieakesson,[],6876,380,522
|
23 |
+
21,1521752982198620161,"Jag uppmanar andra inbjudna partiledare att ställa samma krav. Det får faktiskt finnas någon gräns för tokigheterna, svensk polis förtjänar tydligt stöd från Sveriges politiska partier.",2022-05-04 09:25:58,95972673,jimmieakesson,[],1914,48,106
|
24 |
+
22,1521752934702362626,Järvaveckan får helt enkelt välja – polishataren Kakan Hermansson eller partiledaren Jimmie Åkesson.,2022-05-04 09:25:47,95972673,jimmieakesson,[],1588,53,77
|
25 |
+
23,1521752853110566912,"Jag kommer idag att meddela arrangörerna att jag inte avser närvara och hålla tal på årets Järvaveckan, vilket tidigare planerats, om denna person på något sätt är en del av deras evenemang.",2022-05-04 09:25:27,95972673,jimmieakesson,[],1258,46,56
|
26 |
+
24,1521752814082535424,"Kakan Hermansson har genom åren gjort sig känd för en rad politiskt extrema och direkt stötande uttalanden, inte minst hatiska inlägg om svensk polis.",2022-05-04 09:25:18,95972673,jimmieakesson,[],1006,27,47
|
27 |
+
25,1521752766921781249,"Nu figurerar uppgifter om att årets upplaga av Järvaveckan, en politikervecka i norra Stockholm, återigen genomförs i samarbete med den mycket hårt kritiserade extremvänsterprofilen Kakan Hermansson.",2022-05-04 09:25:07,95972673,jimmieakesson,[],780,10,33
|
28 |
+
26,1521752718070755331,"Den senaste tidens utveckling, med Ramadan-kravaller innefattande grovt våld i uppenbart syfte att döda poliser, har aktualiserat behovet av sammanhållning och rakryggat stöd för Polisen och för det svenska samhället i stort.",2022-05-04 09:24:55,95972673,jimmieakesson,[],853,13,35
|
29 |
+
27,1521752661170794496,Järvaveckan får välja – Kakan Hermansson eller Jimmie Åkesson,2022-05-04 09:24:41,95972673,jimmieakesson,[],3924,465,215
|
30 |
+
28,1521489890369843200,Varför svenska barn påtvingas politisk indoktrinering i skolan är en fråga som kräver en omedelbar förklaring. Vi kommer att kalla Skolverkets generaldirektör till utbildningsutskottet. Från dagens nationella prov i historia.,2022-05-03 16:00:32,95972673,jimmieakesson,[],3359,168,246
|
31 |
+
29,1521489797038157827,Det här äcklar mig på riktigt. Propaganda riktad till skolbarn hör hemma i diktaturer. https://t.co/BDYiNs8NDV,2022-05-03 16:00:10,95972673,jimmieakesson,[],6804,735,711
|
32 |
+
30,1521395241454034945, https://t.co/tpIq94r7BG,2022-05-03 09:44:26,95972673,jimmieakesson,['https://www.youtube.com/watch?v=f-eK1cgzGF8&ab_channel=Sverigedemokraterna'],178,10,20
|
33 |
+
31,1521180859012943878,Ikväll medverkar jag i Aktuellt. Ämnet är trafiken och Sveriges åtgärder för att minska utsläppen. Debatt mot Annie Lööf. Aktuellt sänds i SVT2 med start kl 21.00.,2022-05-02 19:32:33,95972673,jimmieakesson,[],874,104,53
|