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

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容