Skip to content
This repository was archived by the owner on Jul 8, 2024. It is now read-only.

Commit bbd72aa

Browse files
committed
Resolved Discussions
1 parent c541845 commit bbd72aa

3 files changed

Lines changed: 132 additions & 46 deletions

File tree

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Author: Giulio Neusch-Frediani - www.github.com/giulionf
22
from datetime import datetime
3+
from datetime import timedelta
34
from threading import Thread
4-
from queue import Queue
5-
from queue import Empty
5+
from threading import Lock
66
import copy
77

88
from GetOldTweets3.manager.TweetManager import TweetManager
@@ -14,42 +14,61 @@
1414
class ConcurrentTweetManager:
1515

1616
@staticmethod
17-
def getTweets(tweetCriteria, receiveBuffer=None, bufferLength=100, proxy=None, debug=False, worker_count=1, ordered=True):
17+
def getTweets(tweetCriteria, receiveBuffer=None, bufferLength=100, proxy=None, debug=False, worker_count=1,
18+
forceMaxTweets=False):
19+
20+
if worker_count < 1:
21+
raise ValueError("At least one worker is needed")
22+
23+
if tweetCriteria.maxTweets != 0 and not forceMaxTweets:
24+
raise ValueError("Max Tweets is not supported by parallel downloading, since the results can not be ordered"
25+
" by time. If you do not care, you can set forceMaxTweets=True!")
26+
1827
# Init the queues
19-
time_spans = Queue()
28+
time_spans = []
2029
tweets = []
2130
workers = []
31+
lock = Lock()
2232

2333
# Split the date in smaller parts
24-
since_date = datetime.strptime(tweetCriteria.since, DATE_FORMAT) if tweetCriteria.since else FIRST_TWEET_DATE
25-
until_date = datetime.strptime(tweetCriteria.until, DATE_FORMAT) if tweetCriteria.until else datetime.now()
34+
since_date = datetime.strptime(tweetCriteria.since, DATE_FORMAT) if hasattr(tweetCriteria, "since") else FIRST_TWEET_DATE
35+
until_date = datetime.strptime(tweetCriteria.until, DATE_FORMAT) if hasattr(tweetCriteria, "until") else datetime.now()
2636
date_diff_per_worker = (until_date - since_date) / worker_count
2737

38+
if date_diff_per_worker < timedelta(days=1):
39+
max_workers = int((until_date - since_date) / timedelta(days=1))
40+
raise ValueError("Too many workers for the time span. Each worker needs at least one day for himself, or"
41+
" some workers will have the same results leading to inconsistencies."
42+
"For your case, the max worker count is {} workers".format(max_workers))
43+
2844
# Create a TweetCriteria that can be cloned as Model by the Workers
2945
criteria = copy.deepcopy(tweetCriteria)
30-
criteria.setMaxTweets(criteria.maxTweets / worker_count)
46+
if forceMaxTweets:
47+
criteria.setMaxTweets(tweetCriteria.maxTweets / worker_count)
3148

3249
# Create a time span for each of the splitted parts and a corresponding worker
3350
for i in range(1, worker_count+1):
3451
from_time = since_date + (i-1) * date_diff_per_worker
3552
to_time = since_date + i * date_diff_per_worker
36-
time_spans.put((from_time, to_time))
53+
time_spans.append((from_time, to_time))
3754

38-
w = WorkerThread(copy.deepcopy(criteria), time_spans, tweets, receiveBuffer, bufferLength, proxy, debug)
55+
for i in range(0, worker_count):
56+
w = WorkerThread(copy.deepcopy(criteria), time_spans[i], tweets, lock, receiveBuffer, bufferLength,
57+
proxy, debug)
3958
w.start()
4059
workers.append(w)
4160

42-
# Wait for the workers to finish and then stop them
43-
time_spans.join()
61+
# Wait for the workers to finish, then return the results
4462
for worker in workers:
45-
worker.stop()
63+
worker.join()
4664

4765
return sorted(tweets, key=lambda r: r.date)
4866

4967

5068
class WorkerThread(Thread):
5169

