11  Datensammlung: Bluesky

11.1 Setup

11.1.1 Pakete laden

Zur Sammlung von Bluesky-Daten nutzen wir das Paket atrrr.

Wie immer müssen wir das Paket zunächst (einmalig) installieren und dann (in jeder Sitzung) laden.

install.packages("atrrr")
library(atrrr)

Zusätzlich nutzen wir auch in diesem Abschnitt Funktionen aus den tidyverse-Paketen. Sofern dies in der aktuellen R-Session noch nicht geschehen ist, müssen wir auch diese Pakete erst laden

library(tidyverse)

11.1.2 Authentifizierung

Bevor wir Daten sammeln können, müssen wir uns mit unserem Bluesky-Account authentifizieren.

auth("your_user_name") # hier Ihren User Name einfügen (z.B., tmueller.bsky.social)

Sie müssen sich bei Ihrem Bluesky-Konto anmelden und ein „App-Passwort“ erstellen. Dieses Passwort müssen Sie dann in das Popup-Fenster eingeben, das nach dem Ausführen der auth()-Funktion erscheint.

11.2 Daten sammeln

Die Informationen zu den Accounts, von denen wir Daten sammeln wollen, stammen aus dem Datensatz Social-Media-Konten der Kandidierenden bei der Bundestagswahl 2025 (Breuer et al., 2026). Beispiele dafür, wie Sie Accounts aus diesem Datensatz auswählen können, finden Sie im Skript get_accounts.R.

Wir wählen hier beispielhaft aus diesem Datensatz Accounts von prominenten Politiker_innen von der CDU, der SPD, den Grünen, der Linken und der FDP.

bs_accounts <- c("serapgueler.bsky.social", "larsklingbeil.bsky.social",
              "katharinadroege.bsky.social", "heidireichinnek.dielinkebt.de",
              "christianduerr.bsky.social")

NB: Accountnamen bzw User Handles können sich ändern. Dies ist hier z.B. der Fall für Heidi Reichinnek, deren Accountname/User Handle sich seit der Erstellung des o.g. Datensatzes geändert hat. In solchen Fällen muss man ggf. über die Suchfunktion auf Bluesky den korrekten Accountnamen recherchieren.

11.2.1 Profilinformationen

Profilinformationen zu einem Account können mit der Funktion get_user_info() abgerufen werden

bs_user <- get_user_info("serapgueler.bsky.social")

