1、示例说明
python版本:Python3.7
操作系统:win10 64位
爬虫类:HTMLParser
思路:利用栈,把标签入栈出栈的方式遍历标签
2、修改主要方法
2.1、标签开始时调用的方法 handle_starttag
标签开始处,把标签入栈,标签属性入栈
def handle_starttag(self, tag, attrs):
HTMLParser.handle_starttag(self, tag, attrs)
# SGMLParser.finish_starttag(self, tag, attrs)
# self.now_tag_attrs = {} if attrs is None else attrs
#没有属性的标签,设置空列表站位
if attrs == None:
attrs = {}
self.now_tag_attrs.append(attrs)
self.tag_stack.append(tag)
2.2、标签结束时调用的方法 handle_endtag
标签结束处,找寻栈中的标签;
如果是该标签的开始标签,则出栈,并结束循环,
如果不是该标签的开始标签,则出栈,并继续判断
def handle_endtag(self, tag):
HTMLParser.handle_endtag(self, tag)
# SGMLParser.finish_endtag(self, tag)
# self.now_tag_attrs = {}
# 一直pop,直到踢出与当前endtag匹配的为止。
# 这种现象是由于出现<meta/>这种只有starttag的标签导致的
while True:
if operator.eq(tag, self.tag_stack[-1]):
self.tag_stack.pop()
self.now_tag_attrs.pop()
break
else:
self.tag_stack.pop()
self.now_tag_attrs.pop()
2.3、获取标签值的方法 handle_data
判断栈中的层级信息,是否与给出的标签栈相同,相同则说明是自己要取的标签,记录值
说明:标签栈可以理解为标签的层级关系,如下:
('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'strong', 'em')
def handle_data(self, text):
BaseParser.handle_data(self, text)
if text == "":
return
# id
if operator.eq(tuple(self.tag_stack), MyJDParser.__priceStyle_stack__) \
and self.is_now_has_tag_key(MyJDParser.__priceStyle_tag_key_set__):
self.priceStyle = text
2.4、自定义方法,判断标签属性是否合法
在方法handle_data中与标签栈配合使用,检查该标签是发包含给定的属性,例如属性:
('target', 'title','href','onclick')
def is_now_has_tag_key(self, key_set):
if len(self.now_tag_attrs) == 0 and len(key_set) == 0:
return True
# 获取该标签的属性
attrs = {}
attrs = self.now_tag_attrs[-1]
if len(attrs) == 0 and len(key_set) == 0:
return True
elif len(attrs) == 0 and len(key_set) > 0:
return False
for key in key_set:
kv_index = 0
for kv in attrs:
kv_index += 1
if operator.eq(key, kv[0]):
break
if len(self.now_tag_attrs) == kv_index:
return False
return True
2.5、自定义方法,获取标签属性值
方法可在handle_data中调用,如果是自己要的标签,则可以获取改标签对应属性值,例如获取href链接
def __getAttrValue(self, key=''):
if len(self.now_tag_attrs) == 0:
return ''
# 获取该标签的属性
attrs = {}
attrs = self.now_tag_attrs[-1]
for attr in attrs:
if operator.eq(str(attr[0]), key):
return str(attr[1])
return ''
3、爬虫软件调用方法
parser = MyJDParser()
parser.feed(html)
goods = parser.bean_list;
4、小技巧
4.1、获取对应标签的标签栈
抓取的html页面中,利用关键字搜索到标签所在的位置,关键字为标签的值,
在方法handle_data中加入如下内容,debug即可获取准确的标签栈
def handle_data(self, text):
BaseParser.handle_data(self, text)
if text == "":
return
if operator.eq(text, '标签值'):
print(tuple(self.tag_stack))
# id
if operator.eq(tuple(self.tag_stack), MyJDParser.__priceStyle_stack__) \
and self.is_now_has_tag_key(MyJDParser.__priceStyle_tag_key_set__):
self.priceStyle = text
5、完整代码如下
示例为抓取京东商城页面的商品信息
#! /usr/bin/python
# coding:utf-8
# from sgmllib import SGMLParser
from html.parser import HTMLParser
import operator
class BaseParser(HTMLParser):
def __init__(self):
# SGMLParser.__init__(self)
HTMLParser.__init__(self)
# 保存抓取的标签
self.tag_stack = []
# 保存抓取的标签属性(于tag_stack对应)
self.now_tag_attrs = []
def handle_starttag(self, tag, attrs):
HTMLParser.handle_starttag(self, tag, attrs)
# SGMLParser.finish_starttag(self, tag, attrs)
# self.now_tag_attrs = {} if attrs is None else attrs
#没有属性的标签,设置空列表站位
if attrs == None:
attrs = {}
self.now_tag_attrs.append(attrs)
self.tag_stack.append(tag)
# print("start_tag: " + str(self.tag_stack))
# print("start_attrs: " + str(attrs))
def handle_endtag(self, tag):
HTMLParser.handle_endtag(self, tag)
# SGMLParser.finish_endtag(self, tag)
# self.now_tag_attrs = {}
# 一直pop,直到踢出与当前endtag匹配的为止。
# 这种现象是由于出现<meta/>这种只有starttag的标签导致的
while True:
if operator.eq(tag, self.tag_stack[-1]):
self.tag_stack.pop()
self.now_tag_attrs.pop()
break
else:
self.tag_stack.pop()
self.now_tag_attrs.pop()
# print(" end_tag: " + str(self.tag_stack))
def is_now_has_tag_key(self, key_set):
if len(self.now_tag_attrs) == 0 and len(key_set) == 0:
return True
# 获取该标签的属性
attrs = {}
attrs = self.now_tag_attrs[-1]
if len(attrs) == 0 and len(key_set) == 0:
return True
elif len(attrs) == 0 and len(key_set) > 0:
return False
for key in key_set:
kv_index = 0
for kv in attrs:
kv_index += 1
if operator.eq(key, kv[0]):
break
if len(self.now_tag_attrs) == kv_index:
return False
return True
def get_data(self):
pass
class MyJDParser(BaseParser):
# priceStyle
__priceStyle_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'strong', 'em')
__priceStyle_tag_key_set__ = ()
# price
__price_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'strong', 'i')
__price_tag_key_set__ = ()
# 名称
__href_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'a')
__href_tag_key_set__ = ('target', 'title','href','onclick')
#名称
__name_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'a', 'em')
__name_tag_key_set__ = ()
# 名称
__name_font_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'a', 'em','font')
__name_font_tag_key_set__ = ()
# 类型
__promo_words_stack__ = ('html', 'body', 'div', 'div', 'div', 'div', 'div', 'div', 'ul', 'li', 'div', 'div', 'a', 'i')
__promo_words_tag_key_set__ = ('class','id')
def __init__(self):
BaseParser.__init__(self)
self.content_set = {}
self.priceStyle = ''
self.price = '0'
self.name = ''
self.promo_words = ''
self.href = ''
self.bean_list = []
def handle_data(self, text):
BaseParser.handle_data(self, text)
if text == "":
return
if operator.eq(text, '标签值'):
print(tuple(self.tag_stack))
# id
if operator.eq(tuple(self.tag_stack), MyJDParser.__priceStyle_stack__) \
and self.is_now_has_tag_key(MyJDParser.__priceStyle_tag_key_set__):
self.priceStyle = text
# level
elif operator.eq(tuple(self.tag_stack), MyJDParser.__price_stack__) \
and self.is_now_has_tag_key(MyJDParser.__price_tag_key_set__):
self.price = text
# # title
elif operator.eq(tuple(self.tag_stack), MyJDParser.__href_stack__) \
and self.is_now_has_tag_key(MyJDParser.__href_tag_key_set__):
self.href = 'https:' + self.__getAttrValue(key='href')
elif operator.eq(tuple(self.tag_stack), MyJDParser.__name_stack__) \
and self.is_now_has_tag_key(MyJDParser.__name_tag_key_set__):
self.name += text
# # error_type
elif operator.eq(tuple(self.tag_stack), MyJDParser.__name_font_stack__) \
and self.is_now_has_tag_key(MyJDParser.__name_font_tag_key_set__):
self.name += text
elif operator.eq(tuple(self.tag_stack), MyJDParser.__promo_words_stack__) \
and self.is_now_has_tag_key(MyJDParser.__promo_words_tag_key_set__) :
self.promo_words = text
good = {}
good['priceStyle'] = self.priceStyle
good['price'] = self.price
good['name'] = self.name
good['promo_words'] = self.promo_words
good['href'] = self.href
self.bean_list.append(good)
self.priceStyle = ''
self.price = '0'
self.name = ''
self.promo_words = ''
self.href = ''
def __getAttrValue(self, key=''):
if len(self.now_tag_attrs) == 0:
return ''
# 获取该标签的属性
attrs = {}
attrs = self.now_tag_attrs[-1]
for attr in attrs:
if operator.eq(str(attr[0]), key):
return str(attr[1])
return ''