52-
def __init__(self, tweetCriteria, time_spans, tweets, receiveBuffer=None, bufferLength=100, proxy=None, debug=False):
70+
def __init__(self, tweetCriteria, time_span, tweets, lock, receiveBuffer=None, bufferLength=100, proxy=None,
71+
debug=False):
5372
super().__init__()
5473
self.stopped = False
5574
self.manager = TweetManager()
@@ -58,21 +77,13 @@ def __init__(self, tweetCriteria, time_spans, tweets, receiveBuffer=None, buffer
5877
self.bufferLength = bufferLength
5978
self.proxy = proxy
6079
self.debug = debug
61-
self.time_spans = time_spans
80+
self.time_span = time_span
6281
self.tweets = tweets
63-
64-
def stop(self):
65-
self.stopped = True
82+
self.lock = lock
6683

6784
def run(self) -> None:
68-
while not self.stopped:
69-
try:
70-
time_span = self.time_spans.get_nowait()
71-
self.tweetCriteria.setSince(datetime.strftime(time_span[0], "%Y-%m-%d"))
72-
self.tweetCriteria.setUntil(datetime.strftime(time_span[1], "%Y-%m-%d"))
73-
search_results = self.manager.getTweets(self.tweetCriteria, self.receiveBuffer, self.bufferLength,
74-
self.proxy, self.debug)
75-
self.tweets.extend(search_results)
76-
self.time_spans.task_done()
77-
except Empty:
78-
pass
85+
self.tweetCriteria.setSince(datetime.strftime(self.time_span[0], "%Y-%m-%d"))
86+
self.tweetCriteria.setUntil(datetime.strftime(self.time_span[1], "%Y-%m-%d"))
87+
search_results = self.manager.getTweets(self.tweetCriteria, self.receiveBuffer, self.bufferLength, self.proxy,
88+
self.debug)
89+
self.tweets.extend(search_results)

bin/GetOldTweets3

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ if sys.version_info[0] < 3:
4343

4444
import GetOldTweets3 as got
4545

46+
47+
def concatenate_concurrent_files(collected_directory, outputFileName):
48+
from os import listdir
49+
from os.path import isfile, join
50+
file_paths = [collected_directory + "/" + f for f in listdir(collected_directory) if isfile(join(collected_directory, f))]
51+
52+
with open(outputFileName, "w+", encoding="utf8") as total_result_file:
53+
total_result_file.write('date,username,to,replies,retweets,favorites,text,geo,mentions,hashtags,id,permalink\n')
54+
for file_path in reversed(file_paths):
55+
with open(file_path, "r", encoding="utf8") as file:
56+
data = file.read().split("\n", 1)[1]
57+
total_result_file.write(data)
58+
os.remove(file_path)
59+
os.removedirs(collected_directory)
60+
61+
62+
63+
4664
def main(argv):
4765
if len(argv) == 0:
4866
print('You must pass some parameters. Use \"-h\" to help.')
@@ -64,12 +82,18 @@ def main(argv):
6482
"maxtweets=",
6583
"lang=",
6684
"output=",
67-
"debug"))
85+
"debug",
86+
"workers=",
87+
"forcemaxtweets"))
6888

6989
tweetCriteria = got.manager.TweetCriteria()
7090
outputFileName = "output_got.csv"
91+
outputDirNameConcurrent = "output_got.csv_collected"
92+
outputFileNameConcurrent = "{}_to_{}.csv"
7193

7294
debug = False
95+
force_max_tweets = False
96+
workers = 1
7397
usernames = set()
7498
username_files = set()
7599
for opt, arg in opts:
@@ -107,10 +131,17 @@ def main(argv):
107131

108132
elif opt == '--output':
109133
outputFileName = arg
134+
outputDirNameConcurrent = arg + "_collected"
110135

111136
elif opt == '--debug':
112137
debug = True
113138

139+
elif opt == '--workers':
140+
workers = int(arg)
141+
142+
elif opt == '--forcemaxtweets':
143+
force_max_tweets = True
144+
114145
if debug:
115146
print(' '.join(sys.argv))
116147
print("GetOldTweets3", got.__version__)
@@ -136,8 +167,12 @@ def main(argv):
136167
else:
137168
tweetCriteria.username = usernames.pop()
138169

139-
outputFile = open(outputFileName, "w+", encoding="utf8")
140-
outputFile.write('date,username,to,replies,retweets,favorites,text,geo,mentions,hashtags,id,permalink\n')
170+
if workers > 1:
171+
if not os.path.exists(outputDirNameConcurrent):
172+
os.makedirs(outputDirNameConcurrent)
173+
else:
174+
outputFile = open(outputFileName, "w+", encoding="utf8")
175+
outputFile.write('date,username,to,replies,retweets,favorites,text,geo,mentions,hashtags,id,permalink\n')
141176

