使用上节的urllib,可以简单地模拟用户请求,获取完整的网页内容,但大多数时候,我们可能只需要其中的一部分,这就涉及到数据解析了。本节讲解一波「XPath语法」及Python中支持此语法的「解析库lxml」。



python获取子窗口句柄 名称_lxml安装

lxml库安装及使用


lxml是Python中的一个解析库,底层由C语言实现,支持XML及HTML的解析,支持XPath解析方式,解析效率非常高。 安装方法非常简单,直接通过pip命令安装即可:

pip install lxml


利用lxml写个简单的例子提取百度Logo地址:

from urllib import requestfrom lxml import etree
resp = request.urlopen("https://www.baidu.com").read().decode('utf-8')# 传入html文本,得到一个可进行xpath的对象
selector = etree.HTML(resp) # 编写提取图标路径的XPath表达式
xpath_reg = '/html/head/link[@rel="icon"]/@href'
results = selector.xpath(xpath_reg)
print(results[0])# 运行结果如下:
//www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg# 如果是从本地文件中读取
local_selector = etree.parse('hello.html')



python获取子窗口句柄 名称_lxml安装

XPath概念与简介

// 概念 //


全称XML Path Language,XML路径语言,一门在XML文档中查找信息的语言,起初是用来搜寻XML文档的,但同样适用于HTML文档的搜索。


// 简介 //


XPath选择功能强大,提供非常简明的「 路径选择表达式

」,除此之外还提供了超过100个的内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。

XPath于1999年11月16日成为W3C标准,它被设计为供XSLT、XPointer以及其他XML解析软件使用,想了解更多可移步至官方网站:https://www.w3.org/TR/xpath/



python获取子窗口句柄 名称_lxml安装

 XPath语法详解

//  结点关系 //


在Xpath中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释及文档节点(或称根节点)。说完节点类型,再来说下节点关系,比如下面的XML文档:


<executions><execution><id>make-assemblyid><phase>packagephase><goals><goal>singlegoal>goals>execution>executions>


有如下这些节点关系:

  • 父(Parent) → 每个元素及属性都有一个父:execution是id、phase、goals及goal的父;
  • 子(Children) → 元素节点可能有零个或多个子:id、phase、goals及goal是execution的子;
  • 同胞(Sibling) → 拥有相同的父的节点:id、phase、goals及goal是execution的同胞;
  • 先辈(Ancestor) → 某节点的父、父的父,等等:goals的先辈是execution及executions;
  • 后代(Descendant) → 某节点的子,子的子,等等:execution的后代是id、phase、goals及goal;

// 选取节点 //


XPath使用路径表达式在XML文档中选取节点,节点是 沿着路径

Step

来选取的(上述子元素从上往下找节点),接着说下具体的路径表达规则: ----------------

最有用


表达式

描述

nodename

此节点的所有子节点

/

绝对路径,从根节点选取

//

相对路径,从匹配选择的当前节点选择文档中的节点,不考虑位置

.

选取当前节点

..

选取当前节点的父节点

@

选取属性

----------------

谓语(Predicates)

谓语嵌在方括号中,用来查找某个特定结点或包含某个指定值的节点。

表达式

描述

/a/b[1]

选取a子元素的 第一个 b元素

/a/b[last()]

选取 a 子元素的 最后一个 b元素

/a/b[last()-1]

选取 a 子元素的 倒数第二个 b元素

/a/b[postion()<3]

选取 a 子元素的 最前面两个 b元素

//a[@id]

选取所有拥有属性id的a元素

/a[@id='test']

选取所有拥有属性id,且值为test的a元素

/a/b[size>10]

选取a子元素中所有b元素,且其中的size元素值须大于10

/a/b[size>10]/c

选取a子元素中所有b元素(size元素值大于10) 的所有c元素

----------------

选取未知节点


表达式

描述

*

匹配任何元素节点

@*

匹配任何属性节点

node()

匹配任何类型的结点

----------------

选取若干路径

可在路径表达式中使用" |

" 运算符

来选取若干个路径,如: //a/b | //a/c → 选取a元素中所有的b和c元素 ----------------

