PyQuery menghadirkan API bergaya jQuery di atas lxml. Di lima ukuran halaman, dari 1 KB hingga 10 MB, kelima median yang ditampilkan semuanya lebih rendah daripada lxml mentah, termasuk selisih 1,5% pada ukuran terbesar. Namun benchmark ini tidak membuktikan bahwa wrapper tersebut lebih cepat; yang terlihat hanyalah tidak ada perbedaan yang cukup besar untuk mengubah keputusan terkait selector dan pembacaan data ini.
Mulai 10 KB ke atas, hasil median yang ditampilkan juga hanya selisih beberapa persen dari selectolax. Tanpa batas ekuivalensi yang ditentukan sejak awal, ini lebih tepat disebut hasil yang sangat dekat, bukan seri statistik.
Apa itu PyQuery
PyQuery adalah library Python yang memberikan Anda API selector dan chaining ala jQuery di atas tree dokumen lxml. Versi yang diuji: 2.1.0, berlisensi BSD, 2.380 bintang GitHub, 59 issue terbuka, push terakhir 2026-07-27.
Referensi resmi: Dokumentasi PyQuery.

from pyquery import PyQuery as pq
d = pq(html)
titles = [e.text_content() for e in d("h3.title")]
hrefs = [e.get("href") for e in d("a")]
Elemen yang dihasilkan tetap berupa elemen lxml, jadi apa pun yang biasanya bisa Anda lakukan dengan lxml masih tetap berlaku. Memang itulah desainnya: PyQuery adalah lapisan ergonomi, bukan parser. pip install pyquery akan memasang 3 paket — lxml, cssselect, dan PyQuery itu sendiri — dengan ukuran 20,1 MiB, yang hampir seluruhnya berasal dari ekstensi terkompilasi milik lxml.
Jika Anda pernah memakai cheerio di Node, ini adalah ide API yang sama di Python. Dua selector yang diuji di sini bekerja di keduanya; namun pengujian ini tidak membuktikan kesetaraan bahasa selector antara cssselect dan cheerio.
Pengukuran
Basis riset ini sudah memiliki benchmark parser dengan satu properti yang jarang dimiliki benchmark lain: gate parity yang meng-hash konten hasil ekstraksi — judul yang diurutkan plus href yang diurutkan — terhadap parser referensi, sehingga library yang “mengurangi pekerjaan” tidak bisa sekadar mencatat waktu cepat. Lima ukuran halaman, 50 iterasi, tiga kali run independen.
Menambahkan PyQuery ke dalamnya membutuhkan dua langkah.
Menjalankan ulang referensi. selectolax dijalankan lagi dalam proses yang sama. Hash kontennya cocok di 5 dari 5 ukuran, dan p50-nya berada di antara 0,989× sampai 1,079× dari angka yang dipublikasikan — jadi ini benar-benar mesin yang sama dan testbed yang sama.
Menjalankan lxml di proses yang sama juga. Benchmark yang dipublikasikan mencatat mesin dan versi Python, tetapi tidak mencatat versi library, jadi baris lxml di sana bisa saja berasal dari rilis lxml yang berbeda dari yang dibungkus PyQuery di sini. Membandingkan lintas celah itu berarti membandingkan dua versi lxml lalu menyebutnya biaya wrapper. Dengan menjalankan lxml berdampingan, pertanyaannya hilang — keduanya memakai lxml 6.1.1 di venv ini.
| Ukuran halaman | selectolax | PyQuery | lxml | PyQuery vs lxml |
|---|---|---|---|---|
| 1 KB | 0.0286 ms | 0.0456 ms | 0.0508 ms | 0.90Ă— |
| 10 KB | 0.1725 ms | 0.1728 ms | 0.1802 ms | 0.96Ă— |
| 100 KB | 1.4855 ms | 1.4093 ms | 1.4145 ms | 1.00Ă— |
| 1 MB | 14.97 ms | 14.96 ms | 15.03 ms | 1.00Ă— |
| 10 MB | 158.10 ms | 162.86 ms | 165.25 ms | 0.99Ă— |
p50 dalam milidetik, median dari tiga run, semuanya dalam satu proses. parser-bench.json. Ketiga hash konten cocok dengan referensi di setiap ukuran.
Biaya wrapper yang ternyata tidak ada

