본문 바로가기

엔지니어/Python

python html 파싱

728x90
반응형
 
HTML 파싱할 일이 생겼는데,
그동안은 그냥그냥 필요한 내용만 crummy에서 짬짬히 보다가,
BeautifulSoup을 한국말로 잘 정리한 사이트를 찾았다.
 
 
예를 들자면, BeautifulSoup을 이용해서 특정 홈피의 내용 중, 어느 부분은 읽는다면 다음과 같이 간단하게 끝낼수 있을 것이다.
(사실 정규식을 잘 쓴다면 필요없을 것이다...)
 
from BeautifulSoup import BeautifulSoup
import urllib2
 
url = 'http://블라블라'
handle = urllib2.urlopen(url)
data = handle.read()
soup = BeautifulSoup(data)
article = str( soup('div', {'class':'article',}) ) #div내의 article class 추출
print article.decode('utf8')
 
 
위 예제 프로그램은 본문에서 div 내의 article 클래스만을 추출하는 예제이다. (ex. 티스토리)
 
 
자세한 내용은 다시 찾기 귀차니즘으로 인해, 수수깡님이 스크랩한 내용을 아래에 copy&paste 해 놓겠다.
 
-----------------------------------------------------------------------------------
 
Beautiful Soup (2.1.1)
 
 
 
 
웹을 가지고 놀기 위해서는 먼저 웹의 언어인 HTML을 잘 구사할 수 있어야 한다.
 
세상에는 프로그래머가 HTML을 잘 말하고 잘 알아듣기 위해 사용하는 HTML 파서가
 
무수히 많다.  그중에서 사용하기 쉬운 파서를 하나 고르자면 Beautiful Soup을 들
 
수 있다.  Beautiful Soup은 파이선으로 작성되었으며, 동적 스크립트언어의 장점을
 
잘 활용한다.
 
#import urllib
 
#html_source = urllib.urlopen('http://www.naver.com').read()
 
html_source = '''
 
 
 
 
 
'''
 
 
 
from BeautifulSoup import BeautifulSoup
 
soup = BeautifulSoup(html_source)
 
 
 
# 테그 이름을 변수 이름으로 사용할 수 있다.
print soup.html.head.title
# 결과:
 
 
 
# 계층구조의 중간단계를 생략할 수 있다.
print soup.title
# 결과:
 
 
 
# 테그 안에 다른 테그가 없는 경우 string 속성으로 테그 내용을 얻을 수 있다.
print soup.title.string
# 결과: 페이지 제목
 
 
 
# 같은 이름의 테그가 여러개 있다면 제일 먼저 나오는 테그를 알려준다.
# dictionary 문법을 사용하여 테그의 속성만 얻을 수도 있다.
print soup.p
# 결과:
 
첫번째 단락
 
 
print soup.p['class']
# 결과: layout
 
 
 
# 없는 테그를 지칭하면 (BeautifulSoup.) Null 객체를 반환한다.
print soup.body.title
# 결과: Null
 
 
 
# soup('p') 은 첫번째 뿐아니라 모든 p 테그 목록을 반환한다.
# 두번째 아규먼트로 테그의 속성을 제한할 수도 있다.
print soup('p')[0]
# 결과:
첫번째 단락
 
 
print soup('img', { 'name': 'main', })
# 결과: []
print soup('p', 'layout')
# soup('p', { 'class': 'layout' }) 과 같다. CSS 분류를 쉽게 지정할 수 있다.
 
 
 
# parent 속성은 계층구조상 한칸 위에 있는 테그를 지칭하고, 반대로 contents
 
# 속성은 계층구조상 한칸 아래에 있는 테그 목록을 반환한다.
# nextSibling 와 previousSibling 은 계층구조상 같은 위치에 있는 바로 앞뒤 테그를
 
# 지칭한다.  예제에서 첫번째 p 테그의 nextSibling 은 두번째 p 테그가 아니라
 
# 첫번째 p 테그와 두번째 p 테그 사이 영역이고, 이 영역에는 줄바꿈 문자 하나만 있다.
# 이는 soup('p').parent.contents 로 확인할 수 있다.
# next 와 previous 는 계층구조와 무관하게 HTML 소스에서 테그 바로 앞뒤에 위치하는
 
# 테그를 지칭한다.  마지막으로 테그 이름은 name 속성에 저장된다.
print soup('p')[0].nextSibling
# 결과: \n
print soup('p')[0].next
# 결과: 첫번째 단락
 
print soup('p')[0].next.name
# 결과: b
# 앞에서 본 string 속성은 테그 안에 다른 테그가 없는 경우에는 contents[0] 과 같고,
# 다른 테그가 있다면 Null 이다.
 
 
 
print len(soup('p')) # len(soup('p').contents) 와 같다.
for x in soup('p'): # for x in soup('p').contents: 와 같다.
 
    pass
 
 
 
## fetch(name, attrs, recursive, limit) 함수
# 다양한 조건을 가지고 원하는 테그를 찾는 함수로, 앞의 예들은 이 함수의 축약형이다.
 
