ใช้การแสดงออกปกติเพื่อแยก HTML: ทำไมไม่?


207

ดูเหมือนว่าทุกคำถามใน stackoverflow ที่ผู้ถามใช้ regex เพื่อดึงข้อมูลบางอย่างจาก HTML อย่างหลีกเลี่ยงไม่ได้จะมี "คำตอบ" ที่บอกว่าจะไม่ใช้ regex ในการแยกวิเคราะห์ HTML

ทำไมจะไม่ล่ะ? ฉันทราบว่ามีตัวแยกวิเคราะห์ HTML "ของจริง" ที่อ้างถึงออกมาเช่นBeautiful Soupและฉันมั่นใจว่ามันมีประสิทธิภาพและมีประโยชน์ แต่ถ้าคุณเพิ่งทำสิ่งที่ง่ายรวดเร็วหรือสกปรกแล้วทำไม รบกวนการใช้สิ่งที่ซับซ้อนมากเมื่องบ regex ไม่กี่จะทำงานได้ดี?

ยิ่งไปกว่านั้นมีบางสิ่งพื้นฐานที่ฉันไม่เข้าใจเกี่ยวกับ regex ที่ทำให้พวกเขาเป็นตัวเลือกที่ไม่ดีสำหรับการแยกวิเคราะห์โดยทั่วไปหรือไม่?


3
ฉันคิดว่านี่เป็น dupe ของstackoverflow.com/questions/133601
jcrossley3

23
เพราะมีเพียง Chuck Norris เท่านั้นที่สามารถแยก HTML ด้วย regex (ตามที่อธิบายไว้ใน Zalgo สิ่งที่มีชื่อเสียงนี้: stackoverflow.com/questions/1732348/… )
takeshin

1
คำถามนี้กระตุ้นให้ฉันถามอีกคำถามหนึ่งที่เกี่ยวข้องกัน ในกรณีที่คุณสนใจ: ทำไมจึงไม่สามารถใช้ regex ในการแยกวิเคราะห์ HTML / XML: คำอธิบายอย่างเป็นทางการในเงื่อนไขของคนธรรมดา
mac


คำถามนี้ถูกเพิ่มไปยังคำถามที่พบบ่อยของสแต็คโอเวอร์โฟลว์นิพจน์ปกติภายใต้
aliteralmind

คำตอบ:


212

การแยกวิเคราะห์ HTML ทั้งหมดไม่สามารถทำได้ด้วยนิพจน์ทั่วไปเนื่องจากขึ้นอยู่กับการจับคู่การเปิดและแท็กปิดซึ่งไม่สามารถทำได้ด้วย regexps