142177
cnt = 0
143178
def receiveBuffer(tweets):
@@ -167,8 +202,45 @@ def main(argv):
167202
else:
168203
print(cnt, end=' ', flush=True)
169204

205+
def receiveBufferConcurrent(tweets):
206+
nonlocal cnt
207+
208+
last_date = tweets[0].date.strftime("%Y-%m-%d")
209+
first_date = tweets[len(tweets)-1].date.strftime("%Y-%m-%d")
210+
211+
file = open(outputDirNameConcurrent + "/" + outputFileNameConcurrent.format(first_date, last_date), "w+", encoding="utf8")
212+
file.write('date,username,to,replies,retweets,favorites,text,geo,mentions,hashtags,id,permalink\n')
213+
214+
for t in tweets:
215+
data_ = [t.date.strftime("%Y-%m-%d %H:%M:%S"),
216+
t.username,
217+
t.to or '',
218+
t.replies,
219+
t.retweets,
220+
t.favorites,
221+
'"' + t.text.replace('"', '""') + '"',
222+
t.geo,
223+
t.mentions,
224+
t.hashtags,
225+
t.id,
226+
t.permalink]
227+
data_[:] = [i if isinstance(i, str) else str(i) for i in data_]
228+
file.write(','.join(data_) + '\n')
229+
230+
file.flush()
231+
file.close()
232+
cnt += len(tweets)
233+
234+
if sys.stdout.isatty():
235+
print("\rSaved %i" % cnt, end='', flush=True)
236+
else:
237+
print(cnt, end=' ', flush=True)
238+
170239
print("Downloading tweets...")
171-
got.manager.TweetManager.getTweets(tweetCriteria, receiveBuffer, debug=debug)
240+
if workers > 1:
241+
got.manager.ConcurrentTweetManager.getTweets(tweetCriteria, receiveBufferConcurrent, debug=debug, worker_count=workers, forceMaxTweets=force_max_tweets)
242+
else:
243+
got.manager.TweetManager.getTweets(tweetCriteria, receiveBuffer, debug=debug)
172244

173245
except getopt.GetoptError as err:
174246
print('Arguments parser error, try -h')
@@ -182,10 +254,14 @@ def main(argv):
182254
print(str(err))
183255

184256
finally:
185-
if "outputFile" in locals():
186-
outputFile.close()
187-
print()
188-
print('Done. Output file generated "%s".' % outputFileName)
257+
if "workers" in locals() and workers > 1:
258+
concatenate_concurrent_files(outputDirNameConcurrent, outputFileName)
259+
else:
260+
if "outputFile" in locals():
261+
outputFile.close()
262+
print()
263+
print('Done. Output file generated "%s".' % outputFileName)
264+
189265

190266
if __name__ == '__main__':
191267
main(sys.argv[1:])

tests/test_main.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,18 @@ def test_QuerySearch():
2727

2828
def test_MassFetchConcurrent():
2929
time1 = time.time()
30-
tweetCriteria = got.manager.TweetCriteria().setQuerySearch('#europe #refugees')\
31-
.setSince("2015-05-01")\
32-
.setUntil("2015-09-30")\
33-
.setMaxTweets(200)
34-
tweets1 = got.manager.ConcurrentTweetManager.getTweets(tweetCriteria, worker_count=5)
30+
tweetCriteria = got.manager.TweetCriteria().setUsername("@realdonaldtrump")
31+
tweets1 = got.manager.ConcurrentTweetManager.getTweets(tweetCriteria, worker_count=100, forceMaxTweets=True,
32+
receiveBuffer=on_receive_batch)
3533
print("Time Needed Concurrent: {} Secs".format((time.time() - time1)))
3634

3735
time2 = time.time()
38-
tweetCriteria = got.manager.TweetCriteria().setQuerySearch('#europe #refugees')\
39-
.setSince("2015-05-01")\
40-
.setUntil("2015-09-30")\
41-
.setMaxTweets(200)
42-
tweets2 = got.manager.TweetManager.getTweets(tweetCriteria)
36+
tweetCriteria = got.manager.TweetCriteria().setUsername("@realdonaldtrump")
37+
tweets2 = got.manager.TweetManager.getTweets(tweetCriteria, receiveBuffer=on_receive_batch)
4338
print("Time Needed Non Concurrent: {} Secs".format((time.time() - time2)))
4439

4540
assert len(tweets1) == len(tweets2)
41+
42+
43+
def on_receive_batch(results_aux):
44+
print(str(len(results_aux)))

0 commit comments

Comments
 (0)