Strip HTML จากสตริงใน Python


269
from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line

เมื่อพิมพ์บรรทัดในไฟล์ HTML ฉันพยายามค้นหาวิธีแสดงเนื้อหาของแต่ละองค์ประกอบ HTML เท่านั้นไม่ใช่การจัดรูปแบบเอง หากพบว่า'<a href="whatever.com">some text</a>'มันจะพิมพ์เพียง 'ข้อความ' '<b>hello</b>'พิมพ์ 'สวัสดี' ฯลฯ จะทำอย่างไรเกี่ยวกับการทำเช่นนี้?


16
การพิจารณาที่สำคัญคือวิธีจัดการเอนทิตี HTML (เช่น&amp;) คุณสามารถ 1) ลบออกพร้อมกับแท็ก (มักจะไม่พึงประสงค์และไม่จำเป็นเพราะพวกเขาเทียบเท่ากับข้อความธรรมดา), 2) ปล่อยให้พวกเขาไม่เปลี่ยนแปลง (วิธีการแก้ปัญหาที่เหมาะสมถ้าข้อความที่ถูกปล้นจะย้อนกลับไปในบริบท HTML) หรือ 3 ) ถอดรหัสให้เป็นข้อความธรรมดา (หากข้อความที่ตัดจะถูกนำไปไว้ในฐานข้อมูลหรือบริบทที่ไม่ใช่ HTML อื่น ๆ หรือถ้าเว็บเฟรมเวิร์กของคุณทำการ HTML โดยอัตโนมัติเพื่อหลีกเลี่ยงข้อความของคุณ)
SørenLøvborg

2
สำหรับ @ SørenLøvborgจุด 2): stackoverflow.com/questions/753052/…
Robert

2
คำตอบที่ดีที่สุดที่นี่ซึ่งโครงการ Django ใช้จนถึงเดือนมีนาคม 2014 ถูกพบว่าไม่ปลอดภัยต่อการเขียนสคริปต์ข้ามไซต์ - ดูลิงก์นั้นสำหรับตัวอย่างที่ทำให้ผ่าน ฉันแนะนำให้ใช้ Bleach.clean (), striptags ของ Markupsafe หรือแถบ strip_tags ล่าสุดของ Django
rescdsk

คำตอบ:


419

ฉันมักจะใช้ฟังก์ชั่นนี้เพื่อตัดแท็ก HTML เนื่องจากต้องการเพียง Python stdlib:

สำหรับ Python 3:

from io import StringIO
from html.parser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        super().__init__()
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

สำหรับ Python 2:

from HTMLParser import HTMLParser
from StringIO import StringIO

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

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

47
โปรดทราบว่าสิ่งนี้จะดึงเอนทิตี HTML (เช่น&amp;) และแท็ก
SørenLøvborg

30
@surya ฉันแน่ใจว่าคุณเคยเห็นสิ่งนี้แล้ว
tkone

8
ขอบคุณสำหรับคำตอบที่ดี สิ่งหนึ่งที่ควรทราบสำหรับผู้ที่ใช้ Python เวอร์ชันใหม่กว่า (3.2+) คือคุณจะต้องเรียกใช้__init__ฟังก์ชันของคลาสพาเรนต์ ดูที่นี่: stackoverflow.com/questions/11061058/… .
pseudoramble

10
เพื่อรักษาเอนทิตี html (แปลงเป็น unicode) ฉันได้เพิ่มสองบรรทัด: parser = HTMLParser()และ html = parser.unescape(html)ไปที่จุดเริ่มต้นของฟังก์ชั่น strip_tags
James Doepp - pihentagyu

156

ฉันไม่ได้คิดมากเกี่ยวกับกรณีที่มันจะพลาด แต่คุณสามารถทำ regex ง่าย ๆ :

re.sub('<[^<]+?>', '', text)

สำหรับผู้ที่ไม่เข้าใจ regex การค้นหานี้สำหรับสตริง<...>ที่เนื้อหาด้านในทำจากหนึ่งหรือมากกว่า ( +) <ตัวละครที่ไม่ได้เป็น ?หมายความว่ามันจะตรงกับสายที่เล็กที่สุดมันสามารถหา ยกตัวอย่างเช่นที่กำหนด<p>Hello</p>ก็จะตรง<'p>และแยกต่างหากกับ</p> ไม่ว่ามันจะตรงกับสตริงทั้งหมด?<..Hello..>

หากไม่มีแท็ก<ปรากฏใน html (เช่น. 2 < 3) ควรเขียนเป็นลำดับการหลีกเลี่ยง&...ดังนั้น^<อาจไม่จำเป็น


10
นี่เกือบจะเป็นวิธีที่strip_tagsของ Django ทำ
Bluu

10
โปรดทราบว่าสิ่งนี้จะทำให้เอนทิตี HTML (เช่น&amp;) ไม่เปลี่ยนแปลงในเอาต์พุต
SørenLøvborg

35
เรายังคงสามารถหลอกลวงวิธีนี้ด้วยสิ่งนี้: <script <script>> alert ("Hi!") <</script> / script>

