Eksplorasiku Scraping Twitter dan Menyimpan Data dengan SQLite

Hai! Aku baru saja menyelesaikan proyek seru tentang scraping data dari Twitter. Jadi ceritanya, aku ingin mengumpulkan tweet yang menggunakan hashtag tertentu untuk keperluan analisis. Setelah mencari-cari, aku menemukan library twscrape yang bisa membantuku melakukan scraping data dari Twitter dengan mudah.

Legalitas Scraping Twitter

Sebelum aku mulai lebih dalam, aku ingin memastikan bahwa apa yang kulakukan itu legal. Beruntungnya, ini legal namun untuk pengguna basic itu dibatasi hanya ratusan saja. Beruntungnya, aku sudah berlangganan akun Twitter dengan centang biru yang memberikan akses lebih luas ke data Twitter dan berharap batasan lebih luas, aku beli langganan ini juga karena takut kalo ada apa-apa ntar akunku hilang alias suspend. Dengan menggunakan akun ini, proses scraping yang kulakukan jadi 100% legal dan aman.

Persiapan

Pertama-tama, aku memastikan bahwa aku sudah menginstal Python versi 3.10 atau lebih baru di komputerku. Kemudian, aku menginstal library twscrape dan aiosqlite menggunakan perintah berikut:

pip install twscrape aiosqlite

Menambahkan Akun Twitter

Untuk bisa menggunakan twscrape, aku perlu menambahkan akun Twitter yang sudah kubeli tadi. Aku bisa melakukannya melalui API Python atau menggunakan perintah CLI. Kali ini, aku memilih untuk menggunakan perintah CLI. Aku membuat file accounts.txt yang berisi informasi akun Twitter dengan format username:password:email:email_password. Lalu, aku menjalankan perintah berikut:

twscrape add_accounts accounts.txt username:password:email:email_password

Setelah menambahkan akun, aku perlu melakukan proses login untuk mendapatkan token yang diperlukan untuk mengakses API Twitter. Aku menjalankan perintah:

twscrape login_accounts

Atau… Kamu bisa langsung masukin akun kita ke fungsi pythonnya langsung. πŸ˜ŠπŸ˜…

Scraping Data Twitter

Sekarang, saatnya melakukan scraping data! Aku membuat skrip Python seperti ini:

Kamu bisa copas skrip dibawah ini yah, pastikan untuk mengganti yang pasti-pasti aja

  await api.pool.add_account("akunmu", "passwordmu", "[email protected]", "email_password")

kalau akunmu gak ada keamanan lanjutan kayak OTP dan semisal, kamu bisa isi akun dan password aja, kalau ada otp.. kamu tambahkan email dan email password. (Hati-hati jangan gegabah yah, untuk belajar gunakan burner akun alias second akun yang beneran tidak penting)

async def scrape_tweets():
  api = API()
  await api.pool.add_account("herbras_", "x", "[email protected]", "email_password")
  await api.pool.login_all()

  last_tweet_id = await get_last_tweet_id()

  date_ranges = [
    ("2024-04-30", "2024-06-01")
  ]

  total_tweets_saved = 0
  for start_date, end_date in date_ranges:
    query = f"#MayLearnings since:{start_date} until:{end_date}"
    if last_tweet_id:
      query += f" since_id:{last_tweet_id}"

    tweets = await gather(api.search(query, limit=2000))

    print(f"Jumlah tweets yang diambil untuk periode {start_date} - {end_date}: {len(tweets)}")

    await save_tweets_to_db(tweets)

    if tweets:
      last_tweet_id = tweets[0].id
      total_tweets_saved += len(tweets)

  print(f"Scraping selesai. Total tweets tersimpan: {total_tweets_saved}")

Skrip ini melakukan scraping tweet yang mengandung hashtag #MayLearnings dalam rentang tanggal tertentu. Aku menggunakan twscrape untuk melakukan pencarian tweet dan membatasi jumlah tweet yang diambil hingga 2000 tweet per rentang tanggal.

Penjelasan Kode scrape_tweets()

