您的位置:首页 > 编程语言 > Python开发

python学习笔记三:解析html(HTMLParser、SGMLParser),并抓取图片

2016-04-16 16:32 871 查看
在上一篇中,抓取了百度贴吧中的图片并保存在了本地文件夹中,不过可以发现里面是利用正则表达式来过滤出图片地址和页面数字,于是在本节中,考虑直接利用python中自带的解析html的解析器HTMLParser和SGMLParser来分析html,另外由于SGMLParser比HTMLParser好用,于是这里用了SGMLParser。

sgmllib.py 包含一个重要的类: SGMLParser。SGMLParser 将 HTML 分解成有用的片段, 比如开始标记和结束标记。一旦它成功地分解出某个数据为一个有用的片段,它会根据 所发现的数据,调用一个自身内部的方法。为了使用这个分析器,您需要子类化
SGML- Parser类,并且覆盖这些方法。

SGMLParser类里面包含了很多内部方法,开始读取html后,遇到相应的数据就会调用其对应的方法,最重要的方法有三个:

start_tagname(self, attrs)
end_tagname(self)
handle_data(self, text)

tagname就是标签名称,比如当遇到<pre>,就会调用start_pre,遇到</pre>,就会调用 end_pre,attrs即为标签的参数,以[(attribute, value), (attribute, value), ...]的形式传回,我们要做的就是在其子类重载自己感兴趣标签对应的函数。

一个经典的例子:

from sgmllib import SGMLParser

class URLLister(SGMLParser):
def __init__(self):
SGMLParser.__init__(self)
self.urls = []
def start_a(self, attrs):
href = [v for k, v in attrs if k=='href']
if href:
self.urls.extend(href)


顾名思义,这个类的作用就是把html中的所有连接(<a>标签)中的地址(href属性的值)提取出来,放到一个list里面,很实用的功能。^^

比如处理下面的html:
<tr>

<td height="207" colspan="2" align="left" valign="top" class="normal">

<p>Damien Rice - 《0》 </p>

<a href="http://galeki.xy568.net/music/Delicate.mp3">1. Delicate</a><br />

<a href="http://galeki.xy568.net/music/Volcano.mp3">2. Volcano</a><br />

<a href="http://galeki.xy568.net/music/The Blower's Daughter.mp3">3. The Blower's Daughter</a><br/>

<a href="http://galeki.xy568.net/music/Cannonball.mp3">4. Cannonball </a><br />

<a href="http://galeki.xy568.net/music/Older Chests.mp3">5. Order Chests</a><br />

<a href="http://galeki.xy568.net/music/Amie.mp3">6. Amie</a><br />

<a href="http://galeki.xy568.net/music/Cheers Darlin'.mp3">7. Cheers Darling</a><br />

<a href="http://galeki.xy568.net/music/Cold Water.mp3">8. Cold water</a><br />

<a href="http://galeki.xy568.net/music/I Remember.mp3">9. I remember</a><br />

<a href="http://galeki.xy568.net/music/Eskimo.mp3">10. Eskimo</a></p>

</td>

</tr>

很乱对吧?下面让举个例子利用URLLister提取出上面mp3下载的地址:

date="上面那一堆…………"
lister=URLLister()
lister.feed(date)

用feed()把要处理的html传递给对象实体,然后我们来看看处理结果:

print lister.urls

显示:
['http://galeki.xy568.net/music/Delicate.mp3',

'http://galeki.xy568.net/music/Volcano.mp3',

"http://galeki.xy568.net/music/The Blower's Daughter.mp3",

'http://galeki.xy568.net/music/Cannonball.mp3',

'http://galeki.xy568.net/music/Older Chests.mp3',

'http://galeki.xy568.net/music/Amie.mp3',

"http://galeki.xy568.net/music/Cheers Darlin'.mp3",

'http://galeki.xy568.net/music/Cold Water.mp3',

'http://galeki.xy568.net/music/I Remember.mp3',

'http://galeki.xy568.net/music/Eskimo.mp3']