19
อย่าทำแบบนี้! @Julio Garcia พูดว่ามันไม่ปลอดภัย!
rescdsk

18
ผู้ใช้อย่าสับสนระหว่างการปอก HTML และการฆ่าเชื้อ HTML ใช่สำหรับการป้อนข้อมูลที่ไม่ดีหรือเป็นอันตรายคำตอบนี้อาจสร้างผลลัพธ์ด้วยแท็ก HTML ในนั้น ยังคงเป็นวิธีที่ถูกต้องสมบูรณ์ในการตัดแท็ก HTML อย่างไรก็ตามการดึงแท็ก HTML ไม่ใช่การทดแทนที่ถูกต้องสำหรับการฆ่าเชื้อ HTML ที่เหมาะสม กฎคือไม่ยาก: เมื่อใดก็ตามที่คุณใส่สตริงข้อความธรรมดาเข้าออก HTML คุณควรเสมอ HTML หนีมัน (ใช้cgi.escape(s, True)) แม้ว่าคุณจะ "รู้" ว่ามันไม่ได้มี HTML (เช่นเพราะคุณถอดเนื้อหา HTML) . อย่างไรก็ตามนี่ไม่ใช่สิ่งที่ OP ถามเกี่ยวกับ
SørenLøvborg

76

คุณสามารถใช้get_text()คุณสมบัติBeautifulSoup

from bs4 import BeautifulSoup

html_str = '''
<td><a href="http://www.fakewebsite.com">Please can you strip me?</a>
<br/><a href="http://www.fakewebsite.com">I am waiting....</a>
</td>
'''
soup = BeautifulSoup(html_str)

print(soup.get_text()) 
#or via attribute of Soup Object: print(soup.text)

ขอแนะนำให้ระบุparserอย่างชัดเจนตัวอย่างเช่นBeautifulSoup(html_str, features="html.parser")สำหรับเอาต์พุตที่จะทำซ้ำได้


32

เวอร์ชั่นสั้น!

import re, cgi
tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)')

# Remove well-formed tags, fixing mistakes by legitimate users
no_tags = tag_re.sub('', user_input)

# Clean up anything else by escaping
ready_for_web = cgi.escape(no_tags)

แหล่ง regex: MarkupSafe รุ่นของพวกเขาจัดการเอนทิตี HTML ด้วยในขณะที่อันนี้ไม่ได้

ทำไมฉันไม่สามารถดึงแท็กออกแล้วปล่อยทิ้งไว้ได้?

เป็นเรื่องหนึ่งที่จะป้องกันผู้คนจาก<i>italicizing</i>สิ่งต่าง ๆ โดยไม่ปล่อยให้iลอยไปมา แต่ก็เป็นอีกเรื่องหนึ่งที่จะนำข้อมูลเข้ามาโดยพลการและทำให้ไม่เป็นอันตรายอย่างสมบูรณ์ เทคนิคส่วนใหญ่ในหน้านี้จะปล่อยให้สิ่งต่าง ๆ เช่นความคิดเห็นที่ไม่มีการปิดบัง ( <!--) และวงเล็บเหลี่ยมที่ไม่ได้เป็นส่วนหนึ่งของแท็ก ( blah <<<><blah) เหมือนเดิม เวอร์ชัน HTMLParser ยังสามารถใส่แท็กที่สมบูรณ์ได้หากอยู่ในความคิดเห็นที่ไม่มีการปิดบัง

เกิดอะไรขึ้นถ้าแม่แบบของคุณคือ{{ firstname }} {{ lastname }}อะไร? firstname = '<a'และlastname = 'href="http://evil.com/">'จะถูกปล่อยให้ผ่านโดยผู้ลอกแท็กทุกหน้าในหน้านี้ (ยกเว้น @Medeiros!) เพราะพวกเขายังไม่ได้ติดแท็กด้วยตนเอง การแยกแท็ก HTML ปกติออกไม่เพียงพอ

Django's strip_tagsเวอร์ชันที่ปรับปรุงแล้ว (ดูหัวข้อถัดไป) ของคำตอบยอดนิยมสำหรับคำถามนี้ให้คำเตือนต่อไปนี้:

ไม่มีการรับประกันใด ๆ เกี่ยวกับสตริงผลลัพธ์ที่ปลอดภัย HTML ดังนั้นไม่เคยทำเครื่องหมายปลอดภัยผลมาจากการที่โทรโดยไม่ต้องหลบหนีมันเป็นครั้งแรกเช่นกับstrip_tagsescape()

ทำตามคำแนะนำของพวกเขา!

หากต้องการตัดแท็กด้วย HTMLParser คุณต้องเรียกใช้หลายครั้ง

เป็นเรื่องง่ายที่จะหลีกเลี่ยงคำตอบแรกสุดสำหรับคำถามนี้

ดูสตริงนี้ (ที่มาและการสนทนา ):

<img<!-- --> src=x onerror=alert(1);//><!-- -->

