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
- Persiapan
- Menambahkan Akun Twitter
- Scraping Data Twitter
- Penjelasan Kode
scrape_tweets()
- Menyimpan Data ke SQLite dengan aiosqlite
- Penjelasan Kode
save_tweets_to_db()
- Menjalankan Skrip
- Penutup
- Masalah yang Muncul
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()
- Pertama, aku membuat objek
API
daritwscrape
. Ini adalah kunci untuk berinteraksi dengan API Twitter. - Kemudian, aku menambahkan akun Twitter yang sudah kubeli menggunakan
add_account()
dengan menyertakan username, password, email, dan password email. - Setelah itu, aku melakukan login ke semua akun yang sudah ditambahkan menggunakan
login_all()
.
last_tweet_id = await get_last_tweet_id()
- Aku mendapatkan ID tweet terakhir yang tersimpan di database dengan memanggil fungsi
get_last_tweet_id()
. - Ini berguna untuk melanjutkan scraping dari tweet terakhir yang sudah diambil sebelumnya.
date_ranges = [
("2024-05-31", "2024-06-01")
]
total_tweets_saved = 0
- Aku menentukan rentang tanggal yang ingin ku-scrape dalam variabel
date_ranges
. - Dalam contoh ini, aku hanya mengambil tweet dari tanggal β2024-05-31β hingga β2024-06-01β. Kamu bisa menambahkan lebih banyak rentang tanggal sesuai kebutuhanmu.
- Variabel
total_tweets_saved
digunakan untuk menghitung total tweet yang berhasil disimpan.
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))
- Aku melakukan perulangan untuk setiap rentang tanggal yang sudah ditentukan.
- Di dalam perulangan, aku membuat query pencarian dengan menggabungkan hashtag yang diinginkan (
#MayLearnings
) dan rentang tanggal menggunakan formatsince:tanggal until:tanggal
. - Jika ada ID tweet terakhir yang tersimpan (
last_tweet_id
), aku menambahkannya ke dalam query dengan formatsince_id:last_tweet_id
. Ini memastikan aku hanya mengambil tweet yang lebih baru dari tweet terakhir yang sudah diambil sebelumnya. - Kemudian, aku menggunakan
api.search()
untuk mencari tweet berdasarkan query yang sudah dibuat. Aku membatasi jumlah tweet yang diambil hingga 2000 tweet per rentang tanggal menggunakan parameterlimit
.
print(f"Jumlah tweets yang diambil untuk periode {start_date} - {end_date}: {len(tweets)}")
await save_tweets_to_db(tweets)
- Setelah mendapatkan tweet, aku mencetak jumlah tweet yang berhasil diambil untuk setiap rentang tanggal.
- Lalu, aku memanggil fungsi
save_tweets_to_db()
untuk menyimpan tweet-tweet tersebut ke dalam database SQLite.
if tweets:
last_tweet_id = tweets[0].id
total_tweets_saved += len(tweets)
- Jika ada tweet yang berhasil diambil, aku memperbarui
last_tweet_id
dengan ID tweet terbaru yang diambil. Ini akan digunakan untuk melanjutkan scraping pada iterasi berikutnya. - Aku juga menambahkan jumlah tweet yang berhasil diambil ke variabel
total_tweets_saved
.
print(f"Scraping selesai. Total tweets tersimpan: {total_tweets_saved}")
- Terakhir, aku mencetak total tweet yang berhasil disimpan setelah semua rentang tanggal selesai di-scrape.
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:
- Aku membuka koneksi ke database SQLite menggunakan
aiosqlite.connect()
dengan menyertakan nama database (DB_NAME
). - Aku menggunakan
async with
untuk memastikan koneksi ditutup secara otomatis setelah selesai digunakan.
unique_tweet_ids = set()
for tweet in tweets:
if tweet.id in unique_tweet_ids:
continue
unique_tweet_ids.add(tweet.id)
- Aku membuat sebuah set kosong bernama
unique_tweet_ids
untuk menyimpan ID tweet yang unik. - Kemudian, aku melakukan perulangan untuk setiap tweet dalam daftar
tweets
yang diterima sebagai parameter. - Untuk setiap tweet, aku memeriksa apakah ID tweet sudah ada dalam
unique_tweet_ids
. Jika sudah ada, aku melanjutkan ke tweet berikutnya untuk menghindari duplikasi. - Jika ID tweet belum ada dalam
unique_tweet_ids
, aku menambahkannya ke dalam set tersebut.
engagement_score = tweet.retweetCount + tweet.likeCount + tweet.replyCount + tweet.quoteCount
- Aku menghitung skor engagement untuk tweet tersebut dengan menjumlahkan jumlah retweet, like, reply, dan quote.
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
))
- Aku menggunakan
db.execute()
untuk menyimpan data pengguna yang terkait dengan tweet ke dalam tabelusers
. - Aku menggunakan pernyataan SQL
INSERT OR REPLACE
untuk memastikan data pengguna selalu up to date jika sudah ada sebelumnya. - Data pengguna yang disimpan meliputi ID pengguna, username, nama tampilan, jumlah pengikut, jumlah teman, jumlah status, jumlah favorit, jumlah list, jumlah media, lokasi, URL gambar profil, URL banner profil, dan status verifikasi.
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
))
- Aku menggunakan
db.execute()
lagi untuk menyimpan data tweet itu sendiri ke dalam tabeltweets
. - Data tweet yang disimpan meliputi ID tweet, ID pengguna, konten tweet, jumlah retweet, jumlah like, jumlah reply, jumlah quote, skor engagement, tanggal, link tweet, dan jumlah tampilan.
except aiosqlite.IntegrityError as e:
print(f"IntegrityError: {e} for tweet_id {tweet.id}")
except Exception as e:
print(f"Unexpected error: {e}")
- Jika terjadi kesalahan
IntegrityError
saat menyimpan data, aku mencetaknya ke konsol dengan ID tweet yang terkait. - Jika terjadi kesalahan lainnya, aku juga mencetaknya ke konsol.
await db.commit()
- Terakhir, setelah semua tweet selesai diproses, aku melakukan
db.commit()
untuk menyimpan perubahan ke dalam database secara permanen.
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
Nanti kita bahas di artikel selanjutnya πππππ
Bacaan Pondasi untuk Bikin Dashboard Gercep, pakai Alpine JS, HTMX, Hono JS, dan Tailwind CSS.
- Basih Auth dengan Hono JS Middleware
- JSX Rendering di Server
- Bacaan HTMX
- Alpine, Tailwind, dan Chart JS nya sooon akan aku tulis yah
Selamat mencoba dan terima kasih udah membaca yah