【原创】Python网络爬虫

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

2020-04-09  本文已影响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)
上一篇下一篇

猜你喜欢

热点阅读