api = API()
await api.pool.add_account("herbras_", "x", "[email protected]", "email_password")
await api.pool.login_all()
last_tweet_id = await get_last_tweet_id()
date_ranges = [
    ("2024-05-31", "2024-06-01")
]
total_tweets_saved = 0
for start_date, end_date in date_ranges:
    query = f"#MayLearnings since:{start_date} until:{end_date}"
    if last_tweet_id:
        query += f" since_id:{last_tweet_id}"
    tweets = await gather(api.search(query, limit=2000))
print(f"Jumlah tweets yang diambil untuk periode {start_date} - {end_date}: {len(tweets)}")
await save_tweets_to_db(tweets)
if tweets:
    last_tweet_id = tweets[0].id
    total_tweets_saved += len(tweets)
print(f"Scraping selesai. Total tweets tersimpan: {total_tweets_saved}")

Menyimpan Data ke SQLite dengan aiosqlite

Setelah mendapatkan data tweet, aku ingin menyimpannya ke dalam database SQLite agar mudah diakses dan dianalisis nantinya. Di sinilah aiosqlite berperan. aiosqlite memberikan antarmuka yang ramah dan asinkron untuk berinteraksi dengan database SQLite.

Aku membuat fungsi save_tweets_to_db() untuk menyimpan tweet ke dalam database. Fungsi ini menggunakan aiosqlite untuk membuka koneksi ke database, menyimpan data tweet dan data pengguna ke dalam tabel yang sesuai, serta menghitung skor engagement untuk setiap tweet.

