问题:对网页Python会议,用浏览器查看源码;尝试解析HTML,输出Python官网发布的会议时间、名称和地点
准备工作:
①打开网页后,需要提取的信息
②按F12进入开发者模式,找到这部分的源代码
<li> <h3 class="event-title"><a href="/events/python-events/883/">PyConDE & PyData Berlin 2020 (cancelled)</a></h3> <p> <time datetime="2020-10-14T00:00:00+00:00">14 Oct. – 16 Oct. <span class="say-no-more"> 2020</span></time> <span class="event-location">Berlin, Germany</span> </p> </li>
方法1、request请求+正则表达式+re函数
step1、通过GET请求读取网页信息,并转化为str类型
step2、利用正则表达式和re函数进行信息查找
完整代码:
# 从https://www.python.org/events/python-events/,解析HTML # 输出Python官网发布的会议时间、名称和地点 # 正则表达式、匹配函数所在模块re # 请求网页内容:urllib模块request类 import re from urllib import request, error def GetInfo_URL(URL): # 提取网页内容,返回为str,请求方式为GET req = request.Request(URL) try: with request.urlopen(req) as res: print(\'Status:\',res.status,res.reason) data = res.read().decode() # 转码后data为str类型 except error.URLError as e: print(e) # 对data使用正则表达式和findall函数进行信息提取 #行与行间,没有空格及换行符\n re_datetime = re.compile(r\'datetime="(.+)T\') re_duration = re.compile(r\'<time datetime=".*">(.*)<span class="say-no-more">\') #r\'\"(\d{2} \w{3}\..*)\"\' re_name = re.compile(r\'<h3 class="event-title"><.+>(.+)</a\') re_location = re.compile(r\'event-location\">(.+)</span\') ret_datetime = re_datetime.findall(data) ret_duration = re_duration.findall(data) ret_name = re_name.findall(data) ret_location = re_location.findall(data) for i in range(len(ret_datetime)): print(\'Event %d:\'%i) print(\'Datetime:%s\'%ret_datetime[i]) print(\'Duration:%s\' % ret_duration[i].replace(\'–\',\'-\')) #把\'-\'的html符号实体转化为str\'-\' print(\'Name:%s\'%ret_name[i]) print(\'Location:%s\'%ret_location[i]) print(\'\n\') if __name__ == \'__main__\': URL=\'https://www.python.org/events/python-events/\' GetInfo_URL(URL)
需要注意的几点:
1、html内容经过decode解码为str类型后,html中不同行内容以拼接的形式放在同一个str中了,行与行的内容间不存在换行符,空格等分隔符号(这点很重要!!!)
2、接1,因此,构建正则表达式时,可以直接看该模块前后模块的内容,将匹配限制在一个很精确的范围
比如代码中正则表达式re_duration的构建:
re_duration = re.compile(r\'<time datetime=".*">(.*)<span class="say-no-more">\')
方法2、Python的HTMLParser类
导入:
from html.parser import HTMLParser
这个类是专门用来解析HTML的,用起来很方便。
只需要简单的分析,要提取的网页中的各项信息储存格式就可以。
回到这张图片,图片中框起来的是我们要提取出来的信息。
step1、导入模块和类
from html.parser import HTMLParser from urllib import request,error import re
step2、构建此程序专用的MyHTMLParser类(继承自HTMLParser)
class MyHTMLParser(HTMLParser): def __init__(self): super().__init__() self.__parsertag = \'\' self.info = [] def handle_starttag(self, tag, attrs): if (\'class\', \'event-title\') in attrs: self.__parsertag = \'name\' if tag == \'time\': self.__parsertag = \'time\' if (\'class\', \'say-no-more\') in attrs: self.__parsertag = \'year\' if (\'class\', \'event-location\') in attrs: self.__parsertag = \'location\' def handle_endtag(self,tag): self.__parsertag = \'\' def handle_data(self, data): if self.__parsertag == \'name\': self.info.append(f\'会议名称:{data}\') if self.__parsertag == \'time\': self.info.append(f\'会议时间:{data}\') if self.__parsertag == \'year\': data=data.strip()#消去前后多余的空格 # 由于会检出几个匹配但错误的str,所以用正则表达式加以检验 if re.match(\'^\d{4}$\',data): self.info.append(f\'会议年份:{data}\') if self.__parsertag == \'location\': self.info.append(f\'会议地点:{data}\n\')
编写MyHTMLParser有很多细节:
下边我用红色标记关键字,用蓝色标记方法
1、补充属性__parsertag与info
__parsertag用来标记当前语句块对应的信息名字,如\'name\'、\'time\'……
info为list对象,用来存放所有我们提取的信息
2、重写三个方法handle_starttag、handle_endtag、handle_data
handle_starttag(self,tag,attrs):当遇到starttag(<tag attrs>)时,运行该方法,参数中的tag为源码中<后的标识符,attrs为补充参数
handle_endtag(self):遇到endtag(</tag>,与starttag相对应)时,运行该方法
handle_data(self,data):遇到中间的data(没有被<>包括的部分都是data)时,运行该方法
我们需要的内容,都在handle_data方法中利用参数data提取。而handle_starttag则用来标识这个data是属于哪部分的,用属性__parsertag记录下这部分data所属的tag。handle_endtag是在读到代码块的末尾时,用来复原__parsertag的,以便下个模块可以重复上述操作。
下边我们以源代码中的一个html块来解读handle_starttag、handle_data、handle_endtag
从这部分块中提取到的tag被我们标记为"name",对应的data为PyConDE & PyData Berlin 2020 (cancelled)
务必要记住,块的starttag是<tag attrs>格式,endtag是</tag>,data是没有被<>包括的部分。
①HTML解析器在运行到<h3 ...>时,识别出这是starttag,于是调用handle_starttag方法进行处理。
<h3 class=\'event-title\'>经过解码后变为,tag=\'h3\',attrs=(\'class\' , \'event-title\')。这里的tag与attrs就是handle_starttag参数中传入的tag和attrs。更广泛的来看,<X Y=Z>类型的starttag都会被解码为tag=\'X\' , attrs=(\'Y\',\'Z\')的形式。
这样我们就可以很方便地对解码后的传入参数tag与attrs进行分析,而不用考虑html代码了。
②注意到想要提取的\'name\'内容都是以<h3 class=\'event-title\'>为starttag的,我们就可以在handle_starttag中对这部分的内容标记为\'name\';由于tag不具有代表性(比如此处的tag为h3,但是其他代码段也有h3,所以就不能用h3作为判断标志),我们用attrs作为判断标志:
if (\'class\',\'event-tile\') in attrs: self.__parsertag=\'name\'
之所以这样写,可以再回头看一下我在①中第二段所写的内容。
③HTML解析器运行到PyConDE & PyData Berlin 2020 (cancelled)时,由于该部分内容没有被<>包括,所以识别出这部分为data,于是调用handle_data方法进行处理。
if self.__parsertag==\'name\': self.info.append(f\'会议名称:{data}\')
由于在handle_starttag中我们已经将此部分的内容用内部变量__parsertag标注为\'name\',所以进行处理时,只需要用if语句将__parsertag与\'name\'进行匹配,匹配成功就可以继续我们想对\'name\'块的data所作的处理了。由于我们的最终目的是输出,所以我们把这部分的data格式化后加入list中。
④HTML解析器当运行到</h3>时,识别出这是endtag,就调用方法handle_endtag进行处理。由于接下来还有html代码块要进行类似上述流程的处理,所以我们把参数初始化,本例中只需要初始化__parsertag就可以了
self.__parsertag=\'\'
step3、抓取该网页的所有内容——GET请求
如果不清楚如何用GET请求抓取某个网页的所有内容,可以参考访问网页的两种方式:GET与POST
我们在step2中说的那么多,前提是已经把网页内容获取了,而获取网页内容的函数如下:
def GetInfo_URL(URL): headers={\'User-Agent\':\'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36\'} #为了安全起见,最好用try...except语句块进行网页请求 try: req=request.Request(URL,headers=headers) with request.urlopen(req) as res: #输出请求状态 print(f\'Status:{res.status},{res.reason}\') data=res.read() return data.decode() except error.URLError as e: print(e)
step4、在main函数中运行
if __name__==\'__main__\': URL=\'https://www.python.org/events/python-events/\' data=GetInfo_URL(URL) #创建HTML解析器 parser=MyHTMLParser() #对抓取的数据进行解析,feed方法 #如果数据太长,一次装不下,可以多次feed parser.feed(data) for s in parser.info: print(s)
这部分也有几点需要注意的地方:
①创建HTML解析器,用我们自写的MyHTMLParser创建一个实例
parser=MyHTMLParser()
②装载数据+解析——feed()方法
parser.feed(data)
如果data太长一次放不下,可以多次feed,结果和一次feed全部相同
③输出结果
for s in parse.info: print(s)
完整代码:
from html.parser import HTMLParser from urllib import request, error import re class MyHTMLParser(HTMLParser): def __init__(self): super().__init__() self.__parsertag = \'\' self.info = [] def handle_starttag(self, tag, attrs): if (\'class\', \'event-title\') in attrs: self.__parsertag = \'name\' if tag == \'time\': self.__parsertag = \'time\' if (\'class\', \'say-no-more\') in attrs: self.__parsertag = \'year\' if (\'class\', \'event-location\') in attrs: self.__parsertag = \'location\' def handle_endtag(self,tag): self.__parsertag = \'\' def handle_data(self, data): if self.__parsertag == \'name\': self.info.append(f\'会议名称:{data}\') if self.__parsertag == \'time\': self.info.append(f\'会议时间:{data}\') if self.__parsertag == \'year\': data=data.strip()#消去前后多余的空格 # 由于会检出几个匹配但错误的str,所以用正则表达式加以检验 if re.match(\'^\d{4}$\',data): self.info.append(f\'会议年份:{data}\') if self.__parsertag == \'location\': self.info.append(f\'会议地点:{data}\n\') def GetInfo_URL(URL): headers = { \'User-Agent\': \'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36\'} # 为了安全起见,最好用try...except语句块进行网页请求 try: req = request.Request(URL, headers=headers) with request.urlopen(req) as res: # 输出请求状态 print(f\'Status:{res.status},{res.reason}\') data = res.read() return data.decode() except error.URLError as e: print(e) if __name__ == \'__main__\': URL = \'https://www.python.org/events/python-events/\' data = GetInfo_URL(URL) # 创建HTML解析器 parser = MyHTMLParser() # 对抓取的数据进行解析,feed方法 # 如果数据太长,一次装不下,可以多次feed parser.feed(data) for s in parser.info: print(s)