glimpse(bs_user)
Rows: 1
Columns: 15
$ did               <chr> "did:plc:hngkefcientj2tq2gnspgfol"
$ actor_handle      <chr> "serapgueler.bsky.social"
$ actor_name        <chr> "Serap Güler"
$ actor_avatar      <chr> "https://cdn.bsky.app/img/avatar/plain/did:plc:hngke…
$ associated        <list> [0, 0, 0, FALSE, ["followers"]]
$ viewer            <list> [FALSE, FALSE, [32, [["did:plc:6gq5giuz35vjuss33oott…
$ labels            <list> []
$ created_at        <chr> "2023-09-21T14:22:06.936Z"
$ verification      <list> [[["did:plc:z72i7hdynmk6r22z27h6tvur", "Bluesky", "b…
$ actor_description <chr> "Staatsministerin @AuswaertigesAmt, Mitglied im Deut…
$ indexed_at        <chr> "2025-05-09T15:37:49.907Z"
$ banner            <chr> "https://cdn.bsky.app/img/banner/plain/did:plc:hngk…
$ followers_count   <int> 3928
$ follows_count     <int> 119
$ posts_count       <int> 149

Um Profilinformationen zu mehreren Accounts gleichzeitig abzurufen, können wir die Funktion map_df() aus dem purrr-Paket nutzen. Diese führt eine Funktion für mehrere verschiedene Argumente (in unserem Fall Accounts) aus und verbindet die Ergebnisse zu einem Dataframe.

bs_users <- map_df(bs_accounts,
                 ~get_user_info(actor = .x)) # die Funktion wird für jeden Account aus dem Vektor "bs_accounts" ausgeführt, wobei ".x" den aktuellen Account aus der Liste repräsentiert 

glimpse(bs_users)
Rows: 5
Columns: 15
$ did               <chr> "did:plc:hngkefcientj2tq2gnspgfol", "did:plc:n2fhuib…
$ actor_handle      <chr> "serapgueler.bsky.social", "larsklingbeil.bsky.socia…
$ actor_name        <chr> "Serap Güler", "Lars Klingbeil", "Katharina Dröge, M…
$ actor_avatar      <chr> "https://cdn.bsky.app/img/avatar/plain/did:plc:hngke…
$ associated        <list> [0, 0, 0, FALSE, ["followers"]], [0, 0, 0, FALSE, […
$ viewer            <list> [FALSE, FALSE, [32, [["did:plc:6gq5giuz35vjuss33oot…
$ labels            <list> [], [], [], [], []
$ created_at        <chr> "2023-09-21T14:22:06.936Z", "2023-09-30T20:17:49.93…
$ verification      <list> [[["did:plc:z72i7hdynmk6r22z27h6tvur", "Bluesky", "b…
$ actor_description <chr> "Staatsministerin @AuswaertigesAmt, Mitglied im Deu…
$ indexed_at        <chr> "2025-05-09T15:37:49.907Z", "2024-01-26T01:59:36.925…
$ banner            <chr> "https://cdn.bsky.app/img/banner/plain/did:plc:hngke…
$ followers_count   <int> 3928, 18382, 22028, 46779, 1048
$ follows_count     <int> 119, 68, 215, 127, 116
$ posts_count       <int> 149, 21, 931, 82, 56

11.2.2 Posts

Die Posts eines Accounts können mit der Funktion get_skeets_authored_by() abgerufen werden. Diese Funktion bietet verschiedene Argumente, mit denen man u.a. die Anzahl der abzurufenden Posts begrenzen und die Daten in einem Dataframe-Format zurückgeben kann.

bs_posts1 <- get_skeets_authored_by(actor = "larsklingbeil.bsky.social",
                                limit = 100, # maximale Anzahl der abzurufenden Posts
                                parse = TRUE) # Posts in einem Dataframe zurückgeben

glimpse(bs_posts1)
Rows: 28
Columns: 22
$ uri           <chr> "at://did:plc:n2fhuibwlwhyetcnyxe3vno7/app.bsky.feed.pos…
$ cid           <chr> "bafyreicouhg6hs5d527se3tmxdvk3jmcd6hwhnjovh7puz3rkbiubb…
$ author_handle <chr> "larsklingbeil.bsky.social", "larsklingbeil.bsky.social"…
$ author_name   <chr> "Lars Klingbeil", "Lars Klingbeil", "Lars Klingbeil", "L…
$ text          <chr> "Das Verhalten der US-Regierung zeigt einmal mehr, dass …
$ author_data   <list> ["did:plc:n2fhuibwlwhyetcnyxe3vno7", "larsklingbeil.bsk…
$ post_data     <list> ["app.bsky.feed.post", "2025-02-28T19:18:28.467Z", ["de…
$ embed_data    <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
$ reply_count   <int> 250, 11, 10, 0, 18, 1, 1, 27, 7, 19, 2, 4, 2, 3, 8, 4, 2…
$ repost_count  <int> 125, 6, 2, 0, 29, 0, 0, 4, 17, 26, 4, 13, 10, 11, 58, 22…
$ like_count    <int> 898, 66, 29, 1, 184, 2, 4, 102, 134, 165, 25, 95, 77, 80…
$ quote_count   <int> 29, 1, 1, 0, 4, 0, 0, 12, 0, 8, 0, 0, 0, 1, 9, 0, 0, 0, …
$ indexed_at    <dttm> 2025-02-28 19:18:28, 2023-11-16 12:48:05, 2023-11-04 19…
$ in_reply_to   <chr> NA, NA, NA, "at://did:plc:lv5evbckbwxarpmrasspgvqc/app.b…
$ in_reply_root <chr> NA, NA, NA, "at://did:plc:kohpe2g74jghs5lthhz5ra6c/app.b…
$ quotes        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ tags          <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
$ mentions      <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
$ links         <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
$ langs         <list> ["de"], ["de"], ["de"], ["de"], ["de"], ["de"], ["de"],…
$ labels        <list> [], [], [], [], [], [], [], [], [], [], [], [], [], [],…
$ is_reskeet    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …

Auch hier können wir die Funktion map_df() nutzen, um die Posts mehrerer Accounts zu sammeln.

bs_posts <- map_df(bs_accounts,
                    ~get_skeets_authored_by(actor = .x,
                                            limit = 100, # maximale Anzahl der abzurufenden Posts
                                            parse = TRUE)) # Posts in einem Dataframe zurückgeben

glimpse(bs_posts)
Rows: 564
Columns: 22
$ uri           <chr> "at://did:plc:tjj5mybc6kgmkhhjlg43aedg/app.bsky.feed.pos…
$ cid           <chr> "bafyreiahisubwexm4b7t5ck3d5nyudhnfxj557upfvilwqptnronya…
$ author_handle <chr> "golod.bsky.social", "serapgueler.bsky.social", "serapgu…
$ author_name   <chr> "Vassili Golod", "Serap Güler", "Serap Güler", "Serap Gü…
$ text          <chr> "Ist der deutsche Bundeskanzler ein Feminist? Das und me…
$ author_data   <list> ["did:plc:tjj5mybc6kgmkhhjlg43aedg", "golod.bsky.social…
$ post_data     <list> ["app.bsky.feed.post", "2026-03-11T17:38:21.482Z", ["ap…
$ embed_data    <list> [[["https://cdn.bsky.app/img/feed_thumbnail/plain/did:p…
$ reply_count   <int> 1, 0, 2, 1, 0, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 3, 0, 0, 3,…
$ repost_count  <int> 7, 1, 1, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 1,…
$ like_count    <int> 40, 5, 7, 1, 2, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 0, 3…
$ quote_count   <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ indexed_at    <dttm> 2026-03-11 17:38:26, 2025-12-02 13:27:55, 2025-12-02 13…
$ in_reply_to   <chr> NA, "at://did:plc:hngkefcientj2tq2gnspgfol/app.bsky.feed…
$ in_reply_root <chr> NA, "at://did:plc:hngkefcientj2tq2gnspgfol/app.bsky.feed…
$ quotes        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ tags          <list> <NULL>, <NULL>, "Simbabwe", <NULL>, <NULL>, <NULL>, <NU…
$ mentions      <list> "did:plc:hngkefcientj2tq2gnspgfol", <NULL>, "did:plc:ou…
$ links         <list> "https://wdr.de/k/machiavellipod", <NULL>, <NULL>, <NUL…
$ langs         <list> ["de"], ["de"], ["de"], ["de"], ["de"], ["de"], ["de"],…
$ labels        <list> [], [], [], [], [], [], [], [], [], [], [], [], [], [],…
$ is_reskeet    <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F…

11.2.3 Weitere Daten

Neben Profilinformationen und Posts können mit atrrr auch weitere Daten wie z.B. die Follower oder die Accounts, denen ein Account folgt, gesammelt werden. Die entsprechenden Funktionen sind get_followers() und get_follows(). Auch hier können wir die Funktionen für mehrere Accounts gleichzeitig mit map_df() nutzen.

Da wir uns im Kurs auf die Analyse von Textdaten konzentrieren und die Netzwerkanalyse ausklammern, werden wir diese Daten hier nicht weiter betrachten. Weitere Informationen zu diesen Funktionen finden Sie in der Dokumentation zum Paket atrrr.

Eine Einführung in die Netzwerkanalyse bietet u.a. Kapitel 13 im Online-Buch Computational Analysis of Communication.