#       tag.fetch(...) = tag(...)
# name과 attrs는 각각 테그 이름과 테그 속성을 나타내는데 다양한 방법으로 지시할 수 있다.
 
#
# * 문자열: fetch('img'), 모든 img 테그 목록
# * 목록: fetch(['object', 'applet', 'embed']), 모든 object/applet/embed 테그 목록
# * dictionary: fetch('div', { 'class': 'sidebar', 'name': 'menu' })
# * 정규표현식: fetch('div', { 'name': re.compile('list.*') }),
 
#                                           name 속성이 "list"로 시작하는 모든 div 테그 목록
# * 함수: 원하는 조건인 경우 참을 반환하는 함수를 사용하여 복잡한 조건을 지시할 수 있다.
 
#
# recursive와 limit는 계층구조상 현재 테그 아래를 계속 찾아들어갈지, 만약 그렇다면
 
# 어느정도까지 들어갈지를 정한다.  기본적으로 현재 테그 아래로 끝까지 들어가면서
 
# 테그를 찾는다.
 
#
# fetch() 를 기준삼아 first(), fetchText(), firstText(), findNextSibling(),
 
# findPreviousSibling(), fetchNextSibling(), fetchPreviousSibling(),
# findNext(), findPrevious(), fetchNext(), fetchPrevious(), findParent(),
# fetchParent() 와 같은 함수가 있다.  fetch*/*Text() 함수는 테그가 아닌 테그 안의
 
# 문자를 찾거나 가져오고, *Next*/*Previous*/*Parent() 함수는 현재 테그에서
 
# 계층구조상 아래로 내려가지 않고 대신 앞뒤 혹은 위로 이동하며 조건에 맞는 테그를
 
# 찾는다.  각 함수의 자세한 정보는 설명서를 참고하라.
def need_thumbnail(x):
    # 가로나 세로가 60 보다 큰 img 테그라면 True, 아니면 False
 
    if x.name == 'img':
 
        return x.get('height', 0) > 60 or x.get('width', 0) > 60
 
    return False
print soup.ul(need_thumbnail) # = soup.ul.fetch(need_thumbnail)
print soup.p.findNextSibling('p') # 두번째 p 테그
 
 
 
# 다음과 같이 HTML 소스를 수정할 수도 있다.  단, 이때는 앞에서 본 string 같은
# 축약형을 사용할 수 없고 contents 목록을 직접 수정해야 한다.  그후 prettify()
 
# 함수로 수정한 HTML 소스를 계층구조에 따라 들여쓰기하여 출력한다.
print soup
soup.title.contents[0] = '제목 수정'
soup.p['class'] = 'menu'
soup('p')[1].contents = ['두번째 단락 생략',]
 
del soup.body.contents[5]
print soup.prettify()
 
현재 Beautiful Soup은 두가지 문제가 있는데, 하나는 속도이고 다른 하나는 한글처리다.
 
Beautiful Soup은 빠른 속도를 위해 최적화하여 설계되지 않았기때문에 복잡한 HTML
 
소스를 처리할 때 속도가 느려진다.  이런 경우에는 자주 참조하는 테그의 공통분모를
 
미리 변수에 저장해두고 이 변수를 기준으로 테그들을 참조하는 식으로 부담을 덜 수 있다.
 
 
 
    soup('div', { 'name': 'toolbar' })[0].table('tr')[0]('td')[2].ul('li')[0]
 
    soup('div', { 'name': 'toolbar' })[0].table('tr')[0]('td')[2].ul('li')[1]
 
    soup('div', { 'name': 'toolbar' })[0].table('tr')[0]('td')[2].ul('li')[2]
 
    soup('div', { 'name': 'toolbar' })[0].table('tr')[0]('td')[2].ul('li')[3]
 
->
 
    ulist = soup('div', { 'name': 'toolbar' })[0].table('tr')[0]('td')[2].ul
 
    ulist('li')[0]
 
    ulist('li')[1]
 
    ulist('li')[2]
 
    ulist('li')[3]
 
 
 
한글처리에서는 테그의 속성값에 한글이 있는 경우 테그 속성값을 전부 무시해 버린다.
정확히는 파이선 표준 라이브러리의 sgmllib의 문제인데, BeautifulSoup.py 파일의
    from sgmllib import SGMLParser, SGMLParseError
줄을
    from hack_sgmllib import SGMLParser, SGMLParseError
으로 수정하고, 표준 라이브러리의 sgmllib.py 파일의 복사본을 BeautifulSoup.py 와
 
동일한 디렉토리에 hack_sgmllib.py 란 이름으로 저장한다.  그리고 attrfind 정규표현식에서
 
[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@] 부분을 [^ >] 로 수정한다. 깔끔한 방법은
 
아니지만 어쨌든 한글 테그 속성을 인식하게 된다. 
반응형

'엔지니어 > Python' 카테고리의 다른 글

python html 파싱 2  (0) 2022.10.28
python 으로 mysql 를 접근할때  (0) 2022.10.28
Grumpy: Go running Python  (0) 2017.01.11
parsing  (0) 2016.05.26
python source install  (0) 2016.05.26