ครั้งแรกที่ HTMLParser เห็นจะไม่สามารถบอกได้ว่า<img...>เป็นแท็ก มันดูเสียดังนั้น HTMLParser จึงไม่กำจัดมัน มันแค่เอาไป<!-- comments -->ทิ้งคุณไว้

<img src=x onerror=alert(1);//>

ปัญหานี้ได้รับการเปิดเผยต่อโครงการ Django ในเดือนมีนาคม 2014 โดยพื้นฐานของพวกเขาstrip_tagsนั้นเหมือนกับคำตอบแรกของคำถามนี้ เวอร์ชั่นใหม่ของพวกเขาจะรันเป็นลูปจนกว่าจะรันอีกครั้งจะไม่เปลี่ยนสตริง:

# _strip_once runs HTMLParser once, pulling out just the text of all the nodes.

def strip_tags(value):
    """Returns the given HTML with all tags stripped."""
    # Note: in typical case this loop executes _strip_once once. Loop condition
    # is redundant, but helps to reduce number of executions of _strip_once.
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value

แน่นอนว่าไม่มีสิ่งใดที่เป็นปัญหาหากคุณหลีกเลี่ยงผลของstrip_tags()มัน

อัปเดต 19 มีนาคม 2558 : มีข้อผิดพลาดในรุ่น Django ก่อน 1.4.20, 1.6.11, 1.7.7 และ 1.8c1 เวอร์ชันเหล่านี้สามารถป้อนการวนซ้ำไม่สิ้นสุดในฟังก์ชัน strip_tags () รุ่นที่คงที่จะทำซ้ำข้างต้น รายละเอียดเพิ่มเติมที่นี่

สิ่งที่ดีในการคัดลอกหรือใช้

โค้ดตัวอย่างของฉันไม่ได้จัดการเอนทิตี HTML - รุ่นแพคเกจ Django และ MarkupSafe ทำ

โค้ดตัวอย่างของฉันถูกดึงจากห้องสมุดMarkupSafe ที่ยอดเยี่ยมสำหรับการป้องกันการเขียนสคริปต์ข้ามไซต์ สะดวกและรวดเร็ว (ด้วยความเร็ว C ถึงรุ่น Python ดั้งเดิม) มันรวมอยู่ในGoogle App Engineและใช้โดยJinja2 (2.7 ขึ้นไป) , Mako, Pylons และอื่น ๆ มันทำงานได้อย่างง่ายดายด้วยแม่แบบ Django จาก Django 1.7

strip_tags ของ Django และยูทิลิตี้ html อื่น ๆจากเวอร์ชันล่าสุดนั้นดี แต่ฉันคิดว่ามันสะดวกกว่า MarkupSafe มันมีอยู่ในตัวคุณสามารถคัดลอกสิ่งที่คุณต้องการจากไฟล์นี้

หากคุณจำเป็นต้องตัดแถบเกือบทุกแท็กไลบรารีBleachนั้นดี คุณสามารถบังคับใช้กฎเช่น "ผู้ใช้ของฉันสามารถทำให้สิ่งเป็นตัวเอียง แต่พวกเขาไม่สามารถใช้ iframe

ทำความเข้าใจกับคุณสมบัติของผู้ลอกแท็กของคุณ! ใช้การทดสอบ fuzz กับมัน! นี่คือรหัสที่ฉันใช้ในการทำวิจัยสำหรับคำตอบนี้

เหน็บแนมโน้ต - คำถามนั้นเกี่ยวกับการพิมพ์ไปยังคอนโซล แต่นี่คือผลการค้นหาอันดับต้น ๆ ของ Google สำหรับ "python strip html from string" ดังนั้นนี่คือสาเหตุที่คำตอบนี้ 99% เกี่ยวกับเว็บ


โค้ดตัวอย่าง "บรรทัดสุดท้าย" ของฉันไม่ได้จัดการเอนทิตี html - มันแย่ขนาดไหน?
rescdsk

ฉันกำลังแยกวิเคราะห์ HTML เพียงเล็กน้อยโดยไม่มีแท็กพิเศษและเวอร์ชันสั้น ๆ ของคุณทำงานได้ดีมาก ขอบคุณสำหรับการแบ่งปัน!
tbolender

31

ฉันต้องการวิธีดึงแท็กและถอดรหัสเอนทิตี HTML เป็นข้อความธรรมดา วิธีการแก้ปัญหาต่อไปนี้ขึ้นอยู่กับคำตอบของ Eloff (ซึ่งฉันไม่สามารถใช้ได้เพราะมันดึงเอนทิตี)

from HTMLParser import HTMLParser
import htmlentitydefs

