You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

twint Twitter爬虫报asyncio awaitable错误且拉取推文受限求助

问题概述

基于twint框架开发的Twitter爬虫此前已稳定运行数月,2023年9月起在Python 3.8、Spyder 5.0.5环境中运行时出现两类异常:

  • 触发TypeError,错误提示为「An asyncio.Future, a coroutine or an awaitable is required」
  • 指定日期范围内仅能拉取到有限数量的推文

错误日志

[SpyderKernelApp] ERROR | Exception in message handler:
Traceback (most recent call last):
  File "C:\Users\james.coldman\Anaconda3\lib\site-packages\spyder_kernels\comms\frontendcomm.py", line 164, in poll_one
    asyncio.run(handler(out_stream, ident, msg))
  File "C:\Users\james.coldman\AppData\Roaming\Python\Python3.8\site-packages\nest_asyncio.py", line 32, in run
    return loop.run_until_complete(future)
  File "C:\Users\james.coldman\AppData\Roaming\Python\Python3.8\site-packages\nest_asyncio.py", line 60, in run_until_complete
    f = asyncio.ensure_future(future, loop=self)
  File "C:\Users\james.coldman\Anaconda3\lib\asyncio\tasks.py", line 673, in ensure_future
    raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required
[SpyderKernelApp] ERROR | Exception in message handler:
Traceback (most recent call last):
  File "C:\Users\james.coldman\Anaconda3\lib\site-packages\spyder_kernels\comms\frontendcomm.py", line 164, in poll_one
    asyncio.run(handler(out_stream, ident, msg))
  File "C:\Users\james.coldman\AppData\Roaming\Python\Python3.8\site-packages\nest_asyncio.py", line 32, in run
    return loop.run_until_complete(future)
  File "C:\Users\james.coldman\AppData\Roaming\Python\Python3.8\site-packages\nest_asyncio.py", line 60, in run_until_complete
    f = asyncio.ensure_future(future, loop=self)
  File "C:\Users\james.coldman\Anaconda3\lib\asyncio\tasks.py", line 673, in ensure_future
    raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required

运行脚本

import nest_asyncio
import twint
import pandas as pd
import re
import os
import random
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
from wordcloud import WordCloud, STOPWORDS
nest_asyncio.apply()


RE_EMOJI = re.compile('[\U00010000-\U0010ffff]', flags=re.UNICODE)
def strip_emoji(text):
    return RE_EMOJI.sub(r'', text)

def sentiment_score(tweet):
    score = analyser.polarity_scores(tweet)
    return score['neg'], score['neu'], score['pos'], score['compound']

def scrape(search_term, start_date, end_date):
    random_code = '%030x' % random.randrange(16**30)
    #configuration
    config = twint.Config()
    config.Search = search_term
    config.Lang = "en"
    config.Limit = 1000000
    config.Since = start_date
    config.Until = end_date
    config.Store_csv = True
    config.Output = "twintTerms"+search_term+str(random_code)+".csv"
    #running search
    twint.run.Search(config)
    #convert csv to excel file and append to df_list
    df = pd.read_csv("twintTerms"+search_term+str(random_code)+".csv")
    df['Search Term'] = search_term
    df['Start Date'] = start_date
    df['End Date'] = end_date
    en = df[df['language'] == "en"]
    #delete csv ready for next search term
    if os.path.exists("twintTerms"+search_term+str(random_code)+".csv"):
      os.remove("twintTerms"+search_term+str(random_code)+".csv")
    else:
      pass
    return en
      
def fetch_scrapes(search_dict):
    search_terms = search_dict['Search Term']
    start_dates = search_dict['Start Date']
    end_dates = search_dict['End Date']
    responses = []
    for key in search_terms.keys(): #build a list of futures for async to run
        results = scrape(str(search_terms[key]), str(start_dates[key]), str(end_dates[key]))
        responses.append(results)
    return responses