นิพจน์ทั่วไปสามารถจับคู่กับภาษาปกติเท่านั้น แต่ HTML เป็นภาษาที่ไม่มีบริบทและไม่ใช่ภาษาปกติ (เนื่องจาก @StefanPochmann ได้กล่าวไว้แล้วภาษาปกติก็ไม่มีบริบท สิ่งเดียวที่คุณสามารถทำได้กับ regexps บน HTML คือการวิเคราะห์พฤติกรรม แต่จะไม่สามารถใช้ได้กับทุกสภาวะ มันเป็นไปได้ที่จะนำเสนอไฟล์ HTML ที่จะจับคู่ผิดโดยการแสดงออกปกติใด ๆ


26
คำตอบที่ดีที่สุด หากสามารถจับคู่ไวยากรณ์ปกติเท่านั้นเราจะต้องมี regexp ขนาดใหญ่อย่างไม่ จำกัด เพื่อแยกไวยากรณ์ที่ไม่มีบริบทเช่น HTML ฉันชอบเมื่อสิ่งเหล่านี้มีคำตอบทางทฤษฎีที่ชัดเจน
ntownsend

2
ฉันคิดว่าเรากำลังพูดถึง regexes Perl ชนิดที่พวกเขาไม่ได้แสดงออกปกติ
แฮงค์เกย์

5
จริงๆแล้วนิพจน์ปกติ. Net สามารถจับคู่การเปิดด้วยแท็กปิดได้ในระดับหนึ่งโดยใช้กลุ่มที่สมดุลและนิพจน์ที่ออกแบบอย่างระมัดระวัง ที่มีอยู่ทั้งหมดใน regexp ยังคงบ้าแน่นอนมันจะดูเหมือนรหัสที่ดี Chtulhu และอาจจะเรียกตัวจริงเช่นกัน และท้ายที่สุดมันยังไม่สามารถใช้ได้กับทุกกรณี พวกเขาบอกว่าถ้าคุณเขียนนิพจน์ทั่วไปที่สามารถแยก HTML ใด ๆ ได้อย่างถูกต้องเอกภพจะยุบตัว
Alex Paven

5
libs regex บางตัวสามารถทำนิพจน์ทั่วไปแบบเรียกซ้ำ (ทำให้การแสดงผลไม่ใช่แบบปกติได้อย่างมีประสิทธิภาพ :)
Ondra Žižka

43
-1 คำตอบนี้ให้ข้อสรุปที่ถูกต้อง ("เป็นความคิดที่ไม่ดีที่จะแยกวิเคราะห์ HTML ด้วย Regex") จากการขัดแย้งที่ไม่ถูกต้อง ("เพราะ HTML ไม่ใช่ภาษาปกติ") สิ่งที่คนส่วนใหญ่ในปัจจุบันหมายถึงเมื่อพวกเขากล่าวว่า "regex" (PCRE) เป็นอย่างดีมีความสามารถไม่เพียง แต่แยกไวยากรณ์บริบทฟรี (ที่น่ารำคาญจริง) แต่ยังตามบริบทไวยากรณ์ (ดูstackoverflow.com/questions/7434272/ … )
NikiC

35

สำหรับ reg´ อย่างรวดเร็วสกปรกจะทำได้ดี แต่สิ่งพื้นฐานที่ต้องรู้คือมันเป็นไปไม่ได้ที่จะสร้าง regexp ที่จะแยกวิเคราะห์ HTML ได้อย่างถูกต้อง

สาเหตุคือ regexps ไม่สามารถจัดการกับนิพจน์ที่ซ้อนกันโดยพลการ ดูสามารถใช้นิพจน์ทั่วไปเพื่อจับคู่รูปแบบที่ซ้อนกันได้หรือไม่


1
libs regex บางตัวสามารถทำนิพจน์ทั่วไปแบบเรียกซ้ำ (ทำให้การแสดงผลไม่ใช่แบบปกติได้อย่างมีประสิทธิภาพ :)
Ondra Žižka

23

(จากhttp://htmlparsing.com/regexes )

สมมติว่าคุณมีไฟล์ HTML ที่คุณพยายามดึง URL จากแท็ก <img>

<img src="http://example.com/whatever.jpg">

ดังนั้นคุณเขียน regex เช่นนี้ใน Perl:

if ( $html =~ /<img src="(.+)"/ ) {
    $url = $1;
}

ในกรณีนี้ก็จะประกอบด้วย$url http://example.com/whatever.jpgแต่จะเกิดอะไรขึ้นเมื่อคุณเริ่มรับ HTML เช่นนี้:

<img src='http://example.com/whatever.jpg'>

หรือ

<img src=http://example.com/whatever.jpg>

หรือ

<img border=0 src="http://example.com/whatever.jpg">

หรือ

<img
    src="http://example.com/whatever.jpg">

หรือคุณเริ่มได้รับผลบวกปลอมจาก

<!-- // commented out
<img src="http://example.com/outdated.png">
-->

มันดูเรียบง่ายและมันอาจจะง่ายสำหรับไฟล์เดียวที่ไม่มีการเปลี่ยนแปลง แต่สำหรับทุกสิ่งที่คุณจะทำกับข้อมูล HTML โดยพลการ regexes เป็นเพียงสูตรสำหรับความโศกเศร้าในอนาคต


4
นี่ดูเหมือนจะเป็นคำตอบที่แท้จริง - ในขณะที่มันเป็นไปได้ที่จะแยกวิเคราะห์ HTML ด้วย regex โดยพลการเนื่องจาก regexes ในปัจจุบันเป็นมากกว่าออโตมาต้า จำกัด เพื่อแยกวิเคราะห์ HTML โดยพลการและไม่ใช่เพียงหน้าคอนกรีตที่คุณต้องนำ และ regexes จะกลายเป็น 1,000 เท่าที่อ่านไม่ได้
สมิ ธ จอห์น

1
เฮ้แอนดี้ฉันใช้เวลาในการหาคำที่สนับสนุนกรณีที่คุณกล่าวถึง stackoverflow.com/a/40095824/1204332ให้ฉันรู้ว่าคุณคิดอย่างไร! :)
Ivan Chaer

2
เหตุผลในคำตอบนี้เป็นวิธีที่ล้าสมัยและนำไปใช้ในวันนี้น้อยกว่าเดิม (ซึ่งฉันคิดว่ามันไม่ได้) (การอ้างถึง OP: "ถ้าคุณกำลังทำอะไรง่าย ๆ เร็ว ๆ หรือสกปรก ... ")
Sz.

16

เหตุผลสองประการอย่างรวดเร็ว:

  • การเขียน regex ที่สามารถทนต่ออินพุตที่เป็นอันตรายนั้นยาก ยากกว่าการใช้เครื่องมือที่สร้างไว้ล่วงหน้า
  • การเขียน regex ที่สามารถทำงานกับมาร์กอัปที่ไร้สาระที่คุณจะติดอยู่กับมันยาก ยากกว่าการใช้เครื่องมือที่สร้างไว้ล่วงหน้า

เกี่ยวกับความเหมาะสมของการ regexes สำหรับการแยกวิเคราะห์โดยทั่วไป: พวกเขาไม่เหมาะสม คุณเคยเห็น regexes หลายประเภทที่คุณต้องใช้ในการแยกวิเคราะห์ภาษาส่วนใหญ่หรือไม่?


2
ว้าว? การลงคะแนนหลังจาก 2+ ปีหรือไม่ ในกรณีที่มีใครสงสัยฉันไม่ได้พูดว่า "เพราะมันเป็นไปไม่ได้ในทางทฤษฎี" เพราะคำถามที่ถามอย่างชัดเจนเกี่ยวกับ "รวดเร็วและสกปรก" ไม่ใช่ "ถูกต้อง" OP ชัดเจนอ่านแล้วคำตอบที่ครอบคลุมดินแดนที่เป็นไปไม่ได้ในทางทฤษฎีและยังไม่พอใจ
แฮงค์เกย์

1
มี upvote หลังจาก 5 ปีขึ้นไป :) สำหรับสาเหตุที่คุณอาจได้รับ downvote ฉันไม่มีคุณสมบัติที่จะพูด แต่โดยส่วนตัวแล้วฉันชอบที่จะเห็นตัวอย่างหรือคำอธิบายมากกว่าคำถามเชิงโวหารที่ปิดท้าย
อดัมเซ่น

