Sepertinya setiap pertanyaan pada stackoverflow di mana penanya menggunakan regex untuk mengambil beberapa informasi dari HTML pasti akan memiliki "jawaban" yang mengatakan tidak menggunakan regex untuk mem-parsing HTML.
Kenapa tidak? Saya sadar bahwa ada parser HTML "nyata" kutipan-tanda kutip di luar sana seperti Beautiful Soup , dan saya yakin mereka kuat dan berguna, tetapi jika Anda hanya melakukan sesuatu yang sederhana, cepat, atau kotor, lalu mengapa repot menggunakan sesuatu yang begitu rumit ketika beberapa pernyataan regex akan berfungsi dengan baik?
Selain itu, apakah ada sesuatu yang mendasar yang saya tidak mengerti tentang regex yang membuat mereka menjadi pilihan yang buruk untuk penguraian secara umum?
regex
html-parsing
ntownsend
sumber
sumber
Jawaban:
Seluruh parsing HTML tidak dimungkinkan dengan ekspresi reguler, karena itu tergantung pada pencocokan pembukaan dan tag penutup yang tidak mungkin dengan regexps.
Ekspresi reguler hanya dapat mencocokkan bahasa biasa tetapi HTML adalah bahasa bebas konteks dan bukan bahasa biasa (Seperti yang ditunjukkan oleh @StefanPochmann, bahasa biasa juga bebas konteks, jadi bebas konteks tidak selalu berarti tidak biasa). Satu-satunya hal yang dapat Anda lakukan dengan regexps pada HTML adalah heuristik tetapi itu tidak akan bekerja pada setiap kondisi. Seharusnya dimungkinkan untuk menyajikan file HTML yang akan dicocokkan secara salah dengan ekspresi reguler apa pun.
sumber
Untuk quick'n´dirty regexp akan baik-baik saja. Tetapi hal mendasar yang perlu diketahui adalah bahwa tidak mungkin untuk membangun regexp yang akan mem-parsing HTML dengan benar .
Alasannya adalah bahwa regexps tidak dapat menangani ekspresi bersarang secara arbiter. Lihat Bisakah ekspresi reguler digunakan untuk mencocokkan pola bersarang?
sumber
(Dari http://htmlparsing.com/regexes )
Katakanlah Anda memiliki file HTML tempat Anda mencoba mengekstrak URL dari tag <img>.
Jadi Anda menulis regex seperti ini di Perl:
Dalam hal ini,
$url
memang akan mengandunghttp://example.com/whatever.jpg
. Tetapi apa yang terjadi ketika Anda mulai mendapatkan HTML seperti ini:atau
atau
atau
atau Anda mulai mendapatkan positif palsu dari
Itu terlihat sangat sederhana, dan mungkin sederhana untuk satu file yang tidak berubah, tetapi untuk apa pun yang akan Anda lakukan pada data HTML sewenang-wenang, regex hanyalah resep untuk sakit hati di masa depan.
sumber
Dua alasan cepat:
Mengenai kesesuaian regex untuk parsing secara umum: mereka tidak cocok. Pernahkah Anda melihat jenis regex yang Anda perlukan untuk menguraikan sebagian besar bahasa?
sumber
Sejauh penguraian, ekspresi reguler dapat berguna dalam tahap "analisis leksikal" (lexer), di mana input dipecah menjadi token. Ini kurang berguna dalam tahap "build a parse tree" yang sebenarnya.
Untuk parser HTML, saya berharap itu hanya menerima HTML yang terbentuk dengan baik dan yang membutuhkan kemampuan di luar apa yang bisa dilakukan ekspresi reguler (mereka tidak bisa "menghitung" dan memastikan bahwa sejumlah elemen pembuka diseimbangkan dengan angka yang sama elemen penutup).
sumber
Karena ada banyak cara untuk "mengacaukan" HTML yang akan diperlakukan oleh peramban dengan cara yang agak liberal, tetapi perlu upaya yang cukup untuk mereproduksi perilaku liberal peramban untuk menutupi semua kasus dengan ekspresi reguler, sehingga regex Anda pasti akan gagal pada beberapa hal khusus. kasus, dan itu mungkin akan memperkenalkan celah keamanan yang serius di sistem Anda.
sumber
Masalahnya adalah bahwa sebagian besar pengguna yang mengajukan pertanyaan yang berkaitan dengan HTML dan regex melakukan ini karena mereka tidak dapat menemukan regex sendiri yang berfungsi. Maka kita harus berpikir apakah semuanya akan lebih mudah saat menggunakan DOM atau SAX parser atau yang serupa. Mereka dioptimalkan dan dibangun untuk tujuan bekerja dengan struktur dokumen seperti XML.
Tentu, ada masalah yang bisa diselesaikan dengan mudah dengan ekspresi reguler. Namun penekanannya terletak pada mudah .
Jika Anda hanya ingin menemukan semua URL yang terlihat
http://.../
baik-baik saja dengan regexps. Tetapi jika Anda ingin menemukan semua URL yang ada di a-Element yang memiliki kelas 'mylink' Anda mungkin lebih baik menggunakan parser yang sesuai.sumber
Ekspresi reguler tidak dirancang untuk menangani struktur tag bersarang, dan paling rumit (paling buruk, tidak mungkin) untuk menangani semua kasus tepi yang mungkin Anda dapatkan dengan HTML nyata.
sumber
Saya percaya bahwa jawabannya terletak pada teori perhitungan. Untuk bahasa yang akan diuraikan menggunakan regex itu harus dengan definisi "biasa" ( tautan ). HTML bukan bahasa biasa karena tidak memenuhi sejumlah kriteria untuk bahasa biasa (banyak hubungannya dengan banyak tingkat bersarang yang melekat dalam kode html). Jika Anda tertarik pada teori perhitungan, saya akan merekomendasikan buku ini .
sumber
Ekspresi ini mengambil atribut dari elemen HTML. Ini mendukung:
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)
Lihat itu . Ini berfungsi lebih baik dengan bendera "gisx", seperti pada demo.
sumber
<script>
tag.HTML / XML dibagi menjadi markup dan konten. Regex hanya berguna melakukan penguraian tag leksikal. Saya kira Anda dapat menyimpulkan konten. Ini akan menjadi pilihan yang baik untuk parser SAX. Tag dan konten dapat dikirimkan ke fungsi yang ditentukan pengguna di mana elemen / penutupan elemen dapat dilacak.
Sejauh hanya mengurai tag, itu bisa dilakukan dengan regex dan digunakan untuk menghapus tag dari dokumen.
Selama bertahun-tahun pengujian, saya telah menemukan rahasia cara tag parse browser, baik dan buruk terbentuk.
Elemen normal diuraikan dengan bentuk ini:
Inti dari tag ini menggunakan regex ini
Anda akan melihat ini
[^>]?
sebagai salah satu alternatif. Ini akan cocok dengan kutipan yang tidak seimbang dari tag yang dibentuk dengan buruk.Itu juga, satu-satunya akar dari semua kejahatan untuk ekspresi reguler. Cara yang digunakan akan memicu benturan untuk memenuhi wadahnya yang serakah dan harus dicocokkan.
Jika digunakan secara pasif, tidak pernah ada masalah Tapi, jika Anda memaksakan sesuatu untuk mencocokkan dengan menyelingi dengan pasangan atribut / nilai yang diinginkan, dan tidak memberikan perlindungan yang memadai dari pengulangan, itu adalah mimpi buruk yang tidak terkendali.
Ini adalah bentuk umum untuk tag lama biasa. Perhatikan yang
[\w:]
mewakili nama tag? Pada kenyataannya, karakter hukum yang mewakili nama tag adalah daftar karakter Unicode yang luar biasa.Selanjutnya, kami juga melihat bahwa Anda tidak dapat mencari tag tertentu tanpa menguraikan SEMUA tag. Maksud saya Anda bisa, tetapi harus menggunakan kombinasi kata kerja seperti (* SKIP) (* GAGAL) tetapi semua tag harus diuraikan.
Alasannya adalah bahwa sintaksis tag mungkin disembunyikan di dalam tag lain, dll.
Jadi, untuk mem-parsing semua tag secara pasif, diperlukan regex seperti di bawah ini. Yang satu ini juga cocok dengan konten yang tidak terlihat .
Saat HTML baru atau xml atau lainnya mengembangkan konstruksi baru, tambahkan saja sebagai salah satu alternatif.
Catatan halaman web - Saya belum pernah melihat halaman web (atau xhtml / xml) yang
bermasalah dengannya. Jika Anda menemukannya, beri tahu saya.
Catatan kinerja - Cepat. Ini adalah tag parser tercepat yang pernah saya lihat
(mungkin ada yang lebih cepat, siapa tahu).
Saya punya beberapa versi spesifik. Ini juga sangat baik sebagai scraper
(jika Anda tipe tangan).
Regex mentah lengkap
<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\1\s*(?=>))|(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))>
Tampilan diformat
sumber
"Tergantung". Memang benar bahwa regex tidak dan tidak dapat mem-parsing HTML dengan akurasi yang sebenarnya, untuk semua alasan yang diberikan di sini. Namun, jika konsekuensi dari kesalahan (seperti tidak menangani tag bersarang) kecil, dan jika regex sangat nyaman di lingkungan Anda (seperti saat Anda meretas Perl), silakan.
Misalkan Anda, oh, mungkin mem-parsing halaman web yang terhubung ke situs Anda - mungkin Anda menemukannya dengan pencarian tautan Google - dan Anda menginginkan cara cepat untuk mendapatkan gambaran umum tentang konteks di sekitar tautan Anda. Anda mencoba menjalankan sedikit laporan yang mungkin mengingatkan Anda untuk menautkan spam, sesuatu seperti itu.
Dalam hal ini, salah membaca beberapa dokumen tidak akan menjadi masalah besar. Tidak ada seorang pun selain Anda yang akan melihat kesalahan, dan jika Anda sangat beruntung akan ada cukup banyak yang dapat Anda tindak lanjuti secara individu.
Saya kira saya katakan itu tradeoff. Terkadang menerapkan atau menggunakan parser yang benar - semudah itu mungkin - mungkin tidak sepadan dengan masalah jika akurasi tidak kritis.
Berhati-hatilah dengan asumsi Anda. Saya dapat memikirkan beberapa cara cara pintas regexp dapat menjadi bumerang jika Anda mencoba mengurai sesuatu yang akan ditampilkan di depan umum, misalnya.
sumber
Pasti ada kasus di mana menggunakan ekspresi reguler untuk mem-parsing beberapa informasi dari HTML adalah cara yang benar - itu sangat tergantung pada situasi tertentu.
Konsensus di atas adalah bahwa secara umum itu adalah ide yang buruk. Namun jika struktur HTML diketahui (dan tidak mungkin berubah) maka itu masih merupakan pendekatan yang valid.
sumber
Ingatlah bahwa meskipun HTML itu sendiri tidak teratur, bagian dari halaman yang Anda lihat mungkin teratur.
Misalnya, ini merupakan kesalahan
<form>
tag yang akan disarangkan; jika halaman web berfungsi dengan benar, maka menggunakan ekspresi reguler untuk mengambil<form>
akan sepenuhnya masuk akal.Baru-baru ini saya melakukan pengikisan web menggunakan hanya Selenium dan ekspresi reguler. Saya lolos karena data yang saya inginkan dimasukkan ke dalam
<form>
, dan dimasukkan ke dalam format tabel sederhana (jadi saya bahkan bisa mengandalkan<table>
,<tr>
dan<td>
menjadi non-bersarang - yang sebenarnya sangat tidak biasa). Pada tingkat tertentu, ekspresi reguler bahkan hampir diperlukan, karena beberapa struktur yang perlu saya akses dibatasi oleh komentar. (Beautiful Soup dapat memberi Anda komentar, tetapi akan sulit untuk mengambil<!-- BEGIN -->
dan<!-- END -->
memblokir menggunakan Beautiful Soup.)Namun, jika saya harus khawatir tentang tabel bersarang, pendekatan saya tidak akan berhasil! Saya harus kembali pada Beautiful Soup. Meski begitu, kadang-kadang, Anda dapat menggunakan ekspresi reguler untuk mengambil potongan yang Anda butuhkan, dan kemudian menelusuri dari sana.
sumber
Sebenarnya, parsing HTML dengan regex sangat mungkin di PHP. Anda hanya perlu menguraikan seluruh string ke belakang menggunakan
strrpos
untuk menemukan<
dan mengulangi regex dari sana menggunakan specifier ungreedy setiap kali untuk mendapatkan lebih dari tag bersarang. Tidak suka dan sangat lambat pada hal-hal besar, tetapi saya menggunakannya untuk editor templat pribadi saya untuk situs web saya. Sebenarnya saya tidak mem-parsing HTML, tetapi beberapa tag khusus yang saya buat untuk query entri database untuk menampilkan tabel data (<#if()>
tag saya bisa menyorot entri khusus dengan cara ini). Saya tidak siap untuk mencari parser XML hanya pada beberapa tag yang dibuat sendiri (dengan data yang sangat non-XML) di sana-sini.Jadi, meskipun pertanyaan ini sudah sangat mati, pertanyaan itu masih muncul di pencarian Google. Saya membacanya dan berpikir "tantangan diterima" dan selesai memperbaiki kode sederhana saya tanpa harus mengganti semuanya. Memutuskan untuk menawarkan pendapat yang berbeda kepada siapa pun yang mencari alasan yang sama. Juga jawaban terakhir telah diposting 4 jam yang lalu jadi ini masih menjadi topik hangat.
sumber
<tag >
) Apakah Anda mempertimbangkan tag penutup yang dikomentari? (Misalnya,<tag> <!-- </tag> -->
) Apakah Anda mempertimbangkan CDATA? Apakah Anda menganggap tag kasus tidak konsisten? (Misalnya,<Tag> </tAG>
) Apakah Anda juga mempertimbangkan ini ?Saya mencoba tangan saya di regex untuk ini juga. Ini sebagian besar berguna untuk menemukan potongan konten yang dipasangkan dengan tag HTML berikutnya, dan tidak mencari yang cocok dengan tag dekat, tetapi itu akan mengambil tag dekat. Gulung tumpukan dalam bahasa Anda sendiri untuk memeriksanya.
Gunakan dengan opsi 'sx'. 'g' juga jika Anda merasa beruntung:
Yang ini dirancang untuk Python (mungkin bekerja untuk bahasa lain, belum mencobanya, ia menggunakan lookaheads positif, lookbehinds negatif, dan bernama backreferences). Mendukung:
<div ...>
</div>
<!-- ... -->
<![CDATA[ ... ]]>
<div .../>
<input checked>
<div style='...'>
<div style="...">
<a title='John\'s Story'>
(ini bukan HTML yang benar-benar valid, tapi saya orang yang baik)
<a href = '...'>
Ini juga cukup bagus tentang tidak memicu tag salah bentuk, seperti ketika Anda lupa
<
atau>
.Jika rasa regex Anda mendukung tangkapan bernama berulang maka Anda adalah emas, tetapi Python
re
tidak (saya tahu regex tidak, tapi saya perlu menggunakan vanilla Python). Inilah yang Anda dapatkan:content
- Semua konten hingga tag berikutnya. Anda bisa meninggalkan ini.markup
- Seluruh tag dengan semua yang ada di dalamnya.comment
- Jika itu adalah komentar, isi komentar.cdata
- Jika a<![CDATA[...]]>
, isi CDATA.close_tag
- Jika ini tag dekat (</div>
), nama tag.tag
- Jika ini merupakan tag terbuka (<div>
), nama tag tersebut.attributes
- Semua atribut di dalam tag. Gunakan ini untuk mendapatkan semua atribut jika Anda tidak mendapatkan grup berulang.attribute
- Diulang, setiap atribut.attribute_name
- Diulang, setiap nama atribut.attribute_value
- Diulang, setiap nilai atribut. Ini termasuk kutipan jika dikutip.is_self_closing
- Ini adalah/
jika tag penutup sendiri, jika tidak apa-apa._q
dan_v
- Abaikan ini; mereka digunakan secara internal untuk referensi.Jika mesin regex Anda tidak mendukung tangkapan bernama berulang, ada bagian yang dipanggil yang bisa Anda gunakan untuk mendapatkan setiap atribut. Jalankan regex itu pada
attributes
grup untuk mendapatkan masing-masingattribute
,attribute_name
danattribute_value
keluar dari sana.Demo di sini: https://regex101.com/r/mH8jSu/11
sumber
Ekspresi reguler tidak cukup kuat untuk bahasa seperti HTML. Tentu, ada beberapa contoh di mana Anda bisa menggunakan ekspresi reguler. Tetapi secara umum tidak cocok untuk parsing.
sumber
Anda tahu ... ada banyak mentalitas yang TIDAK BISA Anda lakukan dan saya pikir semua orang di kedua sisi pagar itu benar dan salah. Anda BISA melakukannya, tetapi butuh sedikit pemrosesan lebih dari hanya menjalankan satu regex melawannya. Ambil ini (saya menulis ini dalam satu jam) sebagai contoh. Ini mengasumsikan HTML benar-benar valid, tetapi tergantung pada bahasa apa yang Anda gunakan untuk menerapkan regex yang disebutkan di atas, Anda bisa melakukan beberapa perbaikan pada HTML untuk memastikan itu akan berhasil. Misalnya, menghapus tag penutup yang tidak seharusnya ada di sana:
</img>
misalnya. Lalu, tambahkan slash forward HTML tunggal penutup ke elemen yang tidak ada, dll.Saya akan menggunakan ini dalam konteks menulis perpustakaan yang akan memungkinkan saya untuk melakukan pencarian elemen HTML yang mirip dengan JavaScript
[x].getElementsByTagName()
, misalnya. Saya baru saja menambahkan fungsi yang saya tulis di bagian DEFINE dari regex dan menggunakannya untuk melangkah masuk ke dalam pohon elemen, satu per satu.Jadi, apakah ini akan menjadi jawaban akhir 100% untuk memvalidasi HTML? Tidak. Tapi ini awal dan dengan sedikit lebih banyak pekerjaan, itu bisa dilakukan. Namun, mencoba melakukannya di dalam satu eksekusi regex tidak praktis, juga tidak efisien.
sumber