两种方式提取网页信息——爬虫初步
问题:对网页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)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性