3
โดยพื้นฐานแล้วการแยกวิเคราะห์ HTML ที่รวดเร็วและสกปรกที่ทำในการจัดส่งผลิตภัณฑ์หรือเครื่องมือภายในจะกลายเป็นช่องโหว่ด้านความปลอดภัยที่อ้าปากค้างหรือมีข้อบกพร่องที่รอให้เกิดขึ้น มันจะต้องหมดกำลังใจด้วยความเอร็ดอร่อย หากหนึ่งสามารถใช้ regex หนึ่งสามารถใช้ parser html ที่เหมาะสม
Reinstate Monica

16

เท่าที่การแจงไปนิพจน์ทั่วไปจะมีประโยชน์ในขั้นตอน "การวิเคราะห์คำศัพท์" (lexer) ขั้นตอนซึ่งอินพุตถูกแบ่งย่อยเป็นโทเค็น มันมีประโยชน์น้อยกว่าในเวที "build a parse tree"

สำหรับตัวแยกวิเคราะห์ HTML ฉันคาดหวังให้ยอมรับเฉพาะ HTML ที่มีรูปแบบที่ดีและต้องการความสามารถภายนอกสิ่งที่นิพจน์ทั่วไปสามารถทำได้ (พวกเขาไม่สามารถ "นับ" และตรวจสอบให้แน่ใจว่าจำนวนองค์ประกอบการเปิดที่กำหนดนั้น ขององค์ประกอบการปิด)


8

เนื่องจากมีหลายวิธีในการ "ทำให้เสีย" HTML ที่เบราว์เซอร์จะจัดการในลักษณะที่ค่อนข้างเสรี แต่จะใช้ความพยายามอย่างมากในการทำซ้ำพฤติกรรมเสรีของเบราว์เซอร์เพื่อให้ครอบคลุมทุกกรณีด้วยนิพจน์ทั่วไปดังนั้น regex ของคุณจะล้มเหลว เคสและนั่นอาจจะทำให้เกิดช่องว่างด้านความปลอดภัยที่ร้ายแรงในระบบของคุณ


1
จริงมากส่วนใหญ่ของ HTML ออกมีน่ากลัว ฉันไม่เข้าใจว่านิพจน์ทั่วไปที่ล้มเหลวสามารถแนะนำช่องว่างด้านความปลอดภัยที่ร้ายแรงได้อย่างไร คุณยกตัวอย่างได้ไหม
ntownsend

4
ntownsend: ยกตัวอย่างเช่นคุณคิดว่าคุณถอดแท็กสคริปต์ทั้งหมดออกจาก HTML แต่ regex ของคุณล้มเหลวในกรณีพิเศษ (นั่นคือสมมติว่าใช้งานได้กับ IE6 เท่านั้น): boom คุณมีความสามารถในการหยาบคายของ XSS!
Tamas Czinege

1
นี่เป็นตัวอย่างสมมุติอย่างเคร่งครัดเนื่องจากตัวอย่างโลกแห่งความจริงส่วนใหญ่มีความซับซ้อนเกินกว่าที่จะใส่ความคิดเห็นเหล่านี้ได้ แต่คุณสามารถค้นหาบางอย่างได้จากการค้นหาอย่างรวดเร็วในหัวข้อ
Tamas Czinege

3
+1 สำหรับการพูดถึงมุมความปลอดภัย เมื่อคุณเชื่อมต่อกับอินเทอร์เน็ตทั้งหมดคุณจะไม่สามารถเขียนโค้ดแฮ็ค "ใช้เวลาส่วนใหญ่ได้"
j_random_hacker

7

ปัญหาคือผู้ใช้ส่วนใหญ่ที่ถามคำถามที่เกี่ยวข้องกับ HTML และ regex ทำสิ่งนี้เพราะพวกเขาไม่สามารถหา regex ของตัวเองที่ใช้งานได้ ถ้าอย่างนั้นก็ต้องคิดว่าทุกอย่างจะง่ายขึ้นเมื่อใช้ตัวแยกวิเคราะห์ DOM หรือ SAX หรือสิ่งที่คล้ายกัน สิ่งเหล่านี้ได้รับการปรับปรุงและสร้างขึ้นเพื่อวัตถุประสงค์ในการทำงานกับโครงสร้างเอกสารแบบ XML

แน่นอนว่ามีปัญหาที่สามารถแก้ไขได้อย่างง่ายดายด้วยนิพจน์ทั่วไป แต่เน้นในการโกหกได้อย่างง่ายดาย