当上述操作都不能定位时,可以考虑根据节点的父节点或兄弟结点来定位,此时就会用到Xpath轴,利用轴可定位某个相对于当前节点的节点集,语法: 轴名称::元素名

,具体规则如下表所示:

轴名称

描述

child

选取当前节点的所有子元素

parent

选取当前节点的父节点

descendant

选取当前节点的所有后代元素(子、孙等)

descendant-or-self

选取当前节点的所有后代元素(子、孙等)以及当前节点本身

ancestor

选取当前节点的所有先辈(父、祖父等)

ancestor-or-self

选取当前节点的所有先辈(父、祖父等)以及当前节点本身

preceding

选取文档中当前节点的开始标签之前的所有节点

preceding-sibling

选取当前节点之前的所有同级节点

following

选取当前节点的结束标签之后的所有节点

following-sibling

选取当前节点之后的所有同级节点

self

选取当前节点

attribute

选取当前节点的所有属性

namespace

选取当前节点的所有命名空间节点


使用代码示例如下:

# 先定位到图标,然后获取同级结点,然后定位到name为description的meta结点
xpath_reg = '//link[@rel="icon"]/preceding::meta[@name="description"]/@content'# 运行结果如下:
全球最大的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。


----------------

运算符

除了上面介绍的 "|" 运算符外,还有其他的XPATH运算符,此处罗列下:

运算符

描述

+-*

加减乘

div


= !=

等于、不等于

< <= > >=

小于、小于等于、大于、大于等于

or and

或、与

mod

计算除法余数

----------------

函数

除了上面的last()、postion()函数外,还有:

  • text():获得元素文本内容;
  • contains(string1,string2):前后是否匹配,匹配返回True,否则返回False;
  • starts-with():从起始位置匹配字符串;


更多XPath函数及用法,可移步至下述网站自行查阅:

https://www.w3school.com.cn/xpath/xpath_functions.asp

// 调试小技巧 //


可在Chrome浏览器直接测试编写的XPath表达式,看下是否定位成功,在开发者工具的Elements选项卡按下Ctrl+F,然后在底部输入XPath表达式即可定位,示例如下:


python获取子窗口句柄 名称_lxml安装_04


你也可以在此测试编写的xpath表达式能否 定位到目标元素,而不用上来就写程序测试。另外,你还可以右键目标元素,直接获取Xpath,当然不一定是最优的。


python获取子窗口句柄 名称_asp子窗口读取父窗口数据_05



python获取子窗口句柄 名称_lxml安装

实战:爬取小说站点

目标站点

https://www.biqukan.com

爬取内容

爬取某部小说的所有章节,把小说内容保存为本地txt文件(以小说元尊为例);

// ① 提取章节数据列表 //

python获取子窗口句柄 名称_pbdom 不能解析gbk_07


如图,需提取正文卷所有章节,打开Chrome开发者工具,点击左上角的箭头

python获取子窗口句柄 名称_asp子窗口读取父窗口数据_08


然后点击网页上的《元尊》正文卷,快速定位到下述结点:

python获取子窗口句柄 名称_asp子窗口读取父窗口数据_09

需要获得《元尊》正文卷后所有dd节点里的a节点,不难写出这样的XPath表达式:

//dt[text()="《元尊》正文卷"]/following-sibling::dd/a

把表达式放到Chrome浏览器试试看:

python获取子窗口句柄 名称_lxml安装_10

也可以写下代码简单验证下:

from urllib import request, parsefrom lxml import etreeimport ssl# 取消SSL的证书验证
ssl._create_default_https_context = ssl._create_unverified_context
base_url = "https://www.biqukan.com"
novel_url = base_url + "/0_790/"  # 小说《元尊》的链接
novel_headers = {'Host': parse.urlparse(base_url).netloc,'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ''Chrome/86.0.4240.80 Safari/537.36 '
}# 提取章节def fetch_chapter():
    req = request.Request(novel_url, headers=novel_headers)
    resp = request.urlopen(req).read().decode('gbk')
    selector = etree.HTML(resp)
    chapter_xpath_reg = '//dt[text()="《元尊》正文卷"]/following-sibling::dd/a'
    results = selector.xpath(chapter_xpath_reg)for result in results:
        print(result.text, result.attrib.get('href'))# 提取内容if __name__ == '__main__':
    fetch_chapter()

