美文网首页【原创】Python网络爬虫
Python爬虫-数据解析学习笔记之xpath

Python爬虫-数据解析学习笔记之xpath

作者: 复苏的兵马俑 | 来源:发表于2020-04-09 11:00 被阅读0次

    1、xpath学习笔记

    1)xpath描述

      xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。

    2)xpaht开发工具

      A)Chrome插件XPath Helper
      B)Firefox插件Try XPath

    3)xpath语法

    选取节点:xpath使用路径表达式来选取XML文档中的节点或节点集。
      A)当/在最前面时,选取根节点下面的元素,否则选取某个节点下面的元素,然后写标签名,再写谓词进行选取,示例代码如下:
      /div[@class = 'abc']
      /div/tr[@class = 'xyz']
      B)使用//选取整个页面当中的元素,不管在任何位置都是,然后写标签名,再写谓词进行选取,示例代码如下:
      //div[@class = 'abc']
      //div//tr[@class = 'xyz']
      C)使用@选取某个节点的属性,使用中括号[]括起来,示例代码如下:
      //div[@class = 'abc']
      D)使用.指定当前节点,示例代码如下:
      ./div[@class = 'abc']

    谓词用法:谓词用来查找某个特定的节点或包含某个指定的值的节点,被嵌在中括号[]中。
      A)选取某个节点下的第一个节点元素,示例代码如下:
      //div//tr[1]
      B)选取某个节点下的倒数第二个节点元素,示例代码如下:
      //div//tr[last()]
      C)选取某个节点下的前面两个节点元素,示例代码如下:
      //div//tr[position() < 3]
      D)选取某个节点下拥有某个属性的节点元素,示例代码如下:
      //div//tr[@class]
      E)选取某个节点下等于某个值的所有属性的节点元素,示例代码如下:
      //div//tr[@class = 'abc]

    通配符:使用*代表通配符。
      A)使用*匹配任意节点,示例代码如下:
      //div/*
      B)使用@*匹配节点中的任意属性,示例代码如下:
      //div//tr[@*]

    选取多个路径:通过在路径表达式中使用|运算符,可以选取若干个路径,示例代码如下:
      //div//tr[last()] | //div//td[@class = 'abc] | //div//p

    需要注意的知识点
      1、///的区别:/代表只获取直接子节点,//代表获取子孙节点。一般//用的比较多,当然也要视情况而定。
      2、contains:有时候某个属性中包含了多个值,那么可以使用contains()函数,示例代码如下:
      //div[contains(@class, 'job_detail')]
      3、谓词中的下标是从1开始的,不是从0开始的。

    2、lxml学习笔记

    1)lxml描述

      lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
      lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,可以使用之前学习的XPath语法,来快速的定位特定元素以及节点信息。
      lxml Python 官方文档:http://lxml.de/index.html

    2)解析HTML字符串

      使用lxml.etree.HTML进行解析,示例代码如下:

    htmlElement = etree.HTML(text)
    print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))
    
    3)解析HTML文件

      使用lxml.etree.parse进行解析,示例代码如下:

    htmlElement = etree.HTML('tencent.html')
    print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))
    

      这个函数默认使用的是XML解析器,所以如果碰到一些不规范的HTML代码的时候就会解析错误,这时候就要自己创建HTML解析器,示例代码如下:

    parser = etree.HTMLParser(encoding = 'utf-8')
    htmlElement = etree.parse('lagou.html', parser = parser)
    print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))
    

    3、lxml与xpath结合

    1)获取所有li标签
     from lxml import etree
     html = etree.parse('hello.html')
     print type(html)  # 显示etree.parse() 返回类型
     result = html.xpath('//li')
     print(result)  # 打印<li>标签的元素集合
    
    2)获取所有li元素下的所有class属性的值
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li/@class')
     print(result)
    
    3)获取li标签下href为www.baidu.com的a标签
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li/a[@href="www.baidu.com"]')
     print(result)
    
    4)获取li标签下所有span标签
     from lxml import etree
     html = etree.parse('hello.html')
     #result = html.xpath('//li/span')
     #注意这么写是不对的:
     #因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
     result = html.xpath('//li//span')
     print(result)
    
    5)获取li标签下的a标签里的所有class
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li/a//@class')
     print(result)
    
    6)获取最后一个li的a的href属性对应的值
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li[last()]/a/@href')
     # 谓语 [last()] 可以找到最后一个元素
     print(result)
    
    7)获取倒数第二个li元素的内容
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li[last()-1]/a')
     # text 方法可以获取元素内容
     print(result[0].text)
    
    8)获取倒数第二个li元素的内容的第二种方式
     from lxml import etree
     html = etree.parse('hello.html')
     result = html.xpath('//li[last()-1]/a/text()')
     print(result)
    
    9)注意事项

      A)使用xpath语法,应该使用Element.xpath方法来执行xpath的选择,xpath函数返回的永远都是一个列表;
      B)获取文本,是通过xpath中的text()函数;
      C)在某个标签下,再执行xpath函数,获取这个标签下的子孙元素时,应该在//之前加一个点.,代表是在在当前元素下获取。

    4、实战示例

      使用requestsxpath爬取“电影天堂”中“最新电影”的所有数据,示例代码如下:

    import requests
    from lxml import etree
    
    def url_status_code(target_url, headers):
        '''
        获取target_url的status_code(即url访问状态码),常用状态码如下:
        200:返回消息为“OK”,描述-请求被确认
        201:返回消息为“Created”,描述-请求是完整的,新的资源被创建
        202:返回消息为“Accepted”,描述-请求被接受,但未处理完
        301:返回消息为“Moved Permanently”,描述-被请求的页面已经移动到了新的URL下
        302:返回消息为“Found”,描述-被请求的页面暂时性地移动到了新的URL下
        303:返回消息为“See Other”,描述-被请求的页面可以在一个不同的URL下找到
        400:返回消息为“Bad Request”,描述-服务器无法识别请求
        403:返回消息为“Forbidden”,描述-禁止访问所请求的页面
        500:返回消息为“Internal Server Error”,描述-请求不完整,服务器遇见了出乎意料的状况
        502:返回消息为“Bad Gateway”,描述-请求不完整,服务器从上游服务器接受了一个无效的响应
        503:返回消息为“Service Unavailable”,描述-请求不完整,服务器暂时重启或关闭
        504:返回消息为“Gateway Timeout”,描述-网关超时
        :param target_url: (类型:字符串)指定的url地址
        :param headers: (类型:字典)设置的请求头信息
        :return: (类型:整型数值)返回指定url的请求状态码
        '''
        response = requests.get(target_url, headers=headers)    # 使用requests的get()方法请求target_url
        status_code = response.status_code  # 获取url访问的请求状态码,并赋值给status_code
        return status_code  # 返回指定url的请求状态码
    
    def get_page_url(base_domain, page_num_max, headers):
        '''
        获取指定页码数中每页的url地址,该函数为生成器。
        :param base_domain: (类型:字符串)访问站点的域名
        :param page_num_max: (类型:整型数值)最大获取页码数
        :param headers: (类型:字典)设置的请求头信息
        :return: (类型:字符串)返回每页的url地址
        '''
        page_num = 1
        while page_num <= page_num_max:
            page_url = base_domain + 'html/gndy/dyzz/list_23_{}.html'.format(page_num)  # 拼接每页的url地址
            status_code = url_status_code(page_url, headers)    # 获取指定url页面的请求状态码
            '''
            判断每个url页面的请求状态码是否为200,如果是,则返回,否则结束。
            '''
            if status_code == 200:
                page_num += 1
                yield page_url
            else:
                break
    
    def crawl_html(target_url, headers):
        '''
        获取target_url的内容,并返回
        :param target_url: (类型:字符串)指定的url地址
        :param headers: (类型:字典)设置的请求头信息
        :return: 返回target_url页面内容
        '''
        response = requests.get(target_url, headers = headers)
        text = response.content.decode('gbk', errors = 'ignore')
        return text
    
    def get_detail_url(text, base_domain):
        '''
        获取指定text中每个对象的详情页url,该函数为生成器。
        :param text: 指定的text内容
        :param base_domain: (类型:字符串)访问站点的域名
        :return: (类型:字符串)返回详情页url
        '''
        html = etree.HTML(text)
        tables = html.xpath("//table[@class='tbspan']")
        for table in tables:
            detail_url = base_domain + table.xpath(".//a/@href")[0]
            yield detail_url
    
    def parse_detail_url(detail_url, headers):
        '''
        解析详情页中的内容
        :param detail_url: (类型:字符串)指定的详情页url
        :param headers: (类型:字典)设置的请求头信息
        :return: (类型:字典)返回详情页中解析出来的内容
        '''
        movie = {}
        text = crawl_html(detail_url, headers)
        html = etree.HTML(text)
        title = html.xpath("//div[@class='title_all']//font[@color='#07519a']//text()")[0]
        movie['title'] = title
        zoom = html.xpath("//div[@id='Zoom']")[0]
        cover = zoom.xpath(".//img/@src")
        if cover != []:
            cover = cover[0]
        movie['cover'] = cover
        infos = zoom.xpath(".//text()")
    
        def parse_info(info, rule):
            return info.replace(rule, "").strip()
    
        for index, info in enumerate(infos):
            if info.startswith("◎译  名"):
                info = parse_info(info, "◎译  名")
                movie['translation'] = info
            elif info.startswith("◎片  名"):
                info = parse_info(info, "◎片  名")
                movie['name'] = info
            elif info.startswith("◎年  代"):
                info = parse_info(info, "◎年  代")
                movie['year'] = info
            elif info.startswith("◎产  地"):
                info = parse_info(info, "◎产  地")
                movie['country'] = info
            elif info.startswith("类  别"):
                info = parse_info(info, "类  别")
                movie['category'] = info
            elif info.startswith("语  言"):
                info = parse_info(info, "语  言")
                movie['language'] = info
            elif info.startswith("字  幕"):
                info = parse_info(info, "字  幕")
                movie['subtitles'] = info
            elif info.startswith("◎上映日期"):
                info = parse_info(info, "◎上映日期")
                movie['release_date'] = info
            elif info.startswith("◎IMDb评分"):
                info = parse_info(info, "◎IMDb评分")
                movie['IMDb'] = info
            elif info.startswith("◎豆瓣评分"):
                info = parse_info(info, "◎豆瓣评分")
                movie['douban_rating'] = info
            elif info.startswith("◎片  长"):
                info = parse_info(info, "◎片  长")
                movie['duration'] = info
            elif info.startswith("◎导  演"):
                info = parse_info(info, "◎导  演")
                movie['director'] = info
            elif info.startswith("◎编  剧"):
                info = parse_info(info, "◎编  剧")
                writers = [info]
                for x in range(index+1, len(infos)):
                    writer = infos[x].strip()
                    if writer.startswith("◎"):
                        break
                    writers.append(writer)
                movie['writers'] = writers
            elif info.startswith("◎主  演"):
                info = parse_info(info, "◎主  演")
                actors = [info]
                for x in range(index + 1, len(infos)):
                    actor = infos[x].strip()
                    if actor.startswith("◎"):
                        break
                    actors.append(actor)
                movie['actors'] = actors
            elif info.startswith("◎标  签"):
                info = parse_info(info, "◎标  签")
                movie['label'] = info
            elif info.startswith("◎简  介"):
                info = parse_info(info, "◎简  介")
                for x in range(index + 1, len(infos)):
                    profile = infos[x].strip()
                    if profile.startswith("【下载地址】"):
                        break
                    movie['profile'] = profile
        download_url = html.xpath("//td[@bgcolor='#fdfddf']/a/text()")
        movie['download_url'] = download_url
        return movie
    
    if __name__ == '__main__':
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.9 Safari/537.36',
            'Cookie': '37cs_pidx=1; 37cs_user=37cs20498016905; 37cs_show=253; UM_distinctid=171521ba5e1a5-0d67214e920904-721f3a40-13c680-171521ba5e27a; bz_finger=6133b30a7ebc8a1a448492bb4dbea710; CNZZDATA1260535040=1919690611-1586220307-https%253A%252F%252Fwww.dytt8.net%252F%7C1586225707; cscpvrich5041_fidx=4',
        }
        base_domain = 'https://www.dytt8.net/'
        page_num_max = 1  # 可以通过修改page_num_max的值来确定需要爬取总页数
        movies = []
        page_url_generator = get_page_url(base_domain, page_num_max, headers)
        for page_url in page_url_generator:
            text = crawl_html(page_url, headers)
            detail_url_generator = get_detail_url(text, base_domain)
            for detail_url in detail_url_generator:
                movie = parse_detail_url(detail_url, headers)
                movies.append(movies)
                print(movie)
        print(movies)
    

    相关文章

      网友评论

        本文标题:Python爬虫-数据解析学习笔记之xpath

        本文链接:https://www.haomeiwen.com/subject/yyeqmhtx.html