PyQuery berada di level yang sama atau di bawah lxml mentah pada setiap median yang ditampilkan. Itu bukan bukti bahwa wrapper membuat parsing lebih cepat. Tiga median run dan tidak adanya margin ekuivalensi yang ditetapkan sejak awal hanya mendukung penilaian yang lebih sempit: pada fixture ini, tidak terlihat overhead selector yang berarti untuk keputusan.
Pada 10 MB, tiga run PyQuery adalah 162.86, 163.17, dan 161.13 ms; sedangkan lxml adalah 169.46, 165.25, dan 164.18 ms. Rentangnya berdekatan tetapi tidak saling tumpang tindih. Pada 1 MB, selisih median keduanya hanya 0,5%. Run yang kecil seperti ini mendukung penilaian praktis, bukan klaim ekuivalensi statistik.

Mekanismenya sederhana: pq(html) membangun tree lxml sekali, d("h3.title") mengompilasi selector CSS melalui cssselect seperti tree.cssselect(), dan elemen yang dikembalikan tetap elemen lxml. Jadi, bagian kerja PyQuery di hot path yang diukur ini sangat sedikit. Traversal, manipulasi, query berulang, proses import, dan memori berada di luar klaim timing selector ini.
Hasil yang dekat mulai 10 KB ke atas
Temuan yang lebih berguna justru ada di kolom pertama.
Dari 10 KB ke atas, selisih median tercepat-ke-terlambat antara selectolax, lxml, dan PyQuery adalah 4,5% pada 10 KB, 5,4% pada 100 KB, 0,5% pada 1 MB, dan 4,5% pada 10 MB. Run ini bukan uji ekuivalensi; penilaian praktisnya adalah bahwa gap seperti ini tidak akan mengubah pilihan parser untuk workload ini pada kebanyakan kasus.
selectolax memang benar-benar lebih cepat pada 1 KB — 0.0286 ms dibanding 0.0456 dan 0.0508 — tetapi baris itu tidak bisa dipakai untuk pemeringkatan. Di antara ketiga parser, selisih pada ukuran itu mencapai 77,6%, dan tiga run selectolax sendiri berada pada rentang 0.0267 sampai 0.0404 ms. Pada 28 mikrodetik, timer dan scheduler sudah terlalu dominan. Saya tidak akan memberi peringkat apa pun di sana.
Untuk workload selector dan pembacaan data ini, pilihlah di antara ketiganya berdasarkan API dan fakta dependensi yang terukur, bukan asumsi hierarki kecepatan. PyQuery tidak menunjukkan penalti yang berarti dibanding lxml. Selectolax memakai stack parser yang berbeda, tetapi artikel ini tidak mengukur footprint terpasang, cakupan wheel, atau kebutuhan build-nya pada basis yang sama.
Sebagai pembanding, benchmark yang dipublikasikan menempatkan dua opsi Python lain pada fixture 10 MB yang sama, dan justru merekalah yang benar-benar berbeda:
| Parser (10 MB) | p50 |
|---|---|
| selectolax (lexbor) | 159.93 ms |
| lxml | 172.93 ms |
| parsel | 231.85 ms |
| selectolax (modest) | 247.95 ms |
| BeautifulSoup + lxml | 2,261.56 ms |
| BeautifulSoup + html.parser | 2,788.75 ms |
Angka yang dipublikasikan diambil dari bench_parse.json.
Baris benchmark historis menunjukkan BeautifulSoup berada lebih dari satu orde magnitudo di atas median parser yang lebih cepat pada fixture ini. Baris tersebut tidak dijalankan ulang dengan pasangan PyQuery/lxml pada proses saat ini, jadi itu berguna sebagai konteks, bukan pengali yang terkontrol untuk verdict utama.
Baris tersimpan milik cheerio adalah 2.927,89 ms (2927.8857 di parser-bench.json) dengan hash konten hasil ekstraksi yang cocok. Hasil lintas runtime ini juga bergantung pada Node, versi paket, dan kontrol run historis; jadi jangan dibaca sebagai pengali kecepatan library yang berdiri sendiri.
Kenyataan setup
| Library | Paket | Disk | Lisensi | Bintang | Push terakhir |
|---|---|---|---|---|---|
| PyQuery | 3 | 20.1 MiB | BSD | 2,380 | 2026-07-27 |
| cheerio (Node) | 22 (npm) | 9.0 MiB | MIT | 30,449 | 2026-08-11 |
Referensi resmi: PyQuery di PyPI.
Tiga paket adalah jejak dependensi yang rapi, dan dua di antaranya — lxml dan cssselect — adalah sesuatu yang sudah dimiliki banyak proyek scraping Python. Dalam kasus itu, biaya tambahan PyQuery hanya beberapa puluh kilobyte.
Angka 20.1 MiB berasal dari ekstensi terkompilasi lxml, bukan dari PyQuery. Itu adalah 20 MiB yang sama yang Anda bayar jika memakai lxml secara langsung.
Paket ini berlisensi BSD. Pada snapshot bertanggal tersebut, ada 59 issue terbuka dan push dilakukan tiga minggu sebelum pengujian; observasi ini saja tidak cukup untuk menyimpulkan kualitas pemeliharaan atau kompatibilitas di masa depan.
Memori, dan apa yang dilakukan HTML rusak pada memori
Dua hal yang di setiap review batch ini sebelumnya tercatat belum diuji, kini diukur.
Konteks stress test yang lebih luas ada di perbandingan memori dan HTML rusak untuk sepuluh library.
Peak resident memory, via /usr/bin/time -l, satu proses baru per sel — import floor adalah biaya library saat dimuat dan idle, sedangkan puncaknya mencakup dokumen.
| Library | Runtime | Import floor | Peak 226 KB | Peak 10 MB |
|---|---|---|---|---|
| html2text | python3.14 | 18.7 | 19.9 | 71.2 |
| pyquery | python3.14 | 30.3 | 33.9 | 172.5 |
| resiliparse | python3.14 | 20.5 | 25.1 | 225.1 |
| markdownify | python3.14 | 23.9 | 28.9 | 278.5 |
| goose3 | python3.14 | 44.1 | 52.4 | 398.5 |
| cheerio | node22 | 66.8 | 76.5 | 398.5 |
| justext | python3.14 | 30.3 | 36.6 | 431.2 |
| newspaper4k | python3.14 | 52.6 | 61.8 | 668.5 |
| trafilatura | python3.14 | 52.5 | 64.8 | 927.1 |
| turndown | node22 | 47.8 | 68.4 | 2947.1 |
memory-results.json. Baseline Python dan Node tidak bisa dibandingkan secara langsung; interpreter-nya ada di dalam keduanya.
PyQuery lebih ringan daripada resiliparse pada dokumen besar — 172.5 MiB berbanding 225.1 — meskipun import floor-nya lebih tinggi. Tree lxml cukup ringkas, dan sebagian besar floor PyQuery yang 30.3 MiB itu berasal dari lxml yang dimuat, bukan dari sesuatu yang dilakukan PyQuery sendiri.
HTML rusak. Dua belas dokumen, masing-masing merusak tepat satu hal — tag tidak ditutup, inline element yang salah nesting, atribut tanpa tanda kutip yang mengandung spasi, penutup yang nyasar, tanpa <html> sama sekali, atribut duplikat, dokumen terpotong di tengah tag, entity yang salah, <script> yang tidak ditutup, deklarasi charset yang menyesatkan, komentar yang berisi markup, dan nesting 600 level — ditambah dua kontrol yang valid secara struktur pada ukuran yang cocok, karena “hasilnya kosong” hanya berarti sesuatu soal malformed jika library itu juga diam pada dokumen bersih dengan ukuran yang sama.
pyquery melempar error pada 0 dari 14 dan mengembalikan kosong pada 1, lalu memulihkan 10/22 sentinel di seluruh fixture rusak (malformed-results.json). Satu fixture dikecualikan dari hitungan itu: menurut HTML5, semua yang ada setelah <script> yang tidak ditutup memang menjadi konten script, jadi kehilangan konten di sana adalah perilaku yang benar dan memulihkannya justru menjadi deviasinya. Tanpa hasil sentinel dari alternatif langsung di sebelahnya, 10/22 adalah observasi ketahanan, bukan pemeringkatan parser.
Kelebihan dan kekurangan
Kelebihan. Sintaks jQuery, familiar bagi siapa pun yang pernah menulis JavaScript front-end atau memakai cheerio. Tidak terlihat overhead selector yang berarti dibanding lxml mentah pada fixture ini. Hanya 3 paket, dan 2 di antaranya kemungkinan besar sudah ada di tree Anda. Ia mengembalikan elemen lxml, jadi teknik lxml tetap bisa dipakai. BSD. Hash konten cocok dengan referensi di kelima ukuran.
Kekurangan. 20,1 MiB, karena lxml. 2.380 bintang berarti komunitasnya jauh lebih kecil dibanding cheerio yang 30.449 — contoh penggunaan yang lebih sedikit saat ada hal aneh. Ini hanyalah lapisan kenyamanan, jadi apa pun yang tidak bisa dilakukan lxml, PyQuery juga tidak bisa. Dan jika Anda berharap API jQuery memberi performa, itu tidak terjadi: yang didapat adalah ergonomi, sementara parser di bawahnya tetap yang bekerja.
Siapa yang cocok memakai ini, dan siapa yang tidak
Pakai PyQuery bila Anda atau tim Anda lebih nyaman dengan selector bergaya jQuery di Python. Proses pembuatan objek, dua kali seleksi, dan pembacaan yang diukur tidak menunjukkan penalti yang berarti dibanding lxml; operasi PyQuery lainnya tidak diukur waktunya.
Pakai lxml langsung jika Anda lebih suka XPath atau ingin satu paket lebih sedikit. Run ini tidak menunjukkan alasan terkait kecepatan selector untuk memilih salah satu di antara keduanya.
Evaluasi selectolax jika API parser dan stack dependensinya cocok untuk proyek Anda. Baris 1 KB secara eksplisit tidak diperingkat, dan artikel ini tidak mendukung klaim “dependensi paling kecil”.
Di Node, cheerio adalah bentuk API yang paling mirip. Baris lintas-runtime yang tersimpan memang lebih lambat di sini, tetapi perbedaan runtime dan kontrol run historis membuat kesimpulan murni untuk library saja tidak bisa dibuat dengan bersih.
Di mana API terkelola cocok dipakai
PyQuery mem-parse HTML yang sudah Anda miliki. Ia tidak melakukan fetch, tidak merender JavaScript, dan tidak menangani lapisan anti-bot — tidak ada parser di perbandingan ini yang melakukan itu, dan pada banyak target nyata, bagian yang lebih sulit justru ada di sana.
Catatan penulis: Thunderbit adalah opsi terkelola kami untuk pengambilan URL, rendering, dan ekstraksi. Ia tidak dibenchmark melawan PyQuery di sini. Batas yang relevan adalah apakah Anda sudah memegang HTML dan ingin selector lokal, atau ingin akuisisi halaman dan ekstraksi dijalankan sebagai layanan.
Kerangka paling jujur: jika Anda memegang HTML dan sudah tahu selector Anda, PyQuery itu gratis dan menyenangkan. Jika selector terus rusak, atau Anda mengambil data dalam skala besar, itu adalah pembelian yang berbeda.
Untuk gambaran yang lebih luas, ringkasan web scraping API kami membahas opsi hosted, dan pilar scraper open-source membahas opsi self-hosted. Jika output hasil parsing akan masuk ke model, mengonversi HTML ke Markdown di Python adalah tempat fidelity biasanya hilang.
Coba Thunderbit untuk Ekstraksi Data Web
Perlukah Anda memakai PyQuery?
Ya, jika Anda menginginkan sintaks bergaya jQuery di Python dan jalur selector/pembacaan yang diukur memang mewakili workload Anda.
Benchmark ini tidak menemukan overhead selector yang berarti dibanding lxml, sambil tetap mempertahankan parity hash konten. Namun, ia tidak membuktikan bahwa biaya library ini secara keseluruhan nol.
Di lima ukuran, median ketiga parser Python tetap cukup berdekatan sehingga kecocokan API kemungkinan lebih menentukan untuk tugas ini. Tetapkan margin ekuivalensi dan jalankan ulang alternatif yang persis sama sebelum mengubah penilaian ini menjadi pemeringkatan parser yang lebih luas.
Coba Thunderbit untuk Ekstraksi Data Web Get Started Free
FAQ
Apakah PyQuery memperlambat lxml?
Tidak terlihat overhead selector yang berarti pada run ini. Di lima ukuran halaman, median-nya berada di level yang sama atau di bawah lxml mentah, dan keduanya memakai lxml 6.1.1 dalam proses yang sama. Pada 10 MB, rentang yang berdekatan tidak saling tumpang tindih: PyQuery 161.13–163.17 ms dan lxml 164.18–169.46 ms. pq(html) membangun tree lxml, dan selector yang diuji dikompilasi melalui cssselect.
Apakah selectolax lebih cepat daripada PyQuery? Median 1 KB-nya lebih rendah, tetapi baris itu tidak diperingkat karena variasi terlalu besar pada skala mikrodetik. Dari 10 KB ke atas, selisih median berada di rentang 0,5%–5,4%. Itu tergolong dekat untuk workload ini, bukan bukti ekuivalensi atau bukti rentang yang saling tumpang tindih di semua kasus.
Mengapa menjalankan ulang lxml, bukan hanya mengutip angka yang dipublikasikan? Karena benchmark yang dipublikasikan hanya mencatat mesin dan versi Python, bukan versi library. Baris lxml-nya mungkin berasal dari lxml yang berbeda dengan yang dibungkus PyQuery saat ini, dan perbedaan versi seperti itu bisa terlihat seperti biaya wrapper padahal bukan. Menjalankan keduanya dalam satu proses pada lxml 6.1.1 menghilangkan ambiguitas itu.
Bagaimana perbandingannya dengan cheerio? Ide API dasarnya sama, ekosistemnya berbeda. Dua selector yang diuji di sini bekerja di keduanya dan hash kontennya cocok di kelima ukuran; itu belum membuktikan kompatibilitas selector secara penuh. Timing cheerio yang tersimpan memang lebih lambat, tetapi kontrol lintas-runtime dan run historis mencegah klaim pengali yang hanya didasarkan pada library.
Apa yang tidak diuji di sini? Memori diukur sebagai peak RSS untuk import saja, dokumen 226 KB, dan dokumen 10 MB. HTML rusak diuji dengan 12 dokumen yang rusak plus dua kontrol yang cocok. Yang masih belum diuji adalah performa manipulasi dan traversal PyQuery, caching query berulang, fetch URL, concurrency, serta workload situs nyata yang representatif. Timing 1 KB tetap tidak diperingkat.