หากคุณเพียงแค่ต้องการค้นหา URL ทั้งหมดที่ดูเหมือนว่าhttp://.../คุณพอใจกับ regexps แต่ถ้าคุณต้องการค้นหา URL ทั้งหมดที่อยู่ในองค์ประกอบที่มีคลาส 'mylink' คุณน่าจะใช้เครื่องมือแยกวิเคราะห์ที่เหมาะสมกว่า


6

นิพจน์ทั่วไปไม่ได้รับการออกแบบมาเพื่อจัดการโครงสร้างแท็กที่ซ้อนกันและมีความซับซ้อนที่สุด (ที่แย่ที่สุดเป็นไปไม่ได้) ในการจัดการกรณีขอบที่เป็นไปได้ทั้งหมดที่คุณได้รับด้วย HTML จริง


6

ฉันเชื่อว่าคำตอบอยู่ในทฤษฎีการคำนวณ สำหรับภาษาที่จะแยกวิเคราะห์โดยใช้ regex นั้นจะต้องเป็นคำจำกัดความ "ปกติ" ( ลิงค์ ) HTML ไม่ใช่ภาษาปกติเนื่องจากไม่ตรงตามเกณฑ์จำนวนมากสำหรับภาษาปกติ (เกี่ยวข้องกับการซ้อนกันหลายระดับในโค้ด HTML) หากคุณมีความสนใจในทฤษฎีของการคำนวณที่ผมจะแนะนำนี้หนังสือ


1
ฉันอ่านหนังสือเล่มนั้นจริงๆ มันไม่ได้เกิดขึ้นกับฉันว่า HTML เป็นภาษาที่ไม่มีบริบท
ntownsend

4

การแสดงออกนี้ดึงคุณสมบัติจากองค์ประกอบ HTML มันรองรับ:

  • แอตทริบิวต์ที่ไม่มีเครื่องหมาย / ยกมา
  • คำพูดเดี่ยว / คู่
  • คำพูดที่หลบหนีภายในคุณลักษณะ
  • ช่องว่างรอบเท่ากับสัญญาณ
  • คุณลักษณะใด ๆ
  • ตรวจสอบเฉพาะคุณลักษณะภายในแท็ก
  • หลบหนีความคิดเห็นและ
  • จัดการคำพูดที่แตกต่างกันภายในค่าคุณลักษณะ

(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)

ลองดูสิ มันใช้งานได้ดีขึ้นกับธง "gisx" เช่นเดียวกับในตัวอย่าง


1
นั่นเป็นเรื่องที่น่าสนใจมาก ไม่สามารถอ่านได้อาจจะยากในการแก้ไขข้อบกพร่อง แต่ก็ยัง: งานที่น่าประทับใจ!
Eric Duminil

สิ่งนี้ยังคงสันนิษฐานว่า HTML นั้นมีรูปแบบที่ดี หากไม่มีการจับคู่บริบทสิ่งนี้จะจับคู่ URL ที่ชัดเจนในบริบทที่โดยทั่วไปคุณไม่ต้องการจับคู่เหมือนในโค้ด JavaScript ใน<script>แท็ก
tripleee

4

HTML / XML แบ่งออกเป็นมาร์กอัปและเนื้อหา Regex มีประโยชน์ในการทำวิเคราะห์คำแท็กเท่านั้น ฉันเดาว่าคุณสามารถอนุมานเนื้อหาได้ มันจะเป็นตัวเลือกที่ดีสำหรับ SAX parser สามารถส่งแท็กและเนื้อหาไปยังฟังก์ชันที่ผู้ใช้กำหนดซึ่งสามารถติดตามการซ้อน / ปิดองค์ประกอบได้

เท่าที่เป็นเพียงการแยกแท็กก็สามารถทำได้ด้วย regex และใช้ในการแถบแท็กจากเอกสาร

ในการทดสอบเป็นเวลาหลายปีฉันพบความลับในการแยกแท็กของเบราว์เซอร์ทั้งดีและไม่ดี

องค์ประกอบปกติจะถูกแยกวิเคราะห์ด้วยแบบฟอร์มนี้:

หลักของแท็กเหล่านี้ใช้ regex นี้

 (?:
      " [\S\s]*? " 
   |  ' [\S\s]*? ' 
   |  [^>]? 
 )+

คุณจะสังเกตเห็น[^>]?ว่านี่เป็นทางเลือกหนึ่ง สิ่งนี้จะตรงกับคำพูดที่ไม่สมดุลจากแท็กที่ไม่มีรูปแบบ

นอกจากนี้ยังเป็นรากที่สำคัญที่สุดของความชั่วร้ายทั้งหมดต่อการแสดงออกปกติ วิธีที่ใช้จะทำให้เกิดการชนเพื่อตอบสนองความโลภของมัน

หากใช้อย่างอดทนไม่มีปัญหา แต่ถ้าคุณบังคับให้จับคู่บางสิ่งด้วยการสลับคู่กับแอตทริบิวต์ / ค่าที่ต้องการและไม่ให้การป้องกันที่เพียงพอจากการย้อนรอยมันเป็นฝันร้ายที่ไม่สามารถควบคุมได้

