ShineLeBlog

问题:对网页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)

 

分类:

技术点:

相关文章:

  • 2022-12-23
  • 2021-05-19
  • 2021-08-20
  • 2021-12-20
  • 2021-06-16
  • 2021-05-24
  • 2021-04-01
猜你喜欢
  • 2021-09-19
  • 2021-11-27
  • 2022-02-16
  • 2021-12-20
  • 2021-11-13
相关资源
相似解决方案