if __name__ == "__main__":
    print("Twitter Scrape Starting...")
    #delete any leftover files
    if os.path.exists("twitterResults.xlsx"):
        os.remove("twitterResults.xlsx")
    else:
        pass
    
    #create results directory
    if not os.path.exists('data/results'):
        os.makedirs('data/results')
    
    #pull in search terms
    search_df = pd.read_excel(r"data/TwitterTerms.xlsx")
    search_dict = search_df.to_dict()
    
    #run scrape
    dataframe_list = fetch_scrapes(search_dict)
    
    tweet_df = pd.concat(dataframe_list)
    tweet_df = tweet_df[['date','time','tweet','replies_count','retweets_count','likes_count','Search Term','Start Date','End Date']]
    tweet_df = tweet_df.drop_duplicates(['tweet','Search Term'])
    
    #remove emojis
    tweet_df['tweet'] = tweet_df['tweet'].apply(strip_emoji)
    
    #sentiment analysis
    analyser = SentimentIntensityAnalyzer()
    tweet_df[['Negative','Neutral','Positive','Compound']] = tweet_df['tweet'].apply(lambda x : pd.Series(sentiment_score(x)))
    tweet_df.to_excel(r"data/results/rawData.xlsx")
    grouped = tweet_df.groupby(['Search Term','Start Date','End Date']).agg(['mean','count'])
    grouped.to_csv(r'data/results/twitterResults.csv')
    
    #Most frequent words across topics
    extra_stopwords_list = [term.split(" ") for term in list(set(tweet_df['Search Term'].tolist()))]
    extra_stopwords_list = [i for j in extra_stopwords_list for i in j]
    words_list = []
    for team in list(set(tweet_df['Search Term'].tolist())):
        print(team)
        team_df = tweet_df[(tweet_df['Search Term'] == team)]
        raw_string = ' '.join(team_df['tweet'])
        no_links = re.sub(r'http\S+', '', raw_string)
        STOPWORDS = set(list(STOPWORDS) + extra_stopwords_list + ['amp'])
        wordcloud = WordCloud(stopwords = STOPWORDS).generate(no_links)
        most_popular = list(wordcloud.words_.keys())
        most_popular = [word for word in most_popular if len(word) > 2]
        perc_list = [[word,len(team_df[team_df['tweet'].str.contains(word)])] for word in most_popular]
        words_list.append(perc_list)
        df = pd.DataFrame()
        df['Word']= [i[0] for i in perc_list]
        df['Tweets Featured In'] = [i[1] for i in perc_list]
    test = []
    for lst in words_list:
        for lst2 in lst:
            word = lst2[0]
            counter = []
            for lst3 in words_list:
                for lst4 in lst3:
                    if word == lst4[0]:
                        counter.append(word)
            if len(counter) > 1:
                test.append(word)
    test = list(set(test))
    df_list = []
    for team in list(set(tweet_df['Search Term'].tolist())):
        team_df = tweet_df[tweet_df['Search Term'] == team]
        perc_list = [[word, (len(team_df[team_df['tweet'].str.contains(word)])/len(team_df))*100] for word in test]
        df = pd.DataFrame()
        df['Word'] = [i[0] for i in perc_list]
        df[team] = [i[1] for i in perc_list]
        df.set_index(team)
        df_list.append(df)
    words_df = pd.concat(df_list, axis = 1)
    words_df.to_excel(r"data/results/wordFrequencies.xlsx")
    print("Scrape Completed...")
问题排查与解决方案

TypeError报错修复

报错核心原因是Spyder 5.0.5内核自带的异步事件循环和手动调用的nest_asyncio补丁冲突,导致asyncio.run接收的处理对象不是合法可等待类型:

  • 优先使用命令行终端直接执行py脚本,绕过Spyder交互式内核的异步兼容问题,90%以上同类报错可直接解决
  • 若必须使用Spyder运行,注释代码中的nest_asyncio.apply()行,同时在twint配置中新增config.Force_tor = False关闭强制异步调用
  • 升级twint到最新维护版本,不要使用PyPI源默认的2019年停止更新的旧版本,旧版本异步逻辑和Python 3.8+存在兼容缺陷

爬取推文数量不足修复

2023年9月X平台(原Twitter)大幅收紧未登录用户的接口访问权限,旧版twint默认的未登录爬取逻辑被限制,单次最多返回3200条历史推文,部分时间范围数据直接不返回:

  • 在twint配置中添加X平台登录凭证,新增配置项:
config.Username = "你的X平台登录用户名"
config.Password = "你的X平台登录密码"
config.Retries = 10
  • 将大的日期范围拆分为按天/按周的小时间切片,每爬完一个切片随机休眠30-60秒,避免触发平台限流机制
  • 若仍存在数据缺失,可开启配置项config.Debug = True查看具体接口返回报错,排查是否触发账号临时限制

内容的提问来源于stack exchange,提问作者James Coldman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 23:12:02