นี่คือรูปแบบทั่วไปสำหรับแท็กเก่าเพียงธรรมดา สังเกต [\w:]เห็นชื่อแท็กแทนหรือไม่ ในความเป็นจริงตัวอักษรที่ถูกต้องตามกฎหมายที่ใช้แทนชื่อแท็กนั้นเป็นรายการของอักขระ Unicode อย่างไม่น่าเชื่อ

 <     
 (?:
      [\w:]+ 
      \s+ 
      (?:
           " [\S\s]*? " 
        |  ' [\S\s]*? ' 
        |  [^>]? 
      )+
      \s* /?
 )
 >

เรายังเห็นว่าคุณไม่สามารถค้นหาแท็กเฉพาะโดยไม่ต้องแยกวิเคราะห์แท็กทั้งหมด ฉันหมายความว่าคุณทำได้ แต่มันจะต้องใช้คำกริยารวมกันเช่น (* SKIP) (* FAIL) แต่ยังต้องแยกแท็กทั้งหมด

เหตุผลคือไวยากรณ์แท็กอาจถูกซ่อนอยู่ภายในแท็กอื่น ๆ

ดังนั้นในการแยกวิเคราะห์แท็กทั้งหมดแบบพาสซีฟจึงจำเป็นต้องมี regex เหมือนกับแท็กด้านล่าง อันนี้ตรงกับเนื้อหาที่มองไม่เห็นเช่นกัน

ในฐานะที่เป็น HTML ใหม่หรือ XML หรืออื่น ๆ พัฒนาสร้างใหม่เพียงแค่เพิ่มมันเป็นหนึ่งในทางเลือก


บันทึกหน้าเว็บ - ฉันไม่เคยเห็นหน้าเว็บ (หรือ xhtml / xml) ที่สิ่งนี้
มีปัญหา หากคุณพบหนึ่งแจ้งให้เราทราบ

หมายเหตุประสิทธิภาพ - รวดเร็ว นี่คือตัวแยกวิเคราะห์แท็กที่เร็วที่สุดที่ฉันเคยเห็น
(อาจจะเร็วกว่าใครจะรู้)
ฉันมีหลายรุ่น นอกจากนี้ยังยอดเยี่ยมเหมือนมีดโกน
(ถ้าคุณเป็นมือแบบ)


ทำ regex ดิบให้สมบูรณ์

<(?:(?:(?:(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]*?))))>

รูปแบบการจัดรูปแบบ

 <
 (?:
      (?:
           (?:
                # Invisible content; end tag req'd
                (                             # (1 start)
                     script
                  |  style
                  |  object
                  |  embed
                  |  applet
                  |  noframes
                  |  noscript
                  |  noembed 
                )                             # (1 end)
                (?:
                     \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]*? )
           )
      )
 )
 >

3

"มันขึ้นอยู่กับว่า" เป็นความจริงที่ regex จะไม่สามารถแยกวิเคราะห์ HTML ได้อย่างถูกต้องแม่นยำด้วยเหตุผลทั้งหมดที่ให้ไว้ที่นี่ อย่างไรก็ตามหากผลที่ตามมาจากการทำผิด (เช่นไม่จัดการแท็กที่ซ้อนกัน) นั้นมีน้อยและถ้า regexes นั้นสะดวกสบายมากในสภาพแวดล้อมของคุณ (เช่นเมื่อคุณแฮ็ค Perl) ไปข้างหน้า

สมมติว่าคุณโออาจแยกวิเคราะห์หน้าเว็บที่เชื่อมโยงไปยังเว็บไซต์ของคุณ - บางทีคุณอาจพบหน้าเว็บเหล่านั้นด้วยการค้นหาลิงก์ของ Google - และคุณต้องการวิธีที่รวดเร็วในการเข้าใจแนวคิดทั่วไปเกี่ยวกับบริบทของลิงก์ คุณกำลังพยายามเรียกใช้รายงานเล็กน้อยที่อาจแจ้งเตือนคุณให้เชื่อมโยงสแปมบางอย่างเช่นนั้น

ในกรณีนี้การวิเคราะห์เอกสารบางส่วนไม่ถูกต้องจะเป็นเรื่องใหญ่ ไม่มีใคร แต่คุณจะเห็นความผิดพลาดและถ้าคุณโชคดีมากจะมีไม่กี่พอที่คุณสามารถติดตามเป็นรายบุคคล

ฉันเดาว่าฉันพูดว่ามันเป็นการแลกเปลี่ยน บางครั้งการนำไปใช้หรือใช้ตัวแยกวิเคราะห์ที่ถูกต้อง - ง่ายอย่างที่อาจเป็น - อาจไม่คุ้มกับปัญหาหากความแม่นยำไม่สำคัญ

เพียงระวังข้อสันนิษฐานของคุณ ฉันสามารถนึกถึงบางวิธีที่ทางลัด regexp สามารถย้อนกลับมาใช้ใหม่ได้หากคุณพยายามแยกบางสิ่งที่จะแสดงต่อสาธารณะ


3

มีบางกรณีที่การใช้นิพจน์ทั่วไปในการแยกวิเคราะห์ข้อมูลบางอย่างจาก HTML เป็นวิธีที่ถูกต้องในการดำเนินการ - ขึ้นอยู่กับสถานการณ์เป็นจำนวนมาก

ฉันทามติข้างต้นคือโดยทั่วไปแล้วมันเป็นความคิดที่ไม่ดี อย่างไรก็ตามหากทราบว่าโครงสร้าง HTML (และไม่น่าจะเปลี่ยนแปลง) ก็ยังเป็นแนวทางที่ถูกต้อง