运行结果如下图所示:

python获取子窗口句柄 名称_asp子窗口读取父窗口数据_11

// ② 提取章节内容 //

提取完章节列表,接着提取章节内容,打开某个章节的链接,如:

https://www.biqukan.com/0_790/63024951.html

python获取子窗口句柄 名称_pbdom 不能解析gbk_12

快速定位到:

python获取子窗口句柄 名称_pbdom 不能解析gbk_13


这个XPath表达式也很好写,直接定位到

//div[@id="content"]/text()

写下简单的代码验证下:

def fetch_content(novel_url):
    req = request.Request(novel_url, headers=novel_headers)
    resp = request.urlopen(req).read().decode('gbk')
    selector = etree.HTML(resp)
    content_xpath_reg = '//div[@id="content"]/text()'
    results = selector.xpath(content_xpath_reg)for result in results:
        print(result)if __name__ == '__main__':
    fetch_content('https://www.biqukan.com/0_790/63024951.html')

运行结果如下图所示:

python获取子窗口句柄 名称_pbdom 不能解析gbk_14

可以,提取到小说内容了,接着加上保存txt,完善下小细节,写出完整代码。

// ③ 编写完整代码 //

# 爬取小说站点from urllib import request, parse, errorfrom lxml import etreeimport sslimport osimport timefrom random import randint# 取消SSL的证书验证
ssl._create_default_https_context = ssl._create_unverified_context
base_url = "https://www.biqukan.com"
novel_url = base_url + "/0_790/"  # 小说《元尊》的链接
novel_list = []
novel_save_dir = os.path.join(os.getcwd(), 'novel_cache/') # 小说的保存文件夹
novel_headers = {'Host': parse.urlparse(base_url).netloc,'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ''Chrome/86.0.4240.80 Safari/537.36 '
}# 保存小说信息的类class Novel:
    chapter_name = ''
    chapter_url = ''def __init__(self, chapter_name, chapter_url):
        self.chapter_name = chapter_name
        self.chapter_url = base_url + chapter_url# 提取小说章节列表def fetch_chapter():
    print("爬取:", novel_url)
    req = request.Request(novel_url, headers=novel_headers)
    resp = request.urlopen(req).read().decode('gbk')
    selector = etree.HTML(resp)
    chapter_xpath_reg = '//dt[text()="《元尊》正文卷"]/following-sibling::dd/a'
    results = selector.xpath(chapter_xpath_reg)
    data_list = []for result in results:
        data_list.append(Novel(result.text, result.attrib.get('href')))return data_list# 提取小说内容def fetch_content(novel):
    print("爬取:", novel.chapter_url, end="\t")
    req = request.Request(novel.chapter_url, headers=novel_headers)
    resp = request.urlopen(req).read().decode('gbk')
    selector = etree.HTML(resp)
    content_xpath_reg = '//div[@id="content"]/text()'
    results = selector.xpath(content_xpath_reg)
    content = ''for result in results[:-2]:
        content = content + result + "\n"
    save_novel(content, novel.chapter_name)# 将章节内容写入txtdef save_novel(content, novel_name):try:with open(novel_save_dir + novel_name + '.txt', 'w+', encoding='utf-8') as f:
            f.write(content)except (error.HTTPError, OSError) as reason:
        print(str(reason))else:
        print("下载完成:" + novel_name)# 提取内容if __name__ == '__main__':# 判断存储小说的目录是否存在,不存在创建if not os.path.exists(novel_save_dir):
        os.mkdir(novel_save_dir)
    novel_list = fetch_chapter()for n in novel_list:# 随机休眠0-3s,防止ip被封
        time.sleep(randint(0, 3))
        fetch_content(n)

运行结果如下所示:

python获取子窗口句柄 名称_asp子窗口读取父窗口数据_15