class HTMLTextExtractor(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        codepoint = htmlentitydefs.name2codepoint[name]
        self.result.append(unichr(codepoint))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

การทดสอบอย่างรวดเร็ว:

html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>'
print repr(html_to_text(html))

ผลลัพธ์:

u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

การจัดการข้อผิดพลาด:

  • โครงสร้าง HTML ไม่ถูกต้องอาจทำให้เกิดHTMLParseError
  • เอนทิตี HTML ที่มีชื่อไม่ถูกต้อง (เช่น&#apos;ซึ่งถูกต้องใน XML และ XHTML แต่ไม่ใช่ HTML ธรรมดา) จะทำให้เกิดValueErrorข้อยกเว้น
  • เอนทิตี HTML ที่เป็นตัวเลขที่ระบุรหัสจุดที่อยู่นอกช่วง Unicode ที่ยอมรับได้โดย Python (เช่นในบางระบบอักขระที่อยู่นอกBasic Multilingual Plane ) จะทำให้เกิดValueErrorข้อยกเว้น

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

ตัวอย่าง: &lt;script&gt;alert("Hello");&lt;/script&gt;จะถูกแปลงเป็น<script>alert("Hello");</script>ซึ่งเป็นพฤติกรรมที่ถูกต้อง 100% แต่เห็นได้ชัดว่าไม่เพียงพอหากข้อความธรรมดาที่เป็นผลลัพธ์ถูกแทรกตาม - อยู่ในหน้า HTML

กฎคือไม่ยาก: เมื่อใดก็ตามที่คุณใส่สตริงข้อความธรรมดาเข้าออก HTML คุณควรเสมอ HTML หนีมัน (ใช้cgi.escape(s, True)) แม้ว่าคุณจะ "รู้" ว่ามันไม่ได้มี HTML (เช่นเพราะคุณถอดเนื้อหา HTML) .

(อย่างไรก็ตาม OP ถามเกี่ยวกับการพิมพ์ผลลัพธ์ไปยังคอนโซลซึ่งในกรณีนี้ไม่จำเป็นต้องมีการหลบหนี HTML)

Python เวอร์ชัน 3.4+: (พร้อม doctest!)

import html.parser

class HTMLTextExtractor(html.parser.HTMLParser):
    def __init__(self):
        super(HTMLTextExtractor, self).__init__()
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def get_text(self):
        return ''.join(self.result)

def html_to_text(html):
    """Converts HTML to plain text (stripping tags and converting entities).
    >>> html_to_text('<a href="#">Demo<!--...--> <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>')
    'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

    "Plain text" doesn't mean result can safely be used as-is in HTML.
    >>> html_to_text('&lt;script&gt;alert("Hello");&lt;/script&gt;')
    '<script>alert("Hello");</script>'

    Always use html.escape to sanitize text before using in an HTML context!

    HTMLParser will do its best to make sense of invalid HTML.
    >>> html_to_text('x < y &lt z <!--b')
    'x < y < z '

    Unrecognized named entities are included as-is. '&apos;' is recognized,
    despite being XML only.
    >>> html_to_text('&nosuchentity; &apos; ')
    "&nosuchentity; ' "
    """
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

โปรดทราบว่า HTMLParser ได้รับการปรับปรุงใน Python 3 (หมายถึงรหัสน้อยลงและจัดการข้อผิดพลาดได้ดีขึ้น)


18

มีวิธีง่ายๆดังนี้:

def remove_html_markup(s):
    tag = False
    quote = False
    out = ""

    for c in s:
            if c == '<' and not quote:
                tag = True
            elif c == '>' and not quote:
                tag = False
            elif (c == '"' or c == "'") and tag:
                quote = not quote
            elif not tag:
                out = out + c

    return out

มีการอธิบายแนวคิดที่นี่: http://youtu.be/2tu9LTDujbw

คุณสามารถเห็นมันทำงานที่นี่: http://youtu.be/HPkNPcYed9M?t=35s

PS - หากคุณสนใจในชั้นเรียน (ประมาณแก้จุดบกพร่องมาร์ทกับงูหลาม) ผมให้คุณเชื่อมโยง: http://www.udacity.com/overview/Course/cs259/CourseRev/1 แจกฟรี!

ยินดีต้อนรับคุณ! :)


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

2
อาจเป็นคนชอบ libs เพื่อให้พวกเขาปลอดภัย ฉันทดสอบโค้ดของคุณและผ่านไปแล้วและฉันชอบโค้ดขนาดเล็กที่ฉันเข้าใจมากกว่าการใช้ lib และสมมติว่ามันใช้ได้จนกระทั่งมีบั๊กปรากฏขึ้น สำหรับฉันนั่นคือสิ่งที่ฉันกำลังมองหาและขอขอบคุณอีกครั้ง เกี่ยวกับ downvotes อย่าเข้ามาในความคิดนั้น ผู้คนที่นี่ควรใส่ใจคุณภาพและไม่ลงคะแนน เมื่อเร็ว ๆ นี้ได้กลายเป็นสถานที่ที่ทุกคนต้องการคะแนนและไม่ใช่ความรู้
Jimmy Kane

2
ปัญหาเกี่ยวกับวิธีแก้ไขปัญหานี้คือการจัดการข้อผิดพลาด ตัวอย่างเช่นถ้าคุณให้เป็นเอาท์พุทฟังก์ชั่นการป้อนข้อมูล<b class="o'>x</b> xแต่จริงๆแล้วการป้อนข้อมูลนี้ไม่ถูกต้อง ฉันคิดว่านั่นเป็นเหตุผลที่ผู้คนชอบ libs
laltin

1
มันทำงานกับอินพุตนั้นด้วย เพิ่งทดสอบ เพิ่งรู้ว่าในห้องสมุดเหล่านั้นคุณจะพบรหัสที่คล้ายกัน ฉันรู้ว่ามันไม่ได้เป็น pythonic มาก ดูเหมือนรหัส C หรือ Java ฉันคิดว่ามันมีประสิทธิภาพและสามารถนำไปใช้กับภาษาอื่นได้อย่างง่ายดาย
Medeiros

1
เรียบง่าย Pythonic และดูเหมือนว่าจะทำงานได้ดีหรือดีกว่าวิธีอื่น ๆ ที่กล่าวถึง เป็นไปได้ว่ามันจะไม่ทำงานสำหรับ HTML ที่มีรูปแบบไม่ดี แต่ไม่มีการเอาชนะ
denson

16

หากคุณต้องการที่จะรักษาหน่วยงาน HTML (เช่น&amp;) ฉันเพิ่ม "handle_entityref" วิธีการที่จะตอบ Eloff ของ

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def handle_entityref(self, name):
        self.fed.append('&%s;' % name)
    def get_data(self):
        return ''.join(self.fed)

def html_to_text(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

13

หากคุณต้องการตัดแท็ก HTML ทั้งหมดวิธีที่ง่ายที่สุดที่ฉันพบคือใช้ BeautifulSoup:

from bs4 import BeautifulSoup  # Or from BeautifulSoup import BeautifulSoup

def stripHtmlTags(htmlTxt):
    if htmlTxt is None:
            return None
        else:
            return ''.join(BeautifulSoup(htmlTxt).findAll(text=True)) 

ฉันลองใช้รหัสของคำตอบที่ยอมรับ แต่ฉันได้รับ "RuntimeError: ความลึกของการเรียกซ้ำสูงสุดเกิน" ซึ่งไม่ได้เกิดขึ้นกับบล็อกด้านบนของรหัส


1
ฉันลองใช้วิธีการของคุณเพราะมันดูสะอาดกว่าใช้งานได้ดี ... มันไม่ได้ตัดแท็กอินพุต!
kustomrtr

ผมพบว่าโปรแกรมที่ง่ายของ BeautifulSoup ''.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True))มีปัญหากับช่องว่างนี้: ผลลัพธ์คือ "helloworld" ในขณะที่คุณอาจต้องการให้มันเป็น "hello world" ' '.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True))ไม่ช่วยเพราะมันกลายเป็น "เขาโลก"
ฟินน์Årup Nielsen