3

โปรดทราบว่าในขณะที่ HTML นั้นไม่ปกติ แต่บางส่วนของหน้าที่คุณกำลังดูอยู่อาจเป็นเรื่องปกติ

ตัวอย่างเช่นมันเป็นข้อผิดพลาดสำหรับ<form>แท็กที่จะซ้อนกัน; หากหน้าเว็บทำงานอย่างถูกต้องการใช้นิพจน์ทั่วไปเพื่อคว้า a <form>จะสมเหตุสมผลอย่างสมบูรณ์

เมื่อเร็ว ๆ นี้ฉันได้ทำการขูดเว็บโดยใช้ซีลีเนียมและนิพจน์ทั่วไปเท่านั้น ผมได้ไปกับมันเนื่องจากข้อมูลที่ผมอยากได้รับการใส่ใน<form>และใส่ในรูปแบบตารางง่าย (ดังนั้นฉันยังสามารถนับบน<table>, <tr>และ<td>เพื่อไม่ให้ซ้อนกัน - ที่เป็นจริงสูงผิดปกติ) ในระดับหนึ่งนิพจน์ทั่วไปนั้นเกือบจะจำเป็นเพราะโครงสร้างบางอย่างที่ฉันต้องการในการเข้าถึงถูกคั่นด้วยความคิดเห็น (ซุปสวย ๆ สามารถให้ความเห็นกับคุณได้ แต่คงยากที่จะคว้า<!-- BEGIN -->และ<!-- END -->บล็อกโดยใช้ซุปที่สวยงาม)

ถ้าฉันต้องกังวลเกี่ยวกับตารางที่ซ้อนกัน แต่วิธีการของฉันก็จะไม่ทำงาน! ฉันจะต้องย้อนกลับไปในซุปที่สวยงาม อย่างไรก็ตามถึงกระนั้นบางครั้งคุณสามารถใช้นิพจน์ปกติเพื่อจับก้อนที่คุณต้องการแล้วเจาะลงมาจากตรงนั้น


2

