Skip to content
Konsultasi

DOM Chat AI Cuma Nyimpen Setengah Jawaban

Adityo Guni Waluyo

Scraping jawaban chat AI dari DOM cuma dapat setengah karena virtualisasi. Jalur utuhnya: klik Copy, baca clipboard, plus kontrak output ke model.

Ringkasan

Chat modern pakai virtualisasi, jadi DOM cuma snapshot yang terus berubah dan scraping innerText selalu kepotong. Solusinya klik tombol Copy lalu baca clipboard, dilapis accessibility tree dan innerText, dengan polling di tab baru biar stabil. Paling murah, kasih kontrak ke model: bungkus jawaban dalam blok kode ditutup sentinel ===SELESAI===.

Skripnya udah jalan. Chat AI selesai jawab. Saya jalankan scraper buat nangkep teksnya dari DOM, dan hasilnya cuma setengah. Paragraf terakhirnya hilang, padahal di layar kelihatan utuh. Makin panjang jawabannya, makin banyak yang ilang.

Dugaan pertama saya klasik: selector-nya kurang tepat. Saya buka DevTools, gonta-ganti CSS selector, coba textContent, coba innerText, nambah loop scroll manual biar semua ke-render dulu. Hasilnya makin lama makin kacau. Kadang dapat dobel, kadang urutannya acak, kadang tetap aja kepotong. Buang waktu hampir satu jam di tempat yang salah.

Ternyata masalahnya bukan selector. UI chat modern itu pakai virtualisasi. Konsepnya sederhana: cuma elemen yang sedang kelihatan di viewport yang benar-benar ada di DOM, sisanya direcycle pas di-scroll keluar window. Ini trik performa yang wajar buat list panjang, dan web.dev menjelaskan pola ini sebagai cara menahan biaya rendering [2]. Tapi efek sampingnya bagi saya: DOM adalah sumber paling buruk buat ambil jawaban panjang. Node-nya memang pernah ada, cuma udah dihapus duluan sebelum saya baca.

Scroll manual saya tadi malah memperparah. Tiap scroll bikin node lama dibuang dan node baru dibuat, jadi scraper yang nge-dump innerText cuma nebak isi DOM di momen tertentu. Bukan arsip, cuma snapshot yang terus berubah.

Clipboard, jalur yang tadinya saya anggap hack

Solusinya aneh pas pertama kepikiran: jangan baca DOM sama sekali. Klik tombol Copy di halaman, lalu baca clipboard.

Spesifikasi Clipboard API memang mensyaratkan transient user activation buat operasi baca, dan browser praktisnya menambahkan syarat permission sendiri di atasnya [1]. Klik tombol Copy di halaman itu aktivasi user yang sah, jadi persyaratan itu terpenuhi secara natural. Saya nggak perlu ngaku-ngaku jadi user atau main-main di area abu-abu; tinggal kirim klik ke tombol yang memang dirancang buat manusia.

Ini yang bikin saya senyum sendiri. Semua sumber data yang resmi-terstruktur kemarin saya cari, ternyata jalur paling utuh justru fitur copy yang dianggap hack. Clipboard dapet jawaban penuh, urut, tanpa peduli DOM-nya direcycle atau enggak.

Tapi satu jalur doang belum cukup buat skrip yang mau bisa diandalkan. Jadinya ekstraksinya tiga lapis. Utamanya clipboard: klik Copy, baca hasilnya. Lapis kedua, snapshot accessibility tree sebagai anchor, buat ngecek struktur halaman masih masuk akal sebelum percaya hasilnya. Lapis terakhir innerText, jangkar pamungkas kalo dua lapis di atasnya gagal. Urutan ini juga ngebantu pas polling: skrip harus tahan sama kegagalan sementara, dan Playwright punya mekanisme auto-waiting yang nunggu semua check visible, stable, receives events, dan enabled lolos sebelum aksi dijalankan, baru dilempar TimeoutError kalo nggak lolos dalam batas waktu [3].

Kontrak ke model lebih murah daripada parsing

Ada satu sisa masalah yang nggak bisa diselesaikan DOM, clipboard, apapun: kapan jawabannya selesai. Chat streaming itu tamatnya nggak jelas secara struktural. Spinner hilang, tombol berubah, tiap UI beda-beda.

Solusi paling murah malah bukan teknis: saya kasih kontrak ke modelnya sendiri. Seluruh jawaban harus dibungkus dalam satu blok kode, ditutup sentinel ===SELESAI===, dan dilarang nanya balik di akhir jawaban. Nanya balik itu pembunuh polling; jawaban "selesai" tapi model masih nanya "mau saya lanjutkan?" bikin skrip bingung harus nunggu apa.

<jawaban model>
...teks lengkap...
===SELESAI===

Skrip tinggal baca clipboard, cari sentinel, beres. Nggak perlu deteksi "apakah model masih ngetik" lewat observer DOM yang rawan salah baca.

Satu hal lagi yang saya salah duga: polling. Awalnya saya pantau tab asal, reload dan observe di situ. State chatnya langsung kacau, konteks nggak karuan. Yang malah jalan tab baru yang dibuka ke chat URL yang stabil, jadi tab asal dibiarkan tidur dan polling jalan di tempat terpisah.

Pelajaran yang saya bawa dari ini (sama seperti pas saya nemuin penanda kontrol yang kebelah di tengah stream SSE): kalo mau data keluar dari UI yang nggak dirancang buat mesin, perpendek masalahnya di input, bukan parsing di output. Satu kalimat kontrak ke model itu lima menit nulis. Parsing yang handal dari DOM yang didesain buat mata manusia bisa makan hari-hari. Tahun lalu saya mungkin tetap ngejar selector yang "tepat". Sekarang saya buka tombol Copy-nya aja dulu.

---

Sumber:

[1] https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API [2] https://web.dev/articles/virtualize-long-lists-react-window [3] https://playwright.dev/docs/actionability

Artikel terkait