@ customrtr ขอโทษที่ฉันไม่รู้ฉันจะใส่อะไรลงไปในการโต้เถียงตัวเอง? NameError: ไม่ได้กำหนดชื่อ 'self'
Ian_De_Oliveira

@Ian_De_Oliveira คุณสามารถลบมันได้ฉันคิดว่ามันอยู่ในชั้นเรียน แต่ไม่จำเป็น ฉันยังแก้ไขคำตอบเพื่อลบมัน
Vasilis

@Ian_De_Oliveira คุณสามารถลบมันได้ฉันคิดว่ามันอยู่ในชั้นเรียน แต่ไม่จำเป็น ฉันยังแก้ไขคำตอบเพื่อลบมัน
Vasilis

10

นี่คือวิธีแก้ปัญหาง่ายๆที่แยกแท็ก HTML และถอดรหัสเอนทิตี HTML ที่อิงกับไลบรารี่ที่รวดเร็วอย่างน่าอัศจรรย์lxml:

from lxml import html

def strip_html(s):
    return str(html.fromstring(s).text_content())

strip_html('Ein <a href="">sch&ouml;ner</a> Text.')  # Output: Ein schöner Text.

3
ในปี 2020 นี่เป็นวิธีที่เร็วและดีที่สุดในการตัดเนื้อหาของ HTML บวกกับโบนัสในการจัดการการถอดรหัส เหมาะสำหรับการตรวจจับภาษา!
dfabiano

text_content()ส่งคืนlxml.etree._ElementUnicodeResultดังนั้นคุณอาจต้องส่งไปที่สตริงก่อน
Suzana

1
@Suzana จุดที่ดี มันดูเหมือนว่าจะได้รับอัตโนมัติหล่อไปstrสำหรับการดำเนินงานสายเช่นและการจัดทำดัชนี+ []เพิ่มการร่ายเวทย์เพื่อการวัดที่ดี แต่อย่างใด
Robin Dinse

9

lxml.htmlแก้ปัญหาชั่น (lxml เป็นห้องสมุดพื้นเมืองและดังนั้นจึงเร็วกว่าวิธีการแก้ปัญหาใด ๆ หลามบริสุทธิ์)

from lxml import html
from lxml.html.clean import clean_html

tree = html.fromstring("""<span class="item-summary">
                            Detailed answers to any questions you might have
                        </span>""")

print(clean_html(tree).strip())

# >>> Detailed answers to any questions you might have

โปรดดูhttp://lxml.de/lxmlhtml.html#cleaning-up-htmlสำหรับสิ่งที่ lxml.cleaner ทำ