async def save_tweets_to_db(tweets):
  async with aiosqlite.connect(DB_NAME) as db:
    unique_tweet_ids = set()
    for tweet in tweets:
      if tweet.id in unique_tweet_ids:
        continue
      unique_tweet_ids.add(tweet.id)

      try:
        engagement_score = tweet.retweetCount + tweet.likeCount + tweet.replyCount + tweet.quoteCount

        await db.execute('''
          INSERT OR REPLACE INTO users (
            id, username, display_name, followers_count, friends_count, statuses_count,
            favourites_count, listed_count, media_count, location, profile_image_url,
            profile_banner_url, verified
          )
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (
          tweet.user.id, tweet.user.username, tweet.user.displayname, tweet.user.followersCount,
          tweet.user.friendsCount, tweet.user.statusesCount, tweet.user.favouritesCount,
          tweet.user.listedCount, tweet.user.mediaCount, tweet.user.location,
          tweet.user.profileImageUrl, tweet.user.profileBannerUrl, tweet.user.verified
        ))

        await db.execute('''
          INSERT INTO tweets (
            tweet_id, user_id, content, retweet_count, like_count, reply_count, quote_count,
            engagement_score, date, link_tweet, view_count
          )
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (
          tweet.id, tweet.user.id, tweet.rawContent, tweet.retweetCount, tweet.likeCount,
          tweet.replyCount, tweet.quoteCount, engagement_score, tweet.date, tweet.url,
          tweet.viewCount
        ))
      except aiosqlite.IntegrityError as e:
        print(f"IntegrityError: {e} for tweet_id {tweet.id}")
      except Exception as e:
        print(f"Unexpected error: {e}")
    await db.commit(

Penjelasan Kode save_tweets_to_db()

async with aiosqlite.connect(DB_NAME) as db:
unique_tweet_ids = set()
for tweet in tweets:
    if tweet.id in unique_tweet_ids:
        continue
    unique_tweet_ids.add(tweet.id)
engagement_score = tweet.retweetCount + tweet.likeCount + tweet.replyCount + tweet.quoteCount
await db.execute('''
    INSERT OR REPLACE INTO users (
        id, username, display_name, followers_count, friends_count, statuses_count,
        favourites_count, listed_count, media_count, location, profile_image_url,
        profile_banner_url, verified
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
    tweet.user.id, tweet.user.username, tweet.user.displayname, tweet.user.followersCount,
    tweet.user.friendsCount, tweet.user.statusesCount, tweet.user.favouritesCount,
    tweet.user.listedCount, tweet.user.mediaCount, tweet.user.location,
    tweet.user.profileImageUrl, tweet.user.profileBannerUrl, tweet.user.verified
))
await db.execute('''
    INSERT INTO tweets (
        tweet_id, user_id, content, retweet_count, like_count, reply_count, quote_count,
        engagement_score, date, link_tweet, view_count
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
    tweet.id, tweet.user.id, tweet.rawContent, tweet.retweetCount, tweet.likeCount,
    tweet.replyCount, tweet.quoteCount, engagement_score, tweet.date, tweet.url,
    tweet.viewCount
))
except aiosqlite.IntegrityError as e:
    print(f"IntegrityError: {e} for tweet_id {tweet.id}")
except Exception as e:
    print(f"Unexpected error: {e}")
await db.commit()

Menjalankan Skrip

Untuk menjalankan skrip scraping, aku menggunakan fungsi main() yang memanggil fungsi-fungsi yang diperlukan:

async def main():
  await initialize_db()
  await scrape_tweets()

if __name__ == "__main__":
  asyncio.run(main())

Fungsi initialize_db() digunakan untuk membuat tabel-tabel yang diperlukan dalam database SQLite jika belum ada.

Oke, aku akan memecah kode menjadi bagian-bagian yang lebih kecil dan memberikan penjelasan untuk setiap bagiannya. Aku juga akan menjelaskan tentang SQLite dan aiosqlite. Mari kita mulai!

Penutup

Nah, itu dia pengalamanku dalam melakukan scraping data dari Twitter menggunakan twscrape dan menyimpannya ke database SQLite dengan bantuan aiosqlite. Dengan menggunakan pendekatan ini, aku bisa mengumpulkan data tweet secara efisien dan menyimpannya dalam format yang terstruktur untuk analisis lebih lanjut. Here is the fixed markdown:

Masalah yang Muncul

Dalam perjalananku melakukan scraping data dari Twitter, aku menemukan beberapa masalah yang muncul.

1. Adanya tweet yang dikutip (quoted tweet) yang tersimpan dalam hasil scraping

Tweet yang dikutip tersebut tidak mengandung hashtag yang relevan dengan proyek ini, yaitu #maylearnings atau #maylearning.

Untuk mengatasi masalah ini, aku menggunakan pendekatan yang cukup sederhana, yaitu dengan melakukan query secara manual untuk menghapus tweet-tweet yang tidak sesuai tersebut. Query yang aku gunakan adalah sebagai berikut:

SELECT * FROM tweets 
  WHERE content NOT LIKE '%#maylearnings%' AND content NOT LIKE '%#maylearning%';

Query ini akan menyeleksi tweet-tweet yang kontennya tidak mengandung hashtag #maylearnings atau #maylearning. Setelah mendapatkan daftar tweet yang tidak sesuai, aku kemudian menghapusnya dari tabel tweets.

Namun, ada satu masalah lagi yang muncul setelah menghapus tweet-tweet tersebut. Beberapa pengguna yang tweetnya telah dihapus mungkin saja tidak memiliki tweet lain yang tersisa dalam hasil scraping. Dalam kasus ini, aku ingin menghapus data pengguna tersebut dari tabel users agar tidak ada data yang tidak terpakai.

Untuk mengatasi hal ini, aku menggunakan script yang aku terapkan pada fungsi di dashboard yang aku buat. Script tersebut adalah sebagai berikut:

async function deleteUnrelatedTweets() {
  const tweetsToDelete = await db.query(
    "SELECT * FROM tweets WHERE content NOT LIKE '%#maylearnings%' AND content NOT LIKE '%#maylearning%';"
  );

  for (const tweet of tweetsToDelete.results) {
    const tweetId = tweet.id;
    const userId = tweet.user_id;

    // Delete tweet from tweets table
    await db.prepare("DELETE FROM tweets WHERE id = ?;").bind(tweetId).run();

    // Check remaining tweets for the user
    const remainingTweets = await db
      .prepare("SELECT COUNT(*) AS count FROM tweets WHERE user_id = ?;")
      .bind(userId)
      .first();

    if (remainingTweets.count === 0) {
      // Delete user from users table
      await db.prepare("DELETE FROM users WHERE id = ?;").bind(userId).run();
    }
  }

  console.log("Unrelated tweets and users deleted successfully.");
}

Fungsi deleteUnrelatedTweets() ini pertama-tama mengambil tweet-tweet yang tidak terkait menggunakan query yang sama seperti sebelumnya. Kemudian, untuk setiap tweet yang akan dihapus, script ini menghapus tweet tersebut dari tabel tweets menggunakan perintah DELETE.

Setelah itu, script memeriksa apakah pengguna yang terkait dengan tweet yang dihapus masih memiliki tweet yang tersisa dalam tabel tweets. Jika pengguna tidak memiliki tweet lagi, maka data pengguna tersebut juga dihapus dari tabel users menggunakan perintah DELETE.

Dengan menerapkan solusi ini pada fungsi di dashboard, aku dapat dengan mudah menghapus tweet-tweet yang tidak terkait dan pengguna yang tidak memiliki tweet lagi hanya dengan memanggil fungsi deleteUnrelatedTweets().

2. Ada tweet dengan hashtag yang tidak ter-scrape

Solusi yang aku pakai untuk mengatasi masalah ini adalah dengan menggunakan filter berdasarkan pengguna saja, tanpa melibatkan filter tanggal. Jika setelah menggunakan filter pengguna, tweet tersebut masih belum ter-scrape, maka aku akan menambahkan tweet tersebut secara manual dengan cara menyalin teks tweet dan melakukan scraping secara terpisah menggunakan daftar data yang telah aku siapkan.

Dengan pendekatan ini, aku dapat memastikan bahwa semua tweet yang relevan dengan proyek, termasuk tweet yang mungkin terlewat saat scraping awal, tetap dapat ditambahkan ke dalam hasil scraping.

# Tentukan pengguna yang akan di-scrape
users = ["user1", "user2", "user3"]

total_tweets_saved = 0
for user in users:
    query = f"#MayLearnings from:{user}"
    if last_tweet_id:
        query += f" since_id:{last_tweet_id}"

    tweets = await gather(api.search(query, limit=2000))

    print(f"Jumlah tweets yang diambil untuk pengguna {user}: {len(tweets)}")

    await save_tweets_to_db(tweets)

    if tweets:
        last_tweet_id = tweets[0].id
        total_tweets_saved += len(tweets)

print(f"Scraping selesai. Total tweets tersimpan: {total_tweets_saved}")

Setelah melakukan scraping berdasarkan pengguna, aku menambahkan tweet yang tidak ter-scrape secara manual dengan cara berikut:

# Menambahkan tweet yang tidak ter-scrape secara manual
manual_tweets = [
    "Teks tweet manual 1 #MayLearnings",
    "Teks tweet manual 2 #MayLearnings"
]

for tweet_text in manual_tweets:
    # Mengekstrak informasi yang dibutuhkan dari teks tweet
    tweet_data = {
        "content": tweet_text,
        "hashtags": extract_hashtags(tweet_text),
        # Tambahkan informasi lain yang dibutuhkan
    }
    await save_tweet_to_db(tweet_data)

print(f"Penambahan tweet manual selesai. Total tweets tersimpan: {total_tweets_saved + len(manual_tweets)}")

Overall, masih aman dan lumayan deh hasilnya hehehehe.

Oh ya sekarang kita udah punya DATABASE yang bisa kita olah, nah tugas kita sekarang adalah ngolah itu data pakai bahasa yang kamu mau dan gimana biar tampil. Nah kebetulan aku bisanya pakai Javascript, Css, dan HTML. Pakai itu aja dan jadiii deh

DASHBOARD Public and Admin

Nanti kita bahas di artikel selanjutnya 😊😊😊😊😊

Bacaan Pondasi untuk Bikin Dashboard Gercep, pakai Alpine JS, HTMX, Hono JS, dan Tailwind CSS.

Selamat mencoba dan terima kasih udah membaca yah