使用Tweepy收听流并搜索推文。如何停止以前的搜索并只收听新流?

时间:2014-12-11 19:22:45

标签: python flask tweepy

我使用Flask和Tweepy搜索实时推文。在前端,我有一个用户文本输入,以及名为" Search"的按钮。理想情况下,当用户在输入中提供搜索词并点击"搜索"按钮,Tweepy应该监听新的搜索词并停止前一个搜索词流。当"搜索"单击按钮它执行此功能:

@app.route('/search', methods=['POST'])
# gets search-keyword and starts stream
def streamTweets():
    search_term = request.form['tweet']
    search_term_hashtag = '#' + search_term
    # instantiate listener
    listener = StdOutListener()
    # stream object uses listener we instantiated above to listen for data
    stream = tweepy.Stream(auth, listener)

    if stream is not None:
        print "Stream disconnected..."
        stream.disconnect()

    stream.filter(track=[search_term or search_term_hashtag], async=True)
    redirect('/stream') # execute '/stream' sse
    return render_template('index.html')

上述代码中倒数第二行执行的/stream路由如下:

@app.route('/stream')
def stream():
    # we will use Pub/Sub process to send real-time tweets to client
    def event_stream():
        # instantiate pubsub
        pubsub = red.pubsub()
        # subscribe to tweet_stream channel
        pubsub.subscribe('tweet_stream')
        # initiate server-sent events on messages pushed to channel
        for message in pubsub.listen():
            yield 'data: %s\n\n' % message['data']
    return Response(stream_with_context(event_stream()), mimetype="text/event-stream")

我的代码工作正常,因为它会启动一个新流并在每次搜索"搜索"单击按钮,但它不会停止上一次搜索。例如,如果我的第一个搜索词是" NYC"然后我想搜索一个不同的术语,比如说"洛杉矶"它会给我两个" NYC"和洛杉矶"这不是我想要的。我只想要"洛杉矶"被搜查。我该如何解决?换句话说,如何停止上一个流?我查看了其他以前的主题,我知道我必须使用stream.disconnect(),但我不确定如何在我的代码中实现它。任何帮助或输入将不胜感激。非常感谢!!

3 个答案:

答案 0 :(得分:3)

下面是一些代码,它们会在创建新流时取消旧流。它的工作原理是将新流添加到全局列表,然后在创建新流时在列表中的所有流上调用stream.disconnect()

diff --git a/app.py b/app.py
index 1e3ed10..f416ddc 100755
--- a/app.py
+++ b/app.py
@@ -23,6 +23,8 @@ auth.set_access_token(access_token, access_token_secret)
 app = Flask(__name__)
 red = redis.StrictRedis()

+# Add a place to keep track of current streams
+streams = []

 @app.route('/')
 def index():
@@ -32,12 +34,18 @@ def index():
 @app.route('/search', methods=['POST'])
 # gets search-keyword and starts stream
 def streamTweets():
+        # cancel old streams
+        for stream in streams:
+            stream.disconnect()
+
        search_term = request.form['tweet']
        search_term_hashtag = '#' + search_term
        # instantiate listener
        listener = StdOutListener()
        # stream object uses listener we instantiated above to listen for data
        stream = tweepy.Stream(auth, listener)
+        # add this stream to the global list
+        streams.append(stream)
        stream.filter(track=[search_term or search_term_hashtag],
                async=True) # make sure stream is non-blocking
        redirect('/stream') # execute '/stream' sse

这没有解决的是会话管理的问题。使用当前设置,一个用户的搜索将影响所有用户的搜索。这可以通过为用户提供一些标识符并将其流与其标识符一起存储来避免。最简单的方法是使用Flask的session支持。您也可以使用Pierre建议的requestId执行此操作。在任何一种情况下,您还需要使用代码来注意用户何时关闭页面并关闭其流。

答案 1 :(得分:1)

免责声明:我对Tweepy一无所知,但这似乎是一个设计问题。

您是否尝试将状态添加到RESTful API?您可能遇到设计问题。 正如JRichardSnape回答的那样,您的API不应该是负责取消请求的API;它应该在前端完成。我的意思是在javascript / AJAX / etc中调用这个函数,添加另一个调用,到新函数

@app.route('/cancelSearch', methods=['POST']) 使用" POST"具有搜索条件。只要您没有状态,您就无法在异步通话中安全地执行此操作:想象一下其他人同时进行相同的搜索然后取消一个会取消两者(记住,你没有状态,所以你不知道你取消了谁)。也许你的设计确实需要状态

如果你必须继续使用这个,不要介意打破无国籍的"规则,然后添加一个"州"根据您的要求。在这种情况下,它不是那么糟糕,因为您可以启动一个线程并使用userId命名它,然后在每次新搜索时终止线程

def streamTweets():
    search_term = request.form['tweet']
    userId = request.form['userId'] # If your limit is one request per user at a time. If multiple windows can be opened and you want to follow this limit, store userId in a cookie.
    #Look for any request currently running with this ID, and cancel them

或者,您可以返回requestId,然后您将保留在前端,可以调用cancelSearch?requestId=$requestId。在cancelSearch中,你必须找到待处理的请求(因为你没有使用你自己的线程,所以这听起来很糟糕)并断开它。

出于好奇,我只是看了你在Google上搜索时会发生什么,并且它使用了GET请求。看看(调试工具 - >网络;然后输入一些文本并查看自动填充)。 Google会使用随每个请求发送的令牌(每次输入内容时))。它并不意味着它被用于此,但这基本上就是我所描述的。 如果您不想要会话,请使用唯一标识符

答案 2 :(得分:1)

我通过使用计时器方法解决了但是我仍在寻找pythonic方式。

from streamer import StreamListener
def stream():
    hashtag = input
    #assign each user an ID ( for pubsub )
    StreamListener.userid = random_user_id
    def handler(signum, frame):
        print("Forever is over")
        raise Exception("end of time")

    def main_stream():
        stream = tweepy.Stream(auth, StreamListener())
        stream.filter(track=track,async=True)
        redirect(url_for('map_stream'))

    def close_stream():
        # this is for closing client list in redis but don't know it's working
        obj = redis.client_list(tweet_stream)
        redis_client_list = obj[0]['addr']
        redis.client_kill(redis_client_list)
        stream = tweepy.Stream(auth, StreamListener())
        stream.disconnect()

    import signal
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(300)
    try:
        main_stream()
    except Exception:
        close_stream()
        print("function terminate")