两种方式提取网页信息——爬虫初步

问题:对网页Python会议,用浏览器查看源码;尝试解析HTML,输出Python官网发布的会议时间、名称和地点

 

准备工作:

①打开网页后,需要提取的信息

②按F12进入开发者模式,找到这部分的源代码

复制代码
<li>
                        <h3 class="event-title"><a href="/events/python-events/883/">PyConDE &amp; 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('&ndash;','-')) #把'-'的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、补充属性__parsertaginfo

__parsertag用来标记当前语句块对应的信息名字,如'name'、'time'……

infolist对象,用来存放所有我们提取信息

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_starttaghandle_data、handle_endtag

从这部分块中提取到的tag被我们标记为"name",对应的dataPyConDE & 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参数传入tagattrs。更广泛的来看,<X Y=Z>类型的starttag都会被解码为tag='X' , attrs=('Y','Z')的形式。

这样我们就可以很方便地对解码后的传入参数tagattrs进行分析,而不用考虑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)
复制代码

 

posted @   ShineLe  阅读(2054)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示