BeautifulSoup Grab Visible Webpage Text


124

기본적으로 BeautifulSoup을 사용 하여 웹 페이지에 표시되는 텍스트 를 엄격하게 잡으려고합니다 . 예를 들어, 이 웹 페이지 는 제 테스트 케이스입니다. 그리고 주로 본문 텍스트 (기사)와 여기저기서 탭 이름 몇 개만 가져오고 싶습니다. 나는 내가 원하지 않는 많은 태그와 html 주석 을 반환하는 이 SO 질문 에서 제안을 시도했습니다 <script>. findAll()웹 페이지에서 보이는 텍스트를 얻기 위해 함수 에 필요한 인수를 알아낼 수 없습니다 .

그렇다면 스크립트, 주석, CSS 등을 제외한 모든 보이는 텍스트를 어떻게 찾아야합니까?

답변:


239

이 시도:

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))

47
soup.findAll(text=True)그 기능에 대해 전혀 몰랐던 +1
Hartley Brody

7
최근 BS4의 경우 (적어도) isinstance(element, Comment)정규식과 일치하는 대신 주석을 식별 할 수 있습니다.
tripleee

5
2 번 줄이되어야한다고 생각합니다soup = BeautifulSoup(html)
jczaplew 2014

11
보이는 기능에서 주석을 찾기위한 elif가 작동하지 않는 것 같습니다. 나는 그것을 elif isinstance(element,bs4.element.Comment):. 나는 또한 부모 목록에 '메타'를 추가했습니다.
Russ Savage 2015

4
위의 필터는 결과에 많은 \ n이 있습니다. 공백과 새 줄을 제거하려면 다음 코드를 추가하십시오. elif re.match(r"[\s\r\n]+",str(element)): return False
天才 小飞 猫

37

@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()

1
경우 str(element)인코딩 문제로 실패, 당신은 시도해야 unicode(element)파이썬 2를 사용하는 경우 대신
mknaf

31
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'))

4
이전의 대답은 나를 위해 작동하지 않았다,하지만이 : 한
rjurney

URL imfuna.com에서 이것을 시도하면 페이지에 훨씬 더 많은 텍스트 / 단어가 있음에도 불구하고 6 단어 (Imfuna Property Inventory and Inspection Apps) 만 반환됩니다 ...이 답변이 작동하지 않는 이유에 대한 아이디어 URL? @bumpkin
the_t_test_1

10

나는 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에서는 렌더링되지 않은 텍스트를 유지합니다. SO 검색 한 후 몇 가지 솔루션은 여기 와서 BeautifulSoup로의 get_text 모든 태그와 자바 스크립트를 제거하지 않습니다 여기에 파이썬을 사용하여 일반 텍스트로 렌더링 된 HTML을

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

3

성능에 관심이 있다면 더 효율적인 방법이 있습니다.

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여러 루프를 거치지 않고 부모의 태그 이름을 직접 확인할 수 있도록 반환 됩니다.


2

제목은 내부에 <nyt_headline>내부 중첩 태그, <h1>태그와 <div>ID가 "기사"와 태그입니다.

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

작동해야합니다.

기사 본문은 <nyt_text>태그 안에 <div>있으며 ID가 "articleBody"인 태그 안에 중첩되어 있습니다. <nyt_text> 요소 내부에는 텍스트 자체가 <p> 태그 내에 포함됩니다 . 이미지는 해당 <p>태그 내에 없습니다 . 구문을 실험하는 것은 어렵지만 작업 스크랩이 이와 같이 보일 것으로 예상합니다.

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

나는 이것이이 테스트 케이스에서 작동한다고 확신하지만, 다양한 다른 웹 사이트에 적용될 수있는보다 일반적인 대답을 찾고 있습니다. 지금까지 저는 정규 표현식을 사용하여 <script> </ script> 태그와 < !-. *-> 주석을 달고 ""로
바꿉니다

2

어떤 이유로 든 잘못된 HTML의 보이는 부분 (예 : 웹 페이지의 한 부분이나 줄만있는 경우)을 표시하려는 경우 일반적으로 beautiful-soup을 사용하는 것이 좋습니다. 다음은 다음과 같습니다. <>태그 사이의 콘텐츠를 제거 합니다.

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

2

BeautifulSoup을 사용하면 더 적은 코드로 빈 줄과 쓰레기없이 문자열을 얻을 수있는 가장 쉬운 방법입니다.

tag = <Parent_Tag_that_contains_the_data>
soup = BeautifulSoup(tag, 'html.parser')

for i in soup.stripped_strings:
    print repr(i)

0

이 경우를 처리하는 가장 간단한 방법은 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)

"3.7"태그 객체가있는 <span class="ratingsContent">3.7</span>경우 태그 객체 내 에서 텍스트 요소를 찾지 만없는 경우 기본값으로 설정됩니다 NoneType.

getattr(object, name[, default])

개체의 명명 된 속성 값을 반환합니다. 이름은 문자열이어야합니다. 문자열이 객체 속성 중 하나의 이름이면 결과는 해당 속성의 값입니다. 예를 들어, getattr (x, 'foobar')는 x.foobar와 동일합니다. 명명 된 속성이 존재하지 않는 경우 제공되는 경우 기본값이 반환되고 그렇지 않은 경우 AttributeError가 발생합니다.


0
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))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.