ที่จริงแล้วการแยกวิเคราะห์ HTML ด้วย regex นั้นเป็นไปได้อย่างสมบูรณ์ใน PHP คุณเพียงแค่ต้องแยกสตริงทั้งหมดย้อนหลังโดยใช้strrposเพื่อค้นหา<และทำซ้ำ regex จากที่นั่นโดยใช้ตัวระบุ ungreedy ในแต่ละครั้งเพื่อรับแท็กที่ซ้อนกัน ไม่ใช่เรื่องแฟนซีและช้ามากสำหรับเรื่องใหญ่ แต่ฉันใช้มันเพื่อแก้ไขเทมเพลตส่วนตัวสำหรับเว็บไซต์ของฉัน ฉันไม่ได้แยกวิเคราะห์ HTML จริงๆ แต่มีแท็กที่กำหนดเองสองสามตัวที่ฉันทำเพื่อค้นหารายการฐานข้อมูลเพื่อแสดงตารางข้อมูล ( <#if()>แท็กของฉันสามารถเน้นรายการพิเศษด้วยวิธีนี้) ฉันไม่ได้เตรียมที่จะไปกับตัวแยกวิเคราะห์ XML ในแท็กที่สร้างขึ้นด้วยตนเองเพียงไม่กี่แท็ก (ที่มีข้อมูลที่ไม่ใช่ XML ภายใน) และที่นี่

ดังนั้นแม้ว่าคำถามนี้จะตายไปมาก แต่ก็ยังปรากฏในการค้นหาของ Google ฉันอ่านและคิดว่า "ยอมรับการท้าทาย" และแก้ไขรหัสอย่างง่ายของฉันโดยไม่ต้องเปลี่ยนทุกอย่าง ตัดสินใจที่จะเสนอความเห็นที่แตกต่างให้กับทุกคนที่ค้นหาด้วยเหตุผลที่คล้ายกัน นอกจากนี้คำตอบสุดท้ายถูกโพสต์เมื่อ 4 ชั่วโมงก่อนดังนั้นนี่จึงเป็นประเด็นร้อน


2
-1 สำหรับการแนะนำแนวคิด TERRIBLE คุณพิจารณาช่องว่างระหว่างแท็กและวงเล็บมุมปิดหรือไม่ (เช่น, <tag >) คุณพิจารณาแท็กปิดความคิดเห็นหรือไม่? (เช่น<tag> <!-- </tag> -->) คุณพิจารณา CDATA หรือไม่ คุณพิจารณาแท็กตัวพิมพ์เล็กหรือใหญ่ใช่ไหม (เช่น<Tag> </tAG>) คุณพิจารณาเรื่องนี้ด้วยหรือไม่
rmunn

1
ในบางกรณีของแท็กที่กำหนดเองไม่กี่คำใช่นิพจน์ทั่วไปใช้ได้ดี ดังนั้นก็ไม่ได้ว่าการใช้งานของพวกเขาเป็นความผิดพลาดในกรณีของคุณโดยเฉพาะ แต่นั่นไม่ใช่ HTML และการพูดว่า "การแยกวิเคราะห์ HTML ด้วย regex นั้นเป็นไปได้อย่างสมบูรณ์ใน PHP" นั้นเป็นเพียงความผิดพลาดและแนวคิด TERRIBLE ความไม่สอดคล้องกันของ HTML จริง (และมีมากกว่าที่ฉันมีในรายการ) เป็นสาเหตุที่คุณไม่ควรแยกวิเคราะห์ HTML จริงด้วยนิพจน์ทั่วไป ดูดีคำตอบอื่น ๆ ทั้งหมดสำหรับคำถามนี้เช่นเดียวกับคำตอบที่ฉันเชื่อมโยงในความคิดเห็นอื่นของฉันด้านบน
rmunn

2
PHP เป็นภาษาทัวริงที่สมบูรณ์ดังนั้นจึงไม่ผิดพลาดเลย ทุกอย่างเป็นไปได้ในการคำนวณเป็นไปได้รวมถึงการแยก HTML ช่องว่างในแท็กไม่เคยมีปัญหาและฉันได้ปรับเปลี่ยนเพื่อแสดงรายการองค์ประกอบแท็กตามลำดับ การใช้งานแท็กของฉันจะแก้ไขโดยอัตโนมัติพร้อมกับกล่องที่ไม่สอดคล้องกันและถอดสิ่งที่ใส่ความเห็นไว้ในขั้นตอนแรกและหลังจากที่มีการเพิ่มเติมภายหลังในภายหลังแท็กทุกประเภทสามารถเพิ่มได้อย่างง่ายดาย (แม้ว่าจะเป็นกรณี ๆ และฉันค่อนข้างแน่ใจว่า CDATA เป็นองค์ประกอบ XML จริง ๆ ไม่ใช่ HTML
Deji

2
วิธีการเก่าของฉัน (ที่ฉันอธิบายไว้ที่นี่) ค่อนข้างไม่มีประสิทธิภาพและฉันเพิ่งเริ่มเขียนเนื้อหาใหม่จำนวนมาก เมื่อพูดถึงการทำสิ่งเหล่านี้ความเป็นไปได้ไม่ใช่ประเด็น วิธีที่ดีที่สุดคือความกังวลหลักเสมอ คำตอบที่แท้จริงคือ "ไม่มีวิธีง่าย ๆ ใน PHP" ไม่มีใครบอกว่าไม่มีวิธีที่จะทำใน PHP หรือว่ามันเป็นความคิดที่น่ากลัว แต่มันเป็นไปไม่ได้กับ regex ซึ่งฉันไม่เคยลองอย่างสุจริต แต่ข้อบกพร่องที่สำคัญในคำตอบของฉันคือฉันคิดว่าคำถามนั้นหมายถึง regex ภายใต้บริบทของ PHP ซึ่งไม่จำเป็นต้องเป็นอย่างนั้น
Deji

2

ฉันลองใช้มือของฉันที่ regex ด้วยเช่นกัน ส่วนใหญ่จะมีประโยชน์สำหรับการค้นหาชิ้นส่วนเนื้อหาที่จับคู่กับแท็ก HTML ถัดไปและไม่ได้มองหาการจับคู่แท็กปิด แต่จะรับแท็กใกล้ชิด ม้วนสแต็กในภาษาของคุณเองเพื่อตรวจสอบสิ่งเหล่านั้น

ใช้กับตัวเลือก 'sx' 'g' ด้วยถ้าคุณรู้สึกโชคดี:

(?P<content>.*?)                # Content up to next tag
(?P<markup>                     # Entire tag
  <!\[CDATA\[(?P<cdata>.+?)]]>| # <![CDATA[ ... ]]>
  <!--(?P<comment>.+?)-->|      # <!-- Comment -->
  </\s*(?P<close_tag>\w+)\s*>|  # </tag>
  <(?P<tag>\w+)                 # <tag ...
    (?P<attributes>
      (?P<attribute>\s+
# <snip>: Use this part to get the attributes out of 'attributes' group.
        (?P<attribute_name>\w+)
        (?:\s*=\s*
          (?P<attribute_value>
            [\w:/.\-]+|         # Unquoted
            (?=(?P<_v>          # Quoted
              (?P<_q>['\"]).*?(?<!\\)(?P=_q)))
            (?P=_v)
          ))?
# </snip>
      )*
    )\s*
  (?P<is_self_closing>/?)   # Self-closing indicator
  >)                        # End of tag

อันนี้ออกแบบมาสำหรับ Python (อาจใช้ได้กับภาษาอื่นไม่ได้ลองใช้มันใช้ lookaheads เชิงบวก, lookbehinds เชิงลบและชื่อ backreferences) สนับสนุน:

  • เปิดแท็ก - <div ...>
  • ปิดแท็ก - </div>
  • ความคิดเห็น - <!-- ... -->
  • CDATA - <![CDATA[ ... ]]>
  • แท็กปิดตัวเอง - <div .../>
  • ค่าแอตทริบิวต์ตัวเลือก - <input checked>
  • ไม่ได้ระบุ / อ้างอิงค่าคุณสมบัติ - <div style='...'>
  • คำพูดเดี่ยว / คู่ - <div style="...">
  • คำพูดที่หลบหนี - <a title='John\'s Story'>
    (นี่ไม่ใช่ HTML ที่ถูกต้องจริงๆ แต่ฉันเป็นคนดี)
  • ช่องว่างรอบสัญญาณเท่ากับ - <a href = '...'>
  • จับภาพชื่อสำหรับบิตที่น่าสนใจ

นอกจากนี้ยังเป็นรักที่ดีเกี่ยวกับการไม่เรียกแท็กที่ไม่ถูกต้องเช่นเมื่อคุณลืมหรือ<>

หากรสชาติของ regex ของคุณรองรับการจับภาพที่มีชื่อซ้ำกันแสดงว่าคุณเป็นทอง แต่ Python reไม่ได้ (ฉันรู้ว่า regex ทำ แต่ฉันต้องใช้วานิลลา Python) นี่คือสิ่งที่คุณจะได้รับ:

  • content- เนื้อหาทั้งหมดจนถึงแท็กถัดไป คุณสามารถละทิ้งสิ่งนี้ได้
  • markup - แท็กทั้งหมดพร้อมทุกอย่างในนั้น
  • comment - ถ้ามันเป็นความคิดเห็นเนื้อหาความคิดเห็น
  • cdata- ถ้าเป็น<![CDATA[...]]>เนื้อหาของ CDATA
  • close_tag- หากเป็นแท็กปิด ( </div>) หมายถึงชื่อแท็ก
  • tag- หากเป็นแท็กเปิด ( <div>) ชื่อแท็ก
  • attributes- คุณสมบัติทั้งหมดภายในแท็ก ใช้สิ่งนี้เพื่อรับคุณลักษณะทั้งหมดหากคุณไม่ได้รับกลุ่มซ้ำ
  • attribute - ซ้ำแต่ละแอตทริบิวต์
  • attribute_name - ซ้ำชื่อแอตทริบิวต์แต่ละชื่อ
  • attribute_value- ซ้ำแต่ละค่าคุณลักษณะ รวมถึงคำพูดถ้ามันถูกยกมา
  • is_self_closing- นี่คือ/ถ้ามันเป็นแท็กปิดตัวเองมิฉะนั้นไม่มีอะไร
  • _qและ_v- ละเว้นสิ่งเหล่านี้; พวกมันถูกใช้ภายในเพื่อการอ้างอิงย้อนกลับ

หากเอ็นจิ้น regex ของคุณไม่สนับสนุนการจับภาพซ้ำที่ตั้งชื่อซ้ำมีส่วนที่เรียกว่าคุณสามารถใช้เพื่อรับแต่ละแอตทริบิวต์ เพียงแค่เรียกว่า regex ในattributesกลุ่มที่จะได้รับในแต่ละattribute, attribute_nameและattribute_valueออกมาจากมัน

ตัวอย่างที่นี่: https://regex101.com/r/mH8jSu/11


1

การแสดงออกปกติไม่ได้มีประสิทธิภาพเพียงพอสำหรับภาษาเช่น HTML แน่นอนว่ามีบางตัวอย่างที่คุณสามารถใช้นิพจน์ทั่วไปได้ แต่โดยทั่วไปจะไม่เหมาะสำหรับการแยกวิเคราะห์


0

คุณรู้ไหม ... มีความคิดมากมายที่คุณไม่สามารถทำได้และฉันคิดว่าทุกคนในรั้วทั้งสองข้างนั้นถูกและผิด คุณสามารถทำได้ แต่ใช้การประมวลผลมากกว่าเล็กน้อยเพียงแค่เรียกใช้หนึ่ง regex กับมัน ใช้สิ่งนี้ (ฉันเขียนสิ่งนี้ภายในหนึ่งชั่วโมง) เป็นตัวอย่าง มันถือว่า HTML นั้นถูกต้องสมบูรณ์ แต่ขึ้นอยู่กับภาษาที่คุณใช้เพื่อใช้ regex ข้างต้นคุณสามารถทำการแก้ไข HTML เพื่อให้แน่ใจว่ามันจะประสบความสำเร็จ ตัวอย่างเช่นการลบแท็กปิดที่ไม่ควรอยู่: </img>ตัวอย่างเช่น จากนั้นเพิ่มสแลช HTML แบบปิดไปข้างหน้าเดียวกับองค์ประกอบที่ขาดหายไป ฯลฯ

ฉันจะใช้สิ่งนี้ในบริบทของการเขียนห้องสมุดที่จะช่วยให้ฉันทำการดึงองค์ประกอบ HTML คล้ายกับของ JavaScript [x].getElementsByTagName()ตัวอย่างเช่น ฉันแค่แบ่งการทำงานที่ฉันเขียนในส่วน DEFINE ของ regex และใช้มันเพื่อก้าวเข้าไปข้างในต้นไม้ขององค์ประกอบหนึ่งรายการในแต่ละครั้ง

ดังนั้นนี่จะเป็นคำตอบสุดท้าย 100% สำหรับการตรวจสอบ HTML หรือไม่ ไม่ แต่มันเป็นการเริ่มต้นและด้วยการทำงานเพิ่มขึ้นเล็กน้อยก็สามารถทำได้ อย่างไรก็ตามการพยายามที่จะทำมันในการประมวลผล regex เพียงครั้งเดียวนั้นไม่สามารถใช้งานได้จริงหรือมีประสิทธิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.