共计 9644 个字符,预计需要花费 25 分钟才能阅读完成。
大数据时代,各行各业对数据采集的需要日益增多,网络爬虫的使用也更为宽泛,越来越多的人开始学习网络爬虫这项技术,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 etree
element = '''<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 BeautifulSoup
html = '''<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
#匹配字符 a
pattern = 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 World
123
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 是智能解析库,应用它能够主动解析新闻信息,无需本人编写表达式,然而毛病也很显著。
与网络申请库一样,网页解析库的应用是每一个爬虫初学者都应该牢牢把握的知识点,可能纯熟的应用解析库能力更好的实现数据采集工作。
- … ↩