python beautiful soup html parsing
HTML 파싱할 일이 생겼는데,
그동안은 그냥그냥 필요한 내용만 crummy에서 짬짬히 보다가,
BeautifulSoup을 한국말로 잘 정리한 사이트를 찾았다.
susukang98님의 블로그 : http://susukang98.springnote.com/pages/333771
예를 들자면, 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 해 놓겠다.
-----------------------------------------------------------------------------------
Python 프로그래밍을 하다가 HTML Parser가 필요하게 되었습니다.
인터넷에 돌아다니는 수많은 HTML은 실제로 표준을 제대로 지키지 않은 문서가 많기 때문에 일반적인 XML파서가 테그를 파싱하려다가 Syntax Error를 피하지 못하는 수가 많습니다. 그래서 회사에서 주워들은 Beautiful Soap을 다운로드 하려고 사이트를 뒤졌으나... www.crummy.com/software/BeautifulSoup/ 사이트가 맛이 갔군요. 덜덜덜 -_-a susukang (2007/06/27 22:43:27)
앗 BeautifulSoup사이트가 기동되서 받아봤습니다. 단순히 BeautifulSoup.py라는 파일 하나 있고 LIB에다가 넣어두었는데 바로 돌아가는 군요.. 역시 파이썬 ㅋㅋ susukang( 2007/06/29 01:12:29 )
* 외부에서 받은 .py파일은 Python24/Lib/site-packages/ 아래에 넣어주면 좋겠군요. susukang ( 2007/07/10 16:28:10 )
- import BeautifulSoap
- ...중략
- def _getFilePathList(self, crawledHtmlPath, filePathList):
file = open(crawledHtmlPath)
soup = BeautifulSoup.BeautifulSoup(file)
linkList = soup.findAll('a')
- for link in linkList:
linkStr = str(link)
if self._isContainFileExt(linkStr):
filePathList.append(link.get(u'href'))
html코드에서 a Tag의 FileUrl을 가져오는 소스 코드 입니다. 엄청 간단하네요. 이렇게 쉬울줄은 몰랐네요. BeautifulSoap의 Documentation은 http://www.crummy.com/software/BeautifulSoup/documentation.html#The%20basic%20find%20method:%20findAll(name,%20attrs,%20recursive,%20text,%20limit,%20**kwargs) 에 있습니다.
이로써 Html핸들링은 날로 먹었꾼요 ^^a
BeautifulSoup 이 대단한 점은 실제 필드에서 만나는 HTML 의 경우 정확한 형식이 지켜지지 않는 경우를 종종 만나게 되는데, 이러한 HTML 을 적절하게 가정하여 그 상황에서 가장 최선이라 생각되는 구문으로 판단하여 파싱을 해준다는데 있습니다. 오죽하면, 인코딩조차도 실제 문서에 캐릭터들의 출현 패턴을 분석하여 적절한 인코딩을 도출해 내기까지 하니, 거친 HTML 의 바다를 헤쳐나가는데 있어선 최고의 선택이 아닐까 싶습니다 - 이성진 2007/07/10 23:19:45
다음부터는 웹사이트 스크랩입니다. - 2007/08/02 21:50:01
Beautiful Soup (2.1.1)
http://www.crummy.com/software/BeautifulSoup/
웹을 가지고 놀기 위해서는 먼저 웹의 언어인 HTML을 잘 구사할 수 있어야 한다. 세상에는 프로그래머가 HTML을 잘 말하고 잘 알아듣기 위해 사용하는 HTML 파서가 무수히 많다. 그중에서 사용하기 쉬운 파서를 하나 고르자면 Beautiful Soup을 들 수 있다. Beautiful Soup은 파이선으로 작성되었으며, 동적 스크립트언어의 장점을 잘 활용한다.
#import urllib
#html_source = urllib.urlopen('http://www.naver.com').read()
html_source = '''
<html>
<head><title>페이지 제목</title></head>
<body>
<p class="layout"><b id="key">첫번재 단락</b></p>
<p>
<b>두번째 <i>단락</b> 시작</i>
<hr>
<ul>
<li><img src="dooly-1.png" name="main" height=100 width=70>둘리
<li><img src="dooly-2.png" height="70" width="50">도우너
<li><img src="dooly-3.png" height="30" width="20">또치
<li><img src="dooly-4.png">마이콜
</ul>
</p><p>
</body>
</html>'''
from BeautifulSoup import BeautifulSoup
soup = BeautifulSoup(html_source)
# 테그 이름을 변수 이름으로 사용할 수 있다.
print soup.html.head.title
# 결과: <title>페이지 제목</title>
# 계층구조의 중간단계를 생략할 수 있다.
print soup.title
# 결과: <title>페이지 제목</title>
# 테그 안에 다른 테그가 없는 경우 string 속성으로 테그 내용을 얻을 수 있다.
print soup.title.string
# 결과: 페이지 제목
# 같은 이름의 테그가 여러개 있다면 제일 먼저 나오는 테그를 알려준다.
# dictionary 문법을 사용하여 테그의 속성만 얻을 수도 있다.
print soup.p
# 결과: <p class="layout"><b>첫번째 단락</b></p>
print soup.p['class']
# 결과: layout
# 없는 테그를 지칭하면 (BeautifulSoup.) Null 객체를 반환한다.
print soup.body.title
# 결과: Null
# soup('p') 은 첫번째 뿐아니라 모든 p 테그 목록을 반환한다.
# 두번째 아규먼트로 테그의 속성을 제한할 수도 있다.
print soup('p')[0]
# 결과: <p class="layout"><b>첫번째 단락</b></p>
print soup('img', { 'name': 'main', })
# 결과: [<img src="dooly-1.png" name="main" height="100" width="70" />]
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
# 결과: <b>첫번째 단락</b>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, 아니면 Falseif 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./,:;+*%?!&$\(\)_#=~\'"@] 부분을 [^ >] 로 수정한다. 깔끔한 방법은
아니지만 어쨌든 한글 테그 속성을 인식하게 된다.
___________________________________________________________________________________
import BeautifulSoup as bs
html = "<div x='a' y='b' z='c'>hello</div>"
doc = bs.BeautifulSoup(html)
div = doc.find("div")
for attr, val in div.attrs:
print "%s:%s" % (attr, val)
--output:--
x:a
y:b
z:c
____________________________________________________________________________
BeautifulSoup으로 한글 웹페이지 읽어오기
-
from BeautifulSoup import BeautifulSoup
-
import urllib2
-
-
page = urllib2.urlopen("http://aslongas.pe.kr/tt/")
-
soup = BeautifulSoup(page, fromEncoding="euc-kr")
________________________________________
http://kkamagui.tistory.com/457 http://noish.tistory.com/97 from BeautifulSoup import BeautifulSoup import urllib2 def getC(str): page = urllib2.urlopen(str) soup = BeautifulSoup(page) print soup('span', 'postbody')[0].renderContents('cp949') getC("http://python.kr/viewtopic.php?t=24618") |
'Python' 카테고리의 다른 글
pyserial (0) | 2009.03.13 |
---|---|
python directory 다루기 (0) | 2009.01.21 |
django 실습 (0) | 2008.12.13 |