好了,是不是很方便?现在我们知道了如何处理标签中的属性,那么如何处理标签包含的文字呢?就是上面列出的handle_data(self, text),当遇到标签内的内容,就会调用这个函数,传入的text自然就是标签内的内容了,不过,如何筛选出感兴趣标签内的内容呢?比如上面歌曲的列
表,这时候就要配合start_tagname、end_tagname,用做标记的方法来达到这个目的:

from sgmllib import SGMLParser

class ListName(SGMLParser):
def __init__(self):
SGMLParser.__init__(self)
self.is_a = ""
self.name = []
def start_a(self,attrs):
self.is_a = 1
def end_a(self):
self.is_a = ""

def handle_data(self,text):
if self.is_a == 1:
self.name.append(text)


看看结果:

listname=ListName()
listname.feed(date)
print listname.name

或者使用:

for item in listname.name:
print item.decode('gbk').encode('utf8')  #当文字是中文时,这样不会乱码


显示:
['1.Delicate', '2.Volcano', "3.The Blower's Daughter",

'4.Cannonball ', '5.Order Chests', '6.Amie',

'7.Cheers Darling', '8.Cold water', '9.I remember',

'10.Eskimo']

OK,搞定~

很简单,这里定义了一个叫做ListName的类,继承SGMLParser里面的方法。使用一个变量is_a做标记判定html文件中的a标签,如果遇到a标签,则将标签内的内容加入到List变量name中。解释一下start_a()和end_a(),它们原型是SGMLParser中的

start_tagname(self, attrs)

end_tagname(self)

tagname就是标签名称,比如当遇到<pre>,就会调用start_pre,遇到</pre>,就会调用 end_pre。attrs为标签的参数,以[(attribute, value), (attribute, value), ...]的形式传回。

SGMLParser内置的方法不仅仅只有这三个,还有处理注释的handle_comment,还有处理声明的handle_decl等等等等,不过使用方法和上面的基本相同,不再多写了。

完整代码:
#coding:utf-8

from sgmllib import SGMLParser

class ListName(SGMLParser):

def reset(self):
#变量标示是否是标签a,或使用self.is_a=True
self.is_a=""
self.name=[]
#继承父类reset方法
SGMLParser.reset(self)

def start_a(self,attrs):
#如果是标签a,则设置is_a=1
self.is_a=1

def end_a(self):
self.is_a=""

def handle_data(self,data):
#如果是标签a的数据,则追加
if self.is_a:
self.name.append(data)

if __name__ == '__main__':
urls='''''
<tr>
<td height="207" colspan="2" align="left" valign="top" class="normal">
<p>Damien Rice - 《0》 </p>
<a href="http://galeki.xy568.net/music/Delicate.mp3">1. Delicate</a><br />
<a href="http://galeki.xy568.net/music/Volcano.mp3">2. Volcano</a><br />
<a href="http://galeki.xy568.net/music/The Blower's Daughter.mp3">3. The Blower's Daughter</a><br />
<a href="http://galeki.xy568.net/music/Cannonball.mp3">4. Cannonball </a><br />
<a href="http://galeki.xy568.net/music/Older Chests.mp3">5. Order Chests</a><br />
<a href="http://galeki.xy568.net/music/Amie.mp3">6. Amie</a><br />
<a href="http://galeki.xy568.net/music/Cheers Darlin'.mp3">7. Cheers Darling</a><br />
<a href="http://galeki.xy568.net/music/Cold Water.mp3">8. Cold water</a><br />
<a href="http://galeki.xy568.net/music/I Remember.mp3">9. I remember</a><br />
<a href="http://galeki.xy568.net/music/Eskimo.mp3">10. Eskimo</a></p>
</td>
</tr>
'''
listname=ListName()
listname.feed(urls)
#输出文本的列表结果
print listname.name
listname.close()


