
Download tweets + media
Collect timelines per user or search by query with a media-only filter. Preserve attached images and videos as Base64 or extracted files.
TTLabTweetCrawler
Text Technology Lab · Goethe University Frankfurt

TTLabTweetCorpus contains 39,546 public X posts, including 19,056 with media information. More than 15,000 posts come from German politicians during the 2024–2025 election period, and the corpus is linked to GerParCor. Its download component is TTLabTweetCrawler.
$ ttlab-collect-tweets per-query \
--query "WM2026" \
--media-only \
--exclude-retweets \
--max-tweets 2000 \
--output output/wm2026
✓ Saved page 0001 · 100 posts
✓ Following pagination token…
✓ Preserved media + referenced posts
X_BEARER_TOKENResearch workflow
TTLabTweetCrawler provides the download layer of TTLabTweetCorpus; GerParCor and DUUI connect the corpus to parallel text and multimodal analysis workflows.

Collect timelines per user or search by query with a media-only filter. Preserve attached images and videos as Base64 or extracted files.
TTLabTweetCrawler
The collected social-media data can be linked with parliamentary records from the German Parliamentary Corpus.
GerParCor ↗The DUUI pipeline is configured and executed in Java. DUUI-spaCy first segments each tweet into sentences before the selected emotion, topic, and sentiment models run. DUUI provides more components and models; the paper uses the pipeline documented below.
DUUI ↗
Three emotion, three topic, and three sentiment models annotated 33,921 tweets. Each model was then predicted from the outputs of the other eight.
3 emotion · 3 topic · 3 sentiment
Attached images and video frames can be processed with vision-language models served through vLLM.
vLLM · image · videoStudy model selection
The study combines nine text models for emotion, topic, and sentiment with one multimodal vision-language model for structured image and video annotation.
Multimodal model selection · 04B
Multimodal Automatic Annotation ↗ serves Qwen2.5-VL-3B-Instruct ↗ through vLLM for structured multimodal classification.
anger · sadness · apprehension · confusion · happiness
positive · neutral · negative
000 · 100 · 200 · 300 · 400 · 500 · 600 · 700 · 800 · 900
For each task, the model returns a predicted label, a confidence distribution over all labels, and a brief evidence-based explanation in one valid JSON object.
You are an AI system tasked with performing multimodal analysis on a media input (image). Your responsibilities include:
1. **Emotion Classification**
Identify one or more emotional states expressed visually in the image. Use the following emotion labels:
- anger (AN)
- sadness (SD)
- apprehension (AP)
- confusion (CO)
- happiness (HA)
2. **Sentiment Analysis**
Assess the overall sentiment conveyed by the image. Choose from:
- positive
- neutral
- negative
3. **Topic Classification (Based on DDC Level 1)**
Determine the most relevant topic of the image using one of the following Dewey Decimal classes:
- 000: Computer science, information, and general works
- 100: Philosophy and psychology
- 200: Religion
- 300: Social sciences
- 400: Language
- 500: Science
- 600: Technology
- 700: Arts and recreation
- 800: Literature
- 900: History and geography
You must return the result in **valid JSON format**, with the following components for each of the three tasks:
- The **predicted label**
- A **confidence distribution** over all possible labels (values between 0 and 1, summing to 1)
- A **brief explanation**, grounded in specific visual or contextual cues observed in the image
The output must follow this structure:
{
"emotion": {
"predicted": "<label>",
"confidence_distribution": {
"anger": <float>,
"sadness": <float>,
"apprehension": <float>,
"confusion": <float>,
"happiness": <float>
},
"explanation": "<evidence-based explanation>"
},
"sentiment": {
"predicted": "<label>",
"confidence_distribution": {
"positive": <float>,
"neutral": <float>,
"negative": <float>
},
"explanation": "<evidence-based explanation>"
},
"topic": {
"predicted": "<label>",
"confidence_distribution": {
"000": <float>,
"100": <float>,
"200": <float>,
"300": <float>,
"400": <float>,
"500": <float>,
"600": <float>,
"700": <float>,
"800": <float>,
"900": <float>
},
"explanation": "<evidence-based explanation>"
}
}
Important Constraints:
All outputs must be grounded in the specific content of the image provided.
You must not copy or reuse example values.
The confidence values must reflect the actual visual cues in the image.
If the image contains multiple people or elements, base the classification on the dominant or most salient features, but take all visual evidence into account when calculating confidence.
Return only the JSON object. Do not include commentary or template examples.
You are an AI system tasked with performing multimodal analysis on a media input (video and accompanying audio if exists). Your responsibilities include:
1. **Emotion Classification**
Identify one or more emotional states expressed visually in the image. Use the following emotion labels:
- anger (AN)
- sadness (SD)
- apprehension (AP)
- confusion (CO)
- happiness (HA)
2. **Sentiment Analysis**
Assess the overall sentiment conveyed by the image. Choose from:
- positive
- neutral
- negative
3. **Topic Classification (Based on DDC Level 1)**
Determine the most relevant topic of the image using one of the following Dewey Decimal classes:
- 000: Computer science, information, and general works
- 100: Philosophy and psychology
- 200: Religion
- 300: Social sciences
- 400: Language
- 500: Science
- 600: Technology
- 700: Arts and recreation
- 800: Literature
- 900: History and geography
You must return the result in **valid JSON format**, with the following components for each of the three tasks:
- The **predicted label**
- A **confidence distribution** over all possible labels (values between 0 and 1, summing to 1)
- A **brief explanation**, grounded in specific visual or contextual cues observed in the image
The output must follow this structure:
{
"emotion": {
"predicted": "<label>",
"confidence_distribution": {
"anger": <float>,
"sadness": <float>,
"apprehension": <float>,
"confusion": <float>,
"happiness": <float>
},
"explanation": "<evidence-based explanation>"
},
"sentiment": {
"predicted": "<label>",
"confidence_distribution": {
"positive": <float>,
"neutral": <float>,
"negative": <float>
},
"explanation": "<evidence-based explanation>"
},
"topic": {
"predicted": "<label>",
"confidence_distribution": {
"000": <float>,
"100": <float>,
"200": <float>,
"300": <float>,
"400": <float>,
"500": <float>,
"600": <float>,
"700": <float>,
"800": <float>,
"900": <float>
},
"explanation": "<evidence-based explanation>"
}
}
Important Constraints:
All outputs must be grounded in the specific content of the image provided.
You must not copy or reuse example values.
The confidence values must reflect the actual visual cues in the image.
If the image contains multiple people or elements, base the classification on the dominant or most salient features, but take all visual evidence into account when calculating confidence.
Return only the JSON object. Do not include commentary or template examples.
@misc{Bai:et:al:2025,
title = {Qwen2.5-VL Technical Report},
author = {Shuai Bai and Keqin Chen and Xuejing Liu and Jialin Wang and Wenbin Ge and Sibo Song and Kai Dang and Peng Wang and Shijie Wang and Jun Tang and Humen Zhong and Yuanzhi Zhu and Mingkun Yang and Zhaohai Li and Jianqiang Wan and Pengfei Wang and Wei Ding and Zheren Fu and Yiheng Xu and Jiabo Ye and Xi Zhang and Tianbao Xie and Zesen Cheng and Hang Zhang and Zhibo Yang and Haiyang Xu and Junyang Lin},
year = {2025},
eprint = {2502.13923},
archivePrefix = {arXiv},
primaryClass = {cs.CV},
url = {https://arxiv.org/abs/2502.13923}
}
Text model selection · 04A
DUUI contains more components and supports more models than those listed here. For the associated paper, we used exactly the nine models below through the linked DUUI components.
Before emotion, topic, and sentiment annotation, the DUUI-spaCy component ↗ splits every tweet into sentences [P1].
| ID | Task | DUUI Model Name | Short description | DUUI component | Citation |
|---|---|---|---|---|---|
| Emotion Models E1–E3 | |||||
| E1 | Emotion | finetuned-twitter-xlm-roberta-base-emotion |
Twitter-domain XLM-RoBERTa fine-tuned for multilingual emotion classification. | Emotion ↗ | [E1] |
| E2 | Emotion | dreamy-xlm-roberta-emotion |
DReAMy XLM-RoBERTa Large model for detecting emotion presence in multilingual text. | Emotion ↗ | [E2] |
| E3 | Emotion | pol_emo_mDeBERTa |
Multilingual mDeBERTa trained on political communication to identify eight discrete emotions. | Emotion ↗ | [E3] |
| Topic Models T1–T3 | |||||
| T1 | Topic | text2ddc |
fastText-based topic annotation mapped to the Dewey Decimal Classification; provided through its dedicated DUUI component. | DDC fastText ↗ | [T1] |
| T2 | Topic | multilingual-iptc-media-topic-classifier |
XLM-RoBERTa Large classifier for the 17 top-level IPTC Media Topic categories. | Topic ↗ | [T2] |
| T3 | Topic | tweet-topic-large-multilingual |
Large multilingual topic classifier developed for social-media posts and cross-lingual analysis. | Topic ↗ | [T3] |
| Sentiment Models S1–S3 | |||||
| S1 | Sentiment | citizenlab-twitter-xlm-roberta-base-sentiment-finetunned |
CitizenLab multilingual XLM-RoBERTa sentiment model fine-tuned on Twitter data. | Sentiment ↗ | [S1] |
| S2 | Sentiment | distilbert-base-multilingual-cased-sentiment |
Compact multilingual DistilBERT student model for sentiment classification. | Sentiment ↗ | [S2] |
| S3 | Sentiment | twitter-xlm-roberta-base-sentiment |
XLM-T sentiment classifier based on multilingual Twitter language-model pretraining. | Sentiment ↗ | [S3] |
We trained nine classifiers—one for each annotation model. Each classifier predicted the annotations of one model from the outputs of the other eight.
Each BibTeX record can be copied independently. P1 identifies preprocessing; E, T, and S identify the model groups.
@misc{Honnibal:Montani:et:al:2020,
author = {Honnibal, Matthew and Montani, Ines and Van Landeghem, Sofie and Boyd, Adriane},
doi = {10.5281/zenodo.1212303},
title = {{spaCy: Industrial-strength Natural Language Processing in Python}},
year = {2020}
}
@misc{Shivshankar:2023,
author = {Shivshankar},
title = {finetuned-twitter-xlm-roberta-base-emotion},
year = {2023},
url = {https://huggingface.co/02shanky/finetuned-twitter-xlm-roberta-base-emotion},
publisher = {Hugging Face}
}
@article{Bertolini:et:al:2024,
title = {DReAMy: a library for the automatic analysis and annotation of dream reports with multilingual large language models},
journal = {Sleep Medicine},
volume = {115},
pages = {406--407},
year = {2024},
issn = {1389-9457},
doi = {10.1016/j.sleep.2023.11.1092},
url = {https://www.sciencedirect.com/science/article/pii/S1389945723015186},
author = {L. Bertolini and A. Michalak and J. Weeds}
}
@article{Widmann:Wich:2023,
title = {Creating and Comparing Dictionary, Word Embedding, and Transformer-Based Models to Measure Discrete Emotions in German Political Text},
volume = {31},
doi = {10.1017/pan.2022.15},
number = {4},
journal = {Political Analysis},
author = {Widmann, Tobias and Wich, Maximilian},
year = {2023},
pages = {626--641}
}
@inproceedings{Uslu:et:al:2018,
author = {Tolga Uslu and Alexander Mehler and Andreas Niekler and Daniel Baumartz},
title = {Towards a {DDC}-based Topic Network Model of Wikipedia},
booktitle = {Proceedings of 2nd International Workshop on Modeling, Analysis, and Management of Social Networks and their Applications (SOCNET 2018)},
url = {https://www.texttechnologylab.org/wp-content/uploads/2018/03/TowardsDDC.pdf},
year = {2018}
}
@article{Kuzman:Ljubesic:2025,
title = {LLM Teacher-Student Framework for Text Classification With No Manually Annotated Data: A Case Study in IPTC News Topic Classification},
volume = {13},
issn = {2169-3536},
doi = {10.1109/ACCESS.2025.3544814},
journal = {IEEE Access},
publisher = {IEEE},
author = {Kuzman, Taja and Ljube{\v{s}}i{\'c}, Nikola},
year = {2025},
pages = {35621--35633}
}
@inproceedings{Antypas:et:al:2024,
title = {Multilingual Topic Classification in {X}: Dataset and Analysis},
author = {Antypas, Dimosthenis and Ushio, Asahi and Barbieri, Francesco and Camacho-Collados, Jose},
booktitle = {Proceedings of the 2024 Conference on Empirical Methods in Natural Language Processing},
month = {nov},
year = {2024},
address = {Miami, Florida, USA},
publisher = {Association for Computational Linguistics},
url = {https://aclanthology.org/2024.emnlp-main.1123/},
doi = {10.18653/v1/2024.emnlp-main.1123},
pages = {20136--20152}
}
@misc{Citizenlab:2022,
author = {CitizenLabDotCo},
title = {CitizenLab multilingual XLM-Roberta sentiment},
year = {2022},
url = {https://huggingface.co/citizenlab/twitter-xlm-roberta-base-sentiment-finetunned},
publisher = {Hugging Face}
}
@misc{Schmid:2022,
author = {Schmid, Philipp},
title = {distilbert-base-multilingual-cased-sentiment},
year = {2022},
url = {https://huggingface.co/philschmid/distilbert-base-multilingual-cased-sentiment},
publisher = {Hugging Face}
}
@inproceedings{Barbieri:et:al:2022,
title = {{XLM}-{T}: Multilingual Language Models in {Twitter} for Sentiment Analysis and Beyond},
author = {Barbieri, Francesco and Espinosa Anke, Luis and Camacho-Collados, Jose},
booktitle = {Proceedings of the Thirteenth Language Resources and Evaluation Conference},
month = {jun},
year = {2022},
address = {Marseille, France},
publisher = {European Language Resources Association},
url = {https://aclanthology.org/2022.lrec-1.27/},
pages = {258--266}
}
Five-minute setup
Requires Python 3.10 or newer. Clone the GitHub repository and install its Python package in an isolated environment. This provides the tweet, media, GerParCor download, and sentence-matching commands used throughout the workflow below.
Prefer an environment variable. A token passed with --bearer-token may be visible in shell history and the process list.
git clone https://github.com/texttechnologylab/TTLabTweetCorpus.git
cd TTLabTweetCorpus
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install --editable ./python
export X_BEARER_TOKEN="YOUR_BEARER_TOKEN"
Command recipes
Downloads the user's posts between two RFC3339 timestamps. Replies and retweets are included unless excluded explicitly.
ttlab-collect-tweets per-user \
--user-id 123456789 \
--start-time "2025-01-01T00:00:00Z" \
--end-time "2025-02-20T00:00:00Z" \
--max-tweets 500 \
--output output/example_user--media-only adds has:media; --exclude-retweets adds -is:retweet. The original query remains readable.
ttlab-collect-tweets per-query \
--query "WM2026" \
--media-only \
--exclude-retweets \
--max-tweets 2000 \
--page-size 100 \
--output output/wm2026Reads saved API pages, selects direct image URLs or the highest-bitrate MP4 variant, and writes enriched copies.
ttlab-download-media \
--input output/wm2026 \
--output output/wm2026_base64 \
--timeout 120Decodes Base64 and names output by tweet ID. Multiple attachments receive deterministic _1, _2 suffixes.
ttlab-extract-media \
--input output/wm2026_base64 \
--output output/wm2026_mediaDownloads every XMI document for one parliament and an inclusive date range. Use --list-only to preview the selection. Individual files are also available from the manual GerParCor web interface ↗.
ttlab-download-gerparcor \
--parliament Bundestag \
--start 2010-01-01 \
--end 2010-01-31 \
--output gerparcor_documents \
--insecureEmbeds complete tweets and individual GerParCor sentences. Use --top-k for compact results or --all-matches for exhaustive comparisons on small datasets.
ttlab-match-tweets-xmi \
--tweets tweet_json_directory \
--xmi xmi_directory \
--output output/matches.json.gz \
--top-k 5 \
--minimum-score 0.4 \
--device cpuComplete CLI reference
Required arguments are marked. Defaults shown here match the command-line help.
Choose exactly one mode. Run per-user for a timeline or per-query for a search.
-o, --outputrequiredDirectory receiving compressed API response pages.--bearer-tokenenvironmentX API token. Falls back to X_BEARER_TOKEN.--start-timenoneEarliest post time in RFC3339 UTC format.--end-timenoneLatest post time in RFC3339 UTC format.--since-idnoneOnly posts newer than this post ID.--until-idnoneOnly posts older than this post ID.--pagination-tokennoneContinue manually from a known X pagination token.--page-size100Results requested per page; accepted range: 10–100.--max-pagesunlimitedStop after this number of API response pages.--max-tweetsunlimitedStop after this number of primary posts.--timeout30HTTP request timeout in seconds.--max-retries3Retries for rate limits, network failures, and server errors.--no-wait-rate-limitoffFail immediately on HTTP 429 instead of waiting.--tweet-fieldspublic setOverride the comma-separated post fields.--expansionspublic setOverride related objects included in the response.--media-fieldspublic setOverride fields returned for expanded media.--poll-fieldsall publicOverride fields returned for expanded polls.--user-fieldspublic setOverride fields returned for expanded users.--place-fieldsall publicOverride fields returned for expanded places.--user-idone requiredNumeric X user ID. Mutually exclusive with --username.--usernameone requiredX username; resolved to a user ID automatically.--excludenoneExclude replies, retweets, or both as a comma-separated value.per-user.--queryrequiredSearch term or complete X search expression.--media-onlyoffAdd has:media unless a media operator already exists.--exclude-retweetsoffAdd -is:retweet unless already present.--search-endpointrecentChoose recent or plan-dependent full archive all.--sort-orderAPI defaultChoose recency or relevancy.per-query.Reads collected .json.gz pages and writes enriched copies.
-i, --inputrequiredInput page or directory.
-o, --outputrequiredSeparate directory for enriched pages.
--recursiveoffSearch nested input directories.
--timeout60 secTimeout for each image/video request.
--max-bytesunlimitedReject an individual media file above this byte count.
--fail-fastoffStop on first error instead of recording it in JSON.
ttlab-download-media \
-i output/wm2026 \
-o output/wm2026_base64 \
--recursive \
--timeout 120 \
--max-bytes 104857600Decodes embedded Base64 data into image and video files.
-i, --inputrequiredEnriched page or directory.
-o, --outputrequiredDirectory receiving binary media files.
--recursiveoffSearch nested input directories.
123456.jpgMultiple123456_1.jpg123456_2.mp4Resolves usernames and preserves X lookup errors.
usernamesoptional*One or more names, with or without @.
-i, --inputoptional*JSON or text file containing usernames.
-o, --outputrequiredDestination JSON file.
--bearer-tokenenvironmentToken or X_BEARER_TOKEN.
--timeout30 secHTTP request timeout.
* Supply direct usernames, an input file, or both.
Queries document metadata and downloads XMI files for an inclusive date range.
--parliamentrequiredGerParCor parliament name.
--startrequiredInclusive start date in YYYY-MM-DD format.
--endrequiredInclusive end date in YYYY-MM-DD format.
--outputgerparcor_documentsDirectory receiving downloaded XMI files.
--list-onlyoffPrint matching documents without downloading them.
--insecureoffDisable TLS certificate verification only when required.
ttlab-download-gerparcor \
--parliament PARLIAMENT_NAME \
--start YYYY-MM-DD \
--end YYYY-MM-DD \
--output xmi_directory \
--list-onlyComputes normalized embeddings and cosine similarities between complete tweets and individual XMI sentences.
--tweetsrequiredTweet JSON/JSON.GZ file or directory.
--xmirequiredXMI/XMI.GZ file or directory.
--outputrequiredDestination JSON or compressed JSON.GZ file.
--modelmultilingual MiniLMSentence Transformers model name or local model path.
--top-k5Highest-scoring sentence matches retained per tweet.
--all-matchesoffRetain every comparison instead of applying the top-k limit.
--minimum-score-1Discard cosine scores below this threshold.
--embedding-batch-size64Texts encoded in one model batch.
--tweet-chunk-size2048Tweet embeddings compared in one matrix chunk.
--location-rootcurrent directoryBase directory for source paths stored in the result.
--include-textoffCopy tweet and sentence text into the result.
--deviceautomaticExecution device such as cpu, cuda, or mps.
ttlab-match-tweets-xmi \
--tweets tweet_json_directory \
--xmi xmi_directory \
--output output/matches.json.gz \
--model MODEL_NAME \
--top-k 5 \
--device cpuPractical reference
The collector follows every meta.next_token until results end or a configured tweet/page limit is reached.
Each response remains a separate .json.gz file, preserving data, includes, meta, and partial errors.
Public Articles, media metadata, Notes, cards, Communities, metrics, authors, places, polls, and references are requested.
HTTP failures stop collection clearly. Media failures can be recorded per item so that the remaining dataset still completes.
Output naming
output/wm2026_media/
├── 1893300012345678901.jpg
├── 1893300098765432102_1.jpg
└── 1893300098765432102_2.mp4Usage awareness
X API pricing is controlled by X and may change. Use --max-tweets together with --page-size, monitor the Developer Console, and configure an account spending limit.
Research software
TTLabTweetCorpus accompanies an accepted paper at KONVENS 2026. A publication link and DOI will be added when available.
Associated publication
@inproceedings{bagci:et:al:2026,
title = {Extending a Parliamentary Corpus with {MP}s{\textquoteright} Tweets: Automatic Annotation and Evaluation Using {TTLABTWEETCORPUS}},
author = {Mevlüt Bagci and Ali Abusaleh and Daniel Baumartz and Alexander Mehler and Giuseppe Abrami and Maxim Konca},
booktitle = {KONVENS 2026 - Context matters: NLP beyond Text},
year = {2026},
address = {Hamburg, Germany},
note = {Accepted}
}
Ready to collect?
ttlab-collect-tweets per-query --query "WM2026" --max-tweets 10 --page-size 10 --output output/test