【问题标题】:BeautifulSoup Grab Visible Webpage TextBeautifulSoup 抓取可见网页文本
【发布时间】:2010-12-28 12:46:24
【问题描述】:

基本上,我想使用 BeautifulSoup 来严格抓取网页上的可见文本。例如,this webpage 是我的测试用例。而且我主要想在这里和那里获取正文(文章)甚至一些选项卡名称。我已经尝试了这个SO question 中的建议,它返回了很多我不想要的<script> 标签和html cmets。我无法弄清楚函数 findAll() 需要的参数才能仅获取网页上的可见文本。

那么,我应该如何找到除脚本、cmets、css 等之外的所有可见文本?

【问题讨论】:

    标签: python text beautifulsoup html-content-extraction


    【解决方案1】:

    试试这个:

    from bs4 import BeautifulSoup
    from bs4.element import Comment
    import urllib.request
    
    
    def tag_visible(element):
        if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
            return False
        if isinstance(element, Comment):
            return False
        return True
    
    
    def text_from_html(body):
        soup = BeautifulSoup(body, 'html.parser')
        texts = soup.findAll(text=True)
        visible_texts = filter(tag_visible, texts)  
        return u" ".join(t.strip() for t in visible_texts)
    
    html = urllib.request.urlopen('http://www.nytimes.com/2009/12/21/us/21storm.html').read()
    print(text_from_html(html))
    

    【讨论】:

    • +1 for soup.findAll(text=True) 从来不知道这个功能
    • 对于最近的 BS4(至少),您可以使用 isinstance(element, Comment) 识别 cmets,而不是匹配正则表达式。
    • 我相信第2行应该是soup = BeautifulSoup(html)
    • 在可见函数中,查找 cmets 的 elif 似乎不起作用。我不得不将其更新为elif isinstance(element,bs4.element.Comment):。我还在父母列表中添加了“元”。
    • 上述过滤器的结果中有很多\n,添加以下代码消除空格和新行:elif re.match(r"[\s\r\n]+",str(element)): return False
    【解决方案2】:

    @jbochi 批准的答案对我不起作用。 str() 函数调用引发异常,因为它无法对 BeautifulSoup 元素中的非 ascii 字符进行编码。这是将示例网页过滤为可见文本的更简洁的方法。

    html = open('21storm.html').read()
    soup = BeautifulSoup(html)
    [s.extract() for s in soup(['style', 'script', '[document]', 'head', 'title'])]
    visible_text = soup.getText()
    

    【讨论】:

    • 如果 str(element) 因编码问题而失败,如果您使用的是 Python 2,则应尝试使用 unicode(element)
    【解决方案3】:
    import urllib
    from bs4 import BeautifulSoup
    
    url = "https://www.yahoo.com"
    html = urllib.urlopen(url).read()
    soup = BeautifulSoup(html)
    
    # kill all script and style elements
    for script in soup(["script", "style"]):
        script.extract()    # rip it out
    
    # get text
    text = soup.get_text()
    
    # break into lines and remove leading and trailing space on each
    lines = (line.strip() for line in text.splitlines())
    # break multi-headlines into a line each
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    # drop blank lines
    text = '\n'.join(chunk for chunk in chunks if chunk)
    
    print(text.encode('utf-8'))
    

    【讨论】:

    • 以前的答案对我不起作用,但这样做:)
    • 如果我在 url imfuna.com 上尝试这个,它只返回 6 个单词(Imfuna Property Inventory and Inspection Apps),尽管页面上有更多的文本/单词......任何想法为什么这个答案不适用于那个网址? @bumpkin
    • 有没有办法用\n换行符替换<br>标签?
    【解决方案4】:

    我完全尊重使用 Beautiful Soup 来获取渲染内容,但它可能不是获取页面上渲染内容的理想包。

    我在获取渲染内容或典型浏览器中的可见内容时遇到了类似的问题。特别是我有许多可能不典型的案例来处理下面这样一个简单的例子。在这种情况下,不可显示的标签嵌套在样式标签中,并且在我检查过的许多浏览器中不可见。存在其他变体,例如将类标记设置显示定义为无。然后将此类用于 div。

    <html>
      <title>  Title here</title>
    
      <body>
    
        lots of text here <p> <br>
        <h1> even headings </h1>
    
        <style type="text/css"> 
            <div > this will not be visible </div> 
        </style>
    
    
      </body>
    
    </html>
    

    上面发布的一个解决方案是:

    html = Utilities.ReadFile('simple.html')
    soup = BeautifulSoup.BeautifulSoup(html)
    texts = soup.findAll(text=True)
    visible_texts = filter(visible, texts)
    print(visible_texts)
    
    
    [u'\n', u'\n', u'\n\n        lots of text here ', u' ', u'\n', u' even headings ', u'\n', u' this will not be visible ', u'\n', u'\n']
    

    这个解决方案在很多情况下确实有应用,并且通常可以很好地完成工作,但是在上面发布的 html 中,它保留了未呈现的文本。在搜索了一些解决方案之后,这里出现了 BeautifulSoup get_text does not strip all tags and JavaScriptRendered HTML to plain text using Python

    我尝试了这两种解决方案:html2text 和 nltk.clean_html,并对计时结果感到惊讶,因此认为它们值得为后代提供答案。当然,速度很大程度上取决于数据的内容......

    @Helge 的一个答案是关于使用所有事物的 nltk。

    import nltk
    
    %timeit nltk.clean_html(html)
    was returning 153 us per loop
    

    返回带有渲染 html 的字符串非常有效。这个 nltk 模块甚至比 html2text 更快,尽管 html2text 可能更健壮。

    betterHTML = html.decode(errors='ignore')
    %timeit html2text.html2text(betterHTML)
    %3.09 ms per loop
    

    【讨论】:

    【解决方案5】:

    使用 BeautifulSoup 最简单的方法,用更少的代码来获取字符串,没有空行和废话。

    tag = <Parent_Tag_that_contains_the_data>
    soup = BeautifulSoup(tag, 'html.parser')
    
    for i in soup.stripped_strings:
        print repr(i)
    

    【讨论】:

    • 迄今为止最好和最前卫的答案,谢谢!
    • 请注意,但是 stripped_strings 将包含页面上未呈现的页面标题。
    • 进一步阅读,我看到@polor beer 的解决方案使用了 stripped_strings 但更正了页面标题。
    【解决方案6】:

    如果您关心性能,这是另一种更有效的方法:

    import re
    
    INVISIBLE_ELEMS = ('style', 'script', 'head', 'title')
    RE_SPACES = re.compile(r'\s{3,}')
    
    def visible_texts(soup):
        """ get visible text from a document """
        text = ' '.join([
            s for s in soup.strings
            if s.parent.name not in INVISIBLE_ELEMS
        ])
        # collapse multiple spaces to two spaces.
        return RE_SPACES.sub('  ', text)
    

    soup.strings是一个迭代器,它会返回NavigableString,这样就可以直接查看父标签的名字,不用经过多次循环。

    【讨论】:

      【解决方案7】:

      虽然我完全建议总体上使用 beautiful-soup,但如果有人出于任何原因希望显示格式错误的 html 的可见部分(例如,您只有网页的一段或一行),以下将删除&lt;&gt; 标签之间的内容:

      import re   ## only use with malformed html - this is not efficient
      def display_visible_html_using_re(text):             
          return(re.sub("(\<.*?\>)", "",text))
      

      【讨论】:

        【解决方案8】:

        标题位于&lt;nyt_headline&gt; 标记内,该标记嵌套在&lt;h1&gt; 标记和ID 为“article”的&lt;div&gt; 标记内。

        soup.findAll('nyt_headline', limit=1)
        

        应该可以。

        文章正文位于&lt;nyt_text&gt; 标记内,该标记嵌套在ID 为“articleBody”的&lt;div&gt; 标记内。在&lt;nyt_text&gt; 元素内,文本本身包含在&lt;p&gt; 标记中。图片不在&lt;p&gt; 标签内。我很难尝试这种语法,但我希望一个有效的抓取看起来像这样。

        text = soup.findAll('nyt_text', limit=1)[0]
        text.findAll('p')
        

        【讨论】:

        • 我确信这适用于这个测试用例,但是,寻找一个更通用的答案,可以应用于各种其他网站......到目前为止,我已经尝试使用正则表达式来查找 标签和 cmets 并用 "" 替换它们,但由于总和原因,这甚至证明有点困难..
        【解决方案9】:

        处理这种情况的最简单方法是使用getattr()。您可以根据需要调整此示例:

        from bs4 import BeautifulSoup
        
        source_html = """
        <span class="ratingsDisplay">
            <a class="ratingNumber" href="https://www.youtube.com/watch?v=oHg5SJYRHA0" target="_blank" rel="noopener">
                <span class="ratingsContent">3.7</span>
            </a>
        </span>
        """
        
        soup = BeautifulSoup(source_html, "lxml")
        my_ratings = getattr(soup.find('span', {"class": "ratingsContent"}), "text", None)
        print(my_ratings)
        

        这将在标记对象&lt;span class="ratingsContent"&gt;3.7&lt;/span&gt; 中找到文本元素"3.7",如果它存在,则默认为NoneType

        getattr(object, name[, default])

        返回对象的命名属性的值。名称必须是字符串。如果字符串是对象属性之一的名称,则结果是该属性的值。例如,getattr(x, 'foobar') 等价于 x.foobar。如果命名属性不存在,如果提供则返回默认值,否则引发 AttributeError。

        【讨论】:

          【解决方案10】:
          from bs4 import BeautifulSoup
          from bs4.element import Comment
          import urllib.request
          import re
          import ssl
          
          def tag_visible(element):
              if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
                  return False
              if isinstance(element, Comment):
                  return False
              if re.match(r"[\n]+",str(element)): return False
              return True
          def text_from_html(url):
              body = urllib.request.urlopen(url,context=ssl._create_unverified_context()).read()
              soup = BeautifulSoup(body ,"lxml")
              texts = soup.findAll(text=True)
              visible_texts = filter(tag_visible, texts)  
              text = u",".join(t.strip() for t in visible_texts)
              text = text.lstrip().rstrip()
              text = text.split(',')
              clean_text = ''
              for sen in text:
                  if sen:
                      sen = sen.rstrip().lstrip()
                      clean_text += sen+','
              return clean_text
          url = 'http://www.nytimes.com/2009/12/21/us/21storm.html'
          print(text_from_html(url))
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-25
            • 2014-06-20
            • 1970-01-01
            相关资源
            最近更新 更多