หากคุณต้องการควบคุมสิ่งที่ถูกทำให้บริสุทธิ์ก่อนแปลงเป็นข้อความคุณอาจต้องการใช้lxml Cleanerอย่างชัดเจนโดยผ่านตัวเลือกที่คุณต้องการใน Constructor เช่น:

cleaner = Cleaner(page_structure=True,
                  meta=True,
                  embedded=True,
                  links=True,
                  style=True,
                  processing_instructions=True,
                  inline_style=True,
                  scripts=True,
                  javascript=True,
                  comments=True,
                  frames=True,
                  forms=True,
                  annoying_tags=True,
                  remove_unknown_tags=True,
                  safe_attrs_only=True,
                  safe_attrs=frozenset(['src','color', 'href', 'title', 'class', 'name', 'id']),
                  remove_tags=('span', 'font', 'div')
                  )
sanitized_html = cleaner.clean_html(unsafe_html)

1
ผมได้ AttributeError: 'HtmlElement' วัตถุมีแอตทริบิวต์ไม่มี 'แถบ'
aris

7

แพคเกจ Beautiful Soup ทำทันทีสำหรับคุณ

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)

3
จากคิวการตรวจทาน:ฉันขอให้คุณเพิ่มบริบทเพิ่มเติมเกี่ยวกับคำตอบของคุณได้ไหม คำตอบแบบรหัสเท่านั้นยากที่จะเข้าใจ มันจะช่วยผู้ถามและผู้อ่านในอนาคตทั้งสองหากคุณสามารถเพิ่มข้อมูลเพิ่มเติมในโพสต์ของคุณ
help-info.de

2

นี่คือทางออกของฉันสำหรับ python 3

import html
import re

def html_to_txt(html_text):
    ## unescape html
    txt = html.unescape(html_text)
    tags = re.findall("<[^>]+>",txt)
    print("found tags: ")
    print(tags)
    for tag in tags:
        txt=txt.replace(tag,'')
    return txt

ไม่แน่ใจว่ามันสมบูรณ์แบบหรือไม่ แต่แก้ไขกรณีการใช้งานของฉันและดูเรียบง่าย


2

คุณสามารถใช้ตัวแยกวิเคราะห์ HTML ที่แตกต่างกัน ( เช่น lxmlหรือBeautiful Soup ) ซึ่งเป็นฟังก์ชันที่แยกเฉพาะข้อความ หรือคุณสามารถเรียกใช้ regex ในสายอักขระของคุณที่ดึงแท็กออก ดูเอกสารงูใหญ่มาก


1
ลิงค์ amk นั้นตายแล้ว มีทางเลือกอื่นหรือไม่?

2
เว็บไซต์ Python มีวิธีการใช้งานที่ดีในตอนนี้นี่คือวิธีการใช้ regex: docs.python.org/howto/regex
Jason Coon

5
ใน lxml:lxml.html.fromstring(s).text_content()
Bluu

1
ตัวอย่างของ Bluu ที่มี lxml จะถอดรหัสเอนทิตี HTML (เช่น&amp;) เป็นข้อความ
SørenLøvborg

1

ฉันใช้คำตอบของ Eloff เรียบร้อยแล้วสำหรับ Python 3.1 [ขอบคุณมาก ๆ !]

ฉันอัพเกรดเป็น Python 3.2.3 และพบข้อผิดพลาด

วิธีการแก้ปัญหาที่มีให้ที่นี่ต้องขอบคุณโทมัส K ตอบกลับคือการแทรกsuper().__init__()ลงในรหัสต่อไปนี้:

def __init__(self):
    self.reset()
    self.fed = []

... เพื่อให้หน้าตาเป็นแบบนี้:

def __init__(self):
    super().__init__()
    self.reset()
    self.fed = []

... และมันจะใช้งานได้กับ Python 3.2.3

ขอขอบคุณ Thomas K อีกครั้งสำหรับการแก้ไขและสำหรับรหัสดั้งเดิมของ Eloff ที่ระบุไว้ด้านบน!


1

คุณสามารถเขียนฟังก์ชั่นของคุณเอง:

def StripTags(text):
     finished = 0
     while not finished:
         finished = 1
         start = text.find("<")
         if start >= 0:
             stop = text[start:].find(">")
             if stop >= 0:
                 text = text[:start] + text[start+stop+1:]
                 finished = 0
     return text

1
การต่อท้ายสตริงสร้างสำเนาใหม่ของสตริงหรือไม่?
Jeremy L

1
@Nerdling - ใช่แล้วมันสามารถนำไปสู่ความไร้ประสิทธิภาพที่ค่อนข้างน่าประทับใจในฟังก์ชั่นที่ใช้บ่อย (หรือสำหรับเรื่องนั้นฟังก์ชั่นที่ใช้งานไม่บ่อยนักซึ่งทำหน้าที่กับข้อความขนาดใหญ่) ดูหน้านี้สำหรับรายละเอียด : D
Jeremy Sandell

มันทดสอบกับสตริงที่ยกมา? ไม่
จิมมี่เคน