以上是使用SGMLParser的一个简单的例子介绍,下面我将利用SGMLParser和urllib2库来实现图片抓取,主要抓的是http://desk.zol.com.cn/meinv/这个链接下的图片,通过得到第一个图集的起始URL地址,得到第一张图片,然后提取出该图集中下一张图片的URL地址,不断的去获取其下一个图片的URL,并下载图片,下载完第一个图集的图片后,接着得到第二个图集的起始URL地址,然后获取其下一个图片的URL,继而得到所有首页的图集的图片。

代码如下:
# -*- coding: utf-8 -*-
#feimengjuan
import urllib
import urllib2
from sgmllib import SGMLParser
import re

# 根url
host = "http://desk.zol.com.cn"

# 定义保存文件夹
localSavePath = "girls/"

# 根据首页得到的图片集遍历每个图片集,获得该图片集下的所有图片的地址
def getImageUrlByHtmlUrl(htmlUrl):
parser = MySGMLParser(False) # 传入False,来控制解析的是某个图片集网址
try:
print "正在解析的网页是:",htmlUrl
response = urllib2.urlopen(htmlUrl)
content = response.read()
parser.feed(content) # 解析该图片集网址
except urllib2.URLError, e:
print e.reason

# 下载图片
def downLoadImage(imageUrl):
print "正在下载的图片地址:",imageUrl
data = urllib2.urlopen(imageUrl).read()
# 由图片的地址名中分析取出后面的数字作为图片文件名来保存
pattern = '[0-9]*\.jpg'
match = re.search(pattern,imageUrl)
if match:
print '正在下载文件:',match.group()
fileName = localSavePath + match.group()
f = open(fileName,'wb+')
f.write(data)
f.close
else:
print 'no match'

class MySGMLParser(SGMLParser):
def __init__(self,isIndex):
self.isIndex = isIndex  # 程序开始时isIndex = Ture,
SGMLParser.__init__(self)
self.urls = []

# 用标签'a'来查找网页链接,开始时,
# 是从首页面查找所有图片集的网址,由isIndex = True来控制
def start_a(self,attrs):
# 程序最开始时,isIndex = Ture,表示解析的是初始网页,从中获得每个图片集的网址
if(self.isIndex):
if(len(attrs) == 4):
if(attrs[0] == ('class','pic')):
#提取出href的属性,即图片集的网址
newUrl = [host + v for k,v in attrs if k == 'href']
print '找到一处图片集的网页链接:',newUrl[0]
getImageUrlByHtmlUrl(newUrl[0])
# isIndex = False,表示解析每个图片集的第一张图片所在的网页,
# 从中获得每个图片集下所有图片的网页
else:
if(len(attrs) == 4):
# 由该图片集中当前的图片所在网页中获取下一张图片的网址
if(attrs[0] == ('id','pageNext')):
#提取出href的属性,即下一张图片的网址
href = [ v for k,v in attrs if k == 'href']
# 在每个图片集最后一张图片中没有下一张图片的链接地址,
# 而是为"javascript:;",所以要区分出来
if (href[0] != "javascript:;"):
nextUrl = host + href[0]
print '找到该图片集中一处图片的网页链接:',nextUrl
getImageUrlByHtmlUrl(nextUrl)

def start_img(self,attrs): # 用标签img来查找图片地址并下载
if(self.isIndex == False):
# 在每个图片集的最后一张图片的html中无法找到下一张图片的网页链接,
# 因此会一直将这个html解析到最后,故需要加一个判断,表示已经解析完整个html了,
# 此时attrs为空,表示已经下载完了该图片集的最后一张图片
if attrs:
if(attrs[0] == ('id','bigImg')):
imageUrl = attrs[1][1]  # 图片的地址
print '找到一张图片:',imageUrl
downLoadImage(imageUrl) # 下载保存图片

if __name__ == '__main__':
url = "http://desk.zol.com.cn/meinv/"
html = urllib.urlopen(url).read()
# 开始时,传入参数True,表示解析的图集首页,即给的网址
mySparser = MySGMLParser(True)
mySparser.feed(html)


最后,会在本地girls文件夹下,保存了所有图集的图片
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: