大数据时代,各行各业对数据采集的需要日益增多,网络爬虫的使用也更为宽泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前曾经推出不少爬虫进阶、逆向相干文章,为实现从易到难全方位笼罩,特设【0根底学爬虫】专栏,帮忙小白疾速入门爬虫,本期为网页解析库的应用。

概述

前几期的文章中讲到了网络申请库的应用,咱们曾经可能应用各种库对指标网址发动申请,并获取响应信息。本期咱们会介绍各网页解析库的应用,解说如何解析响应信息,提取所需数据。

XPath的应用

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。同样,XPath 也反对HTML文档的解析。

介绍

XPath 应用门路表达式来匹配HTML文档中的节点或节点集,门路表达式基于HTML文档树,因而在学习XPath 时须要对网页构造有一个初步理解,对于网页构造这些在之前的文章《网页根本构造》中曾经介绍到了。

装置

应用XPath 须要装置Python的第三方库lxml,能够应用命令pip install lxml进行装置

应用

下文中,咱们会通过一个示例来理解xpath的用法。

<div id="box">    <p name="test">这是一个测试网页1</p></div><p name="test">这是一个测试网页2</p><div id="city">    <ul>        <li id="u1">北京</li>        <li id="u2">上海</li>        <li id="u3">广州</li>        <li id="u4"><a name="sz" href="sz.html" target="_self">深圳</a></li>    </ul></div><div class="article"><h3>题目</h3></div><div class="article"><p>内容1</p></div><div class="article"><p>内容2</p></div><div class="article"><p>内容3</p></div>

这是一个简略的网页body构造。咱们想要提取页面中的信息,就须要先剖析它的构造,理清构造后,编写门路表达式就会更加不便。

在前文对xpath的介绍中咱们理解到xpath是对XML或HTML文档进行解析的性能,但在代码中,示例中的html文本只是一段字符串,所以在应用xpath进行匹配前首先要将字符串转成HTML对象。

from lxml import etreeelement = '''    <div id="box">        <p name="test">这是一个测试网页1</p>    </div>    <p name="test">这是一个测试网页2</p>    <div id="city">        <ul>            <li id="u1">北京</li>            <li id="u2">上海</li>            <li id="u3">广州</li>            <li id="u4"><a name="sz" href="sz.html" target="_self">深圳</a></li>        </ul>    </div>    <div class="article"><h3>题目</h3></div>    <div class="article"><p>内容1</p></div>    <div class="article"><p>内容2</p></div>    <div class="article"><p>内容3</p></div>'''html = etree.HTML(element)print(html)#输入:<Element html at 0x1b642114388>

将文本转化为html对象后,就能够应用xpath进行匹配了。

门路表达式

表达式形容示例示例形容
nodename选取此节点下的所有子节点head获取以后head节点下的所有子节点
/从根节点选取/html/head从根节点匹配head节点
//从任意地位匹配节点//head匹配任意head节点
.选取以后节点
..选取以后节点的父节点//head/..匹配head节点的父节点
@选取属性//div[@id="box"]匹配任意id值为box的div标签

选取节点

以示例代码为例,咱们想要匹配所有的li标签,能够这样实现:

html.xpath("//li")#输入 [<Element li at 0x24c09d3e5c8>, <Element li at 0x24c09d3e588>, <Element li at 0x24c09d3e648>, <Element li at 0x24c09d3e688>]

谓语

获取id属性值为box的div标签信息

html.xpath('//div[@id="box"]')#输入 [<Element div at 0x127672dc688>]

获取所有class属性值为article的标签信息

html.xpath('//*[@class="article"]')#输入 [<Element div at 0x2898696e4c8>, <Element div at 0x2898696e588>, <Element div at 0x2898696e5c8>, <Element div at 0x2898696e608>]

获取所有class属性值为article的标签下h3标签的文本信息

html.xpath('//*[@class="article"]/h3/text()')#输入 ['题目']

获取所有class属性值为article的标签下p标签的文本信息

html.xpath('//*[@class="article"]/p/text()')#输入 ['内容1', '内容2', '内容3']

获取第一个li标签的文本信息

html.xpath('//li[1]/text()')#输入 ['北京']

获取最初一个li标签下的所有文本信息

html.xpath('//li[last()]//text()')#输入 ['深圳']

获取倒数第二个li标签下的所有文本信息

html.xpath('//li[last()-1]//text()')#输入 ['广州']

获取前两个li标签下的文本信息

html.xpath('//li[position()<3]//text()')#输入 ['北京', '上海']

选取多个门路

html.xpath('//div[@class="article"]/h3/text() | //div[@id="box"]/p/text()')#输入 ['这是一个测试网页1', '题目']

轴可定义绝对于以后节点的节点集。

轴名称后果
ancestor选取以后节点的所有先辈(父、祖父等)。
ancestor-or-self选取以后节点的所有先辈(父、祖父等)以及以后节点自身。
attribute选取以后节点的所有属性。
child选取以后节点的所有子元素。
descendant选取以后节点的所有后辈元素(子、孙等)。
descendant-or-self选取以后节点的所有后辈元素(子、孙等)以及以后节点自身。
following选取文档中以后节点的完结标签之后的所有节点。
namespace选取以后节点的所有命名空间节点。
parent选取以后节点的父节点。
preceding选取文档中以后节点的开始标签之前的所有节点。
preceding-sibling选取以后节点之前的所有同级节点。
self选取以后节点。

获取ul标签下的子li标签下的a标签的href属性

html.xpath('//ul/child::li/a/@href')#输入 ['sz.html']

获取a标签的所有先辈div标签

html.xpath('//a/ancestor::div')#输入 [<Element div at 0x23a712de588>]

获取a标签的所有属性

html.xpath('//a/attribute::*')#输入 ['sz', 'sz.html', '_self']

获取id属性值为u2的li标签之后的所有p标签的文本信息

html.xpath('//li[@id="u2"]/following::p/text()')#输入 ['内容1', '内容2', '内容3']

获取id属性值为u2的li标签之前的所有p标签的文本信息

html.xpath('//li[@id="u2"]/preceding::p/text()')#输入 ['这是一个测试网页1', '这是一个测试网页2']

获取id属性值为u2的li标签之后的所有同级标签的文本信息

html.xpath('//li[@id="u2"]/following-sibling::*/text()')#输入 ['广州']

获取id属性值为u3的li标签之前的所有同级标签的文本信息

html.xpath('//li[@id="u2"]/preceding-sibling::*/text()')#输入 ['北京']

运算符

获取id属性值为u1或者u2的标签下的文本信息

html.xpath('//li[@id="u1" or @id="u2"]/text()')#输入 ['北京', '上海']

判断a标签的name属性值是否为sz

html.xpath('//a/@name="sz"')#输入 True

函数

xpath提供了十分多的内置函数,这些函数能够用于各种值的计算与解决,这里只介绍罕用的函数。

获取所有属性值蕴含test的标签的文本信息

html.xpath('//*[contains(attribute::*,"test")]/text()')#输入 ['这是一个测试网页1', '这是一个测试网页2']

将id为u1的li标签和id为u2的li标签的文本信息进行拼接

html.xpath('concat(//li[@id="u1"]/text(),//li[@id="u2"]/text())')#输入 北京上海

获取id属性值以u结尾的所有li标签的文本信息

html.xpath('//li[starts-with(@id,"u")]/text()')#输入 ['北京', '上海', '广州']

上文中讲到的门路表达式的写法只是xpath中比拟罕用的写法,根本可能笼罩大部分需要。xpath门路也能够通过F12开发者工具间接获取,在 element 中右键须要匹配的节点元素,复制残缺xpath即可。复制下来的残缺xpath门路如:/html/body/ul/li[1]。这种办法尽管简略,但实际上门路并不精确,而且门路为绝对路径,绝对简单,所以门路表达式举荐本人手动编写。

BeautifulSoup的应用

BeautifulSoup与上文中介绍的xpath一样,都是用于解析XML或HTML标签中的信息。BeautifulSoup 与 xpath 各有劣势,应用哪个能够凭集体爱好。

装置

目前风行的 beautifulsoup 版本为beautifulsoup4,上面简称bs4。

pip install beautifulsoup4

应用

与 xpath 不同,bs4 须要本人抉择解析器,罕用的解析器有:

html.parser:Python内置解析器

lxml HTML:HTML解析器

lxml XML:XML解析器

各解析器之间的区别次要在于文档解析容错能力,对于不标准的 HTML 文本,它们的解析后果并不统一。

这里咱们举荐应用 lxml 作为解析器,应用 lxml 作为解析器须要提前装置 lxml 第三方库。

from bs4 import BeautifulSouphtml = '''    <div id="box">        <p name="test">这是一个测试网页1</p>    </div>    <p name="test">这是一个测试网页2</p>    <div id="city">        <ul>            <li id="u1">北京</li>            <li id="u2">上海</li>            <li id="u3">广州</li>            <li id="u4"><a name="sz" href="sz.html" target="_self">深圳</a></li>        </ul>    </div>    <div class="article"><h3>题目</h3></div>    <div class="article"><p>内容1</p></div>    <div class="article"><p>内容2</p></div>    <div class="article"><p>内容3</p></div>'''soup = BeautifulSoup(html,'lxml')#返回残缺的html文本

bs4的写法比拟简洁,更人性化。

soup.body :获取body信息soup.li :获取第一个li标签soup.div :获取第一个div标签soup.li['id'] :获取第一个li标签的id属性值soup.a.attrs :获取第一个a标签的所有属性值,返回类型为字典

获取多个信息

soup.find_all('li') :获取所有li标签soup.find_all(["p","a"]) :获取所有p标签与a标签soup.find_all('div','article') :获取所有类名为article的div标签soup.find_all(id="box") :获取所有id属性值为box的标签

节点

soup.ul.parent :获取ul标签的父节点soup.find('a').find_parent("li") :获取第一个a标签的父li标签soup.find('a').find_parents("li") :获取第一个a标签的所有父li标签soup.find('li').find_next_siblings("li") :获取第一个li标签后的兄弟li标签soup.find('li').find_next_sibling("li") :获取第一个li标签后的第一个兄弟li标签soup.find(attrs={'id':'u4'}).find_previous_siblings("li") :获取id属性值为u4的li标签前的所有兄弟li标签soup.find(attrs={'id':'u4'}).find_previous_sibling("li") :获取id属性值为u4的li标签前的第一个兄弟li标签

CSS选择器

BeautifulSoup 反对大部分的CSS选择器,CSS选择器在之前的文章《网页根本构造》中做了介绍。

soup.select('div h3') :获取div下的h3标签soup.select('#city #u2') :获取id属性值为city的标签下id值u2的标签soup.select('a[href="sz.html"]') :获取href属性值为sz.html的a标签soup.select_one(".article") :获取第一个类名为article的标签

bs4 作用上与 xpath 基本一致,然而 bs4 的劣势就在于语句的简洁性,用bs4匹配数据比 xpath 略微简略一些,然而它在性能上比 xpath 要稍弱。

re正则表达式的应用

正则表达式(Regular Expression,通常简写为“regex”或“regexp”)是一种用来匹配文本字符串的模式。在编程和文本处理中,正则表达式通常被用来进行字符串匹配、搜寻、替换等操作。

理论开发中,咱们会对一些非结构化数据进行解析,对于这类数据,无论是 xpath 还是 BeautifulSoup 都无奈进行解析。这时咱们就须要用到正则表达式,正则表达式的弱小在于它可能匹配任意类型的文本数据,能够帮忙开发者疾速的解决文本数据。

装置

Python 中内置了 re 库,无需额定装置

应用

模式形容
\w匹配字母数字及下划线
\W匹配非字母数字及下划线
\s匹配任意空白字符,等价于[\t\n\r\f]
\S匹配任意非空白字符
\d匹配任意数字,等价于[0-9]
\D匹配任意非数字的字符
\A匹配字符串的结尾
\Z匹配字符串的结尾,如存在换行,只匹配到换行前的完结字符串
\z匹配字符串的结尾,如存在换行,会匹配换行符
^匹配字符串的结尾
$匹配字符串的结尾
.匹配任意字符,除换行符,当re.DOTALL被指定时能够匹配包含换行符的任意字符
[...]匹配一组字符,如[abc],匹配a,b,c
1匹配不在[]中的字符
*匹配0或多个表达式
+匹配1或多个表达式
?对它后面的正则式匹配0到1次
{n}匹配n个之前的正则表达式
{n,m}对表达式进行n到m次匹配,尽量取最多
()匹配括号内的任意表达式

compile函数

re.compile 能够将正则表达式款式的字符串编译为一个正则表达式对象,能够通过这个对象来调用下述办法。

pattern = re.compile("\d")

match函数

re.match 会从字符串的起始地位进行匹配正则表达式,匹配胜利后会返回匹配胜利的后果,匹配失败则返回None。

import re#匹配字符apattern = re.compile("a") #从字符串结尾开始匹配print(pattern.match("cat")) #从下标为1的地位开始匹配print(pattern.match("cat",1)) 

运行后果:

None<re.Match object; span=(1, 2), match='a'>

表达式匹配:

#以hello结尾两头为数字前面是World的字符串pattern = re.compile("^hello\s(\d+)\sWorld") print(pattern.match("hello 123 World!!!")) 

运行后果:

<re.Match object; span=(0, 15), match='hello 123 World'>

能够看到,两次的运行后果都是一个对象,能够应用group()办法获取匹配到的文本信息。

pattern = re.compile("^hello\s(\d+)\sWorld")result = pattern.match("hello 123 World!!!")print(result.group())print(result.group(1))

运行后果:

hello 123 World123

group(1)会返回第一个被括号突围的匹配后果,示例中被括号突围的是\d+,所以输入的后果为123。

Search函数

match函数是从字符串的结尾开始匹配,想要从其它中央开始匹配须要本人传入地位,用这种办法匹配数据局限性很大。咱们想要从任意地位开始匹配数据能够应用 re.search 函数。

pattern = re.compile("(\d+)")result = pattern.search("hello 123 World321!!!")print(result)print(result.group(1))

运行后果:

<re.Match object; span=(6, 9), match='123'>123

能够看到,应用 search 办法咱们无需指定地位,它会搜查整个字符串,返回第一个匹配胜利的后果。

findall函数

search 函数能够从任意地位进行匹配,然而它只会返回第一个匹配胜利的后果。咱们想要获取所有匹配胜利的后果就须要用到 findall 函数。

pattern = re.compile("(\d+)")result = pattern.findall("hello 123 World321!!!")print(result)

运行后果:

['123', '321']

findall 函数会返回一个列表,因而 findall 函数返回的后果无奈应用 group 办法。

通用匹配

在理论开发中,咱们往往会遇到非常复杂的文本构造如:

"hello 123 this is a 999Regex Demo!!!World321"

这时如果应用\w,\s进行匹配会显得非常复杂,这时咱们就能够应用一个通配组合.*,上文中介绍到了.是匹配任意字符,*是匹配0或多个表达式,.*搭配在一起就是匹配任意多个字符。应用.*能够简略无效的进行数据匹配。

#匹配hello World之间的信息pattern = re.compile("hello(.*)World")result = pattern.findall("hello123 World this is a 999Regex Demo!!!World321")print(result)

运行后果:

['123 World this is a 999Regex Demo!!!']

这里咱们能够看到,应用.*后它匹配到了第一个 hello 到最初一个 World 之间的所有文本。但如果咱们想要匹配 hello 到第一个 world 之间的信息呢。这时咱们就须要理解一下贪心匹配与非贪心匹配。

贪心与非贪心

顾名思义,贪心模式示意尽可能多的匹配,非贪心模式示意尽可能少的匹配。从上文的示例咱们能够看到re.compile("hello(.*)World")它会从 hello 开始,匹配到最初一个 World,这显然就是贪心模式,它会尽可能多的匹配,也就是匹配到最初一个合乎规定的地位。正则表达式中,非贪心模式须要应用到?,前文中讲到了?是匹配0到1次,应用?就能实现尽可能少的匹配。

#匹配hello World之间的信息(非贪心)pattern = re.compile("hello(.*?)World")result = pattern.findall("hello123 World this is a 999Regex Demo!!!World321")print(result)

运行后果:

['123 ']

能够看到应用.*?后,正则表达式只匹配到第一个 World 就停下了,这样就实现了非贪心匹配。

Newspaper智能解析库的应用

Newspaper 是 Python 的第三方库,次要用于抓取新闻网页。它可能主动解析网页内容,匹配出新闻的各种信息。而且操作简略,非常容易上手。然而它并不适用于理论开发,因为它不够稳固,存在各种问题,无奈应答爬虫开发中可能遇到的问题,如反爬虫等,所以这里对它只做介绍。

装置

命令行装置:pip install newspaper3k

应用

它的应用非常简单,传入指标网址后,调用 download() 办法下载网页源代码,应用 parse() 办法解析源码。

from newspaper import Article# 指标新闻网址url = 'https://指标文章'news = Article(url, language='zh')news.download()news.parse()#获取新闻网页源码print(news.html)#获取新闻标题print(news.title)#获取新闻注释print(news.text)#获取新闻作者print(news.authors)#获取新闻公布工夫print(news.publish_date)#获取新闻关键词print(news.keywords)#获取新闻摘要print(news.summary)#获取新闻配图地址print(news.top_image)#获取新闻视频地址print(news.movies)

Newspaper 能够与 requests 配合应用,通过 requests 获取源码,由 Newspaper 进行解析提取。Newspaper 库并不能完满的解析出各种信息,适宜非专业人士应用。

总结

上文中,讲到了四个爬虫解析库的应用,其中 xpath 与 beautifulSoup 次要用于对 html 文本的解析,正则表达式次要用于对非结构化文本的解析,无奈用 xpath 和 beautifulSoup 解析的文本信息通常会应用正则来进行匹配。Newspaper 是智能解析库,应用它能够主动解析新闻信息,无需本人编写表达式,然而毛病也很显著 。

与网络申请库一样,网页解析库的应用是每一个爬虫初学者都应该牢牢把握的知识点,可能纯熟的应用解析库能力更好的实现数据采集工作。


  1. ... ↩