1

โซลูชันที่ใช้ HTML-Parser นั้นสามารถแตกหักได้ทั้งหมดหากใช้งานเพียงครั้งเดียว:

html_to_text('<<b>script>alert("hacked")<</b>/script>

ผลลัพธ์ใน:

<script>alert("hacked")</script>

สิ่งที่คุณตั้งใจจะป้องกัน หากคุณใช้ HTML-Parser ให้นับแท็กจนกระทั่งศูนย์ถูกแทนที่:

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
        self.containstags = False

    def handle_starttag(self, tag, attrs):
       self.containstags = True

    def handle_data(self, d):
        self.fed.append(d)

    def has_tags(self):
        return self.containstags

    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    must_filtered = True
    while ( must_filtered ):
        s = MLStripper()
        s.feed(html)
        html = s.get_data()
        must_filtered = s.has_tags()
    return html

1
หากคุณเรียกใช้ฟังก์ชันที่เรียกว่าhtml_to_textและคุณฝังข้อความที่กำลังถูกส่งออกจากฟังก์ชั่นนั้นใน html โดยไม่ต้องหลบหนีข้อความนั้นนั่นคือการขาดการหลบหนีซึ่งเป็นช่องโหว่ด้านความปลอดภัยไม่ใช่html_to_textฟังก์ชั่น html_to_textฟังก์ชั่นไม่เคยสัญญาว่าผลผลิตจะเป็นข้อความ และการแทรกข้อความลงใน html โดยไม่ต้องหลบหนีเป็นช่องโหว่ด้านความปลอดภัยที่อาจเกิดขึ้นไม่ว่าคุณจะได้รับข้อความจากhtml_to_text แหล่งอื่นหรือไม่ก็ตาม
kasperd

คุณอยู่ในกรณีของการขาดการหลบหนี แต่คำถามคือการตัด html จากสตริงที่กำหนดไม่ให้หนีจากสตริงที่กำหนด หากคำตอบก่อนหน้านี้สร้าง html ใหม่ด้วยโซลูชันของพวกเขาอันเป็นผลมาจากการลบ html บางส่วนการใช้โซลูชันนี้เป็นอันตราย
Falk Nisius

1

นี่เป็นการแก้ไขด่วนและสามารถปรับให้เหมาะสมยิ่งขึ้น แต่จะใช้งานได้ดี รหัสนี้จะแทนที่แท็กที่ไม่ว่างเปล่าทั้งหมดด้วย "" และตัดแท็ก html ทั้งหมดในรูปแบบข้อความป้อนข้อมูลที่กำหนดคุณสามารถเรียกใช้ได้โดยใช้. /file.py เอาต์พุตอินพุต

    #!/usr/bin/python
import sys

def replace(strng,replaceText):
    rpl = 0
    while rpl > -1:
        rpl = strng.find(replaceText)
        if rpl != -1:
            strng = strng[0:rpl] + strng[rpl + len(replaceText):]
    return strng


lessThanPos = -1
count = 0
listOf = []

try:
    #write File
    writeto = open(sys.argv[2],'w')

    #read file and store it in list
    f = open(sys.argv[1],'r')
    for readLine in f.readlines():
        listOf.append(readLine)         
    f.close()

    #remove all tags  
    for line in listOf:
        count = 0;  
        lessThanPos = -1  
        lineTemp =  line

            for char in lineTemp:

            if char == "<":
                lessThanPos = count
            if char == ">":
                if lessThanPos > -1:
                    if line[lessThanPos:count + 1] != '<>':
                        lineTemp = replace(lineTemp,line[lessThanPos:count + 1])
                        lessThanPos = -1
            count = count + 1
        lineTemp = lineTemp.replace("&lt","<")
        lineTemp = lineTemp.replace("&gt",">")                  
        writeto.write(lineTemp)  
    writeto.close() 
    print "Write To --- >" , sys.argv[2]
except:
    print "Help: invalid arguments or exception"
    print "Usage : ",sys.argv[0]," inputfile outputfile"

1

หลาม 3 ดัดแปลงคำตอบของsøren-løvborg

from html.parser import HTMLParser
from html.entities import html5

class HTMLTextExtractor(HTMLParser):
    """ Adaption of http://stackoverflow.com/a/7778368/196732 """
    def __init__(self):
        super().__init__()
        self.result = []

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        if name in html5:
            self.result.append(unichr(html5[name]))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

1

สำหรับโครงการหนึ่งฉันจำเป็นต้องตัดแถบ HTML แต่ก็ต้องใช้ css และ js ด้วย ดังนั้นฉันจึงตอบ Eloffs หลายรูปแบบ:

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.fed = []
        self.css = False
    def handle_starttag(self, tag, attrs):
        if tag == "style" or tag=="script":
            self.css = True
    def handle_endtag(self, tag):
        if tag=="style" or tag=="script":
            self.css=False
    def handle_data(self, d):
        if not self.css:
            self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

1

นี่คือวิธีการแก้ปัญหาที่คล้ายกับคำตอบที่ยอมรับในปัจจุบัน ( https://stackoverflow.com/a/925630/95989 ) ยกเว้นว่าจะใช้HTMLParserคลาสภายในโดยตรง (เช่นไม่มีคลาสย่อย) จึงทำให้มีความหมายสั้นกว่า:

def strip_html (ข้อความ):
    ส่วน = []                                                                      
    parser = HTMLParser ()                                                           
    parser.handle_data = parts.append                                               
    parser.feed (ข้อความ)                                                               
    return '' .join (ชิ้นส่วน)

0

ฉันกำลังแยกวิเคราะห์ readith ของ Github และฉันพบว่าสิ่งต่อไปนี้ใช้ได้ดีจริงๆ:

import re
import lxml.html

def strip_markdown(x):
    links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x)
    bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub)
    emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub)
    return emph_sub

def strip_html(x):
    return lxml.html.fromstring(x).text_content() if x else ''

และจากนั้น

readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" />

            sky is a web scraping framework, implemented with the latest python versions in mind (3.4+). 
            It uses the asynchronous `asyncio` framework, as well as many popular modules 
            and extensions.

            Most importantly, it aims for **next generation** web crawling where machine intelligence 
            is used to speed up the development/maintainance/reliability of crawling.

            It mainly does this by considering the user to be interested in content 
            from *domains*, not just a collection of *single pages*
            ([templating approach](#templating-approach))."""

strip_markdown(strip_html(readme))

ลบ markdown และ html ทั้งหมดออกอย่างถูกต้อง


0

การใช้ BeautifulSoup, html2text หรือรหัสจาก @Eloff ส่วนใหญ่จะยังคงเป็นองค์ประกอบ HTML, รหัส javascript ...

ดังนั้นคุณสามารถใช้การรวมกันของไลบรารีเหล่านี้และลบการจัดรูปแบบมาร์กดาวน์ (Python 3):

import re
import html2text
from bs4 import BeautifulSoup
def html2Text(html):
    def removeMarkdown(text):
        for current in ["^[ #*]{2,30}", "^[ ]{0,30}\d\\\.", "^[ ]{0,30}\d\."]:
            markdown = re.compile(current, flags=re.MULTILINE)
            text = markdown.sub(" ", text)
        return text
    def removeAngular(text):
        angular = re.compile("[{][|].{2,40}[|][}]|[{][*].{2,40}[*][}]|[{][{].{2,40}[}][}]|\[\[.{2,40}\]\]")
        text = angular.sub(" ", text)
        return text
    h = html2text.HTML2Text()
    h.images_to_alt = True
    h.ignore_links = True
    h.ignore_emphasis = False
    h.skip_internal_links = True
    text = h.handle(html)
    soup = BeautifulSoup(text, "html.parser")
    text = soup.text
    text = removeAngular(text)
    text = removeMarkdown(text)
    return text

มันทำงานได้ดีสำหรับฉัน แต่มันสามารถปรับปรุงได้แน่นอน ...


0

รหัสง่าย ๆ การดำเนินการนี้จะลบแท็กและเนื้อหาทุกประเภทที่อยู่ภายใน

def rm(s):
    start=False
    end=False
    s=' '+s
    for i in range(len(s)-1):
        if i<len(s):
            if start!=False:
                if s[i]=='>':
                    end=i
                    s=s[:start]+s[end+1:]
                    start=end=False
            else:
                if s[i]=='<':
                    start=i
    if s.count('<')>0:
        self.rm(s)
    else:
        s=s.replace('&nbsp;', ' ')
        return s

แต่จะไม่ให้ผลเต็มที่ถ้าข้อความมีสัญลักษณ์<>อยู่ข้างใน


0
# This is a regex solution.
import re
def removeHtml(html):
  if not html: return html
  # Remove comments first
  innerText = re.compile('<!--[\s\S]*?-->').sub('',html)
  while innerText.find('>')>=0: # Loop through nested Tags
    text = re.compile('<[^<>]+?>').sub('',innerText)
    if text == innerText:
      break
    innerText = text

  return innerText.strip()

-2

วิธีนี้ใช้ได้อย่างไม่มีที่ติสำหรับฉันและไม่ต้องการการติดตั้งเพิ่มเติม:

import re
import htmlentitydefs

def convertentity(m):
    if m.group(1)=='#':
        try:
            return unichr(int(m.group(2)))
        except ValueError:
            return '&#%s;' % m.group(2)
        try:
            return htmlentitydefs.entitydefs[m.group(2)]
        except KeyError:
            return '&%s;' % m.group(2)

def converthtml(s):
    return re.sub(r'&(#?)(.+?);',convertentity,s)

html =  converthtml(html)
html.replace("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).

3
สิ่งนี้จะถอดรหัสเอนทิตี HTML เป็นข้อความธรรมดา แต่แน่นอนว่าไม่ได้ตัดแท็กใด ๆ ซึ่งเป็นคำถามเดิม (นอกจากนี้บล็อกการลองใช้ข้อที่สองจะต้องไม่ถูกย่อหน้าเพื่อให้โค้ดทำงานได้มาก)
SørenLøvborg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.