《爬虫基础以及一个简单的实例》一文中,我们使用了正则表达式来解析爬取的网页。但是正则表达式有些繁琐,使用起来不是那么方便。这次我们试一下用Xpath选择器来解析网页。

首先,什么是XPath?XPathXML路径语言(XML Path Language),用于在XML文档中查找信息(在XML文档中对元素和属性进行遍历),也适用于HTML文档。

那么,怎样来选择我们想要的内容呢?常用的规则如下:(以下摘自:https://cuiqingcai.com/2621.html

选取节点:使用路径表达式

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

查找某个特定的节点或者包含某个指定的值的节点:使用谓语(注:谓语被嵌在方括号中)

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

选取未知节点:使用通配符

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

Xpath运算符

运算符 描述 实例 返回值
| 计算两个节点集 //book | //cd 返回所有拥有 book 和 cd 元素的节点集
+ 加法 6 + 4 10
减法 6 – 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等于 price=9.80 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
!= 不等于 price!=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
< 小于 price<9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
<= 小于或等于 price<=9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
> 大于 price>9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
>= 大于或等于 price>=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
and price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod 计算除法的余数 5 mod 2 1

节点之间的关系:这部分比较简单,稍微看一下https://cuiqingcai.com/2621.html上的例子就明白了。

1, 父(Parent)

2. 子(Children)

3. 同胞(Sibling)

4. 先辈(Ancestor) --- 包括父和父的父

5. 后代(Descendant) --- 包括子和子的子


一些路径表达式的例子:(摘自:https://www.jianshu.com/p/89c10770d72c

使用绝对路径:/html/body/div/form/input

绝对路径是从网页起始标签开始一直到要定位的元素的路径,如果要定位的元素在页面最下面,则这个Xpath路径会非常长。如果在要定位的元素与页面开始之间的元素有任何增减,元素定位就会失败。

使用相对路径://input

相对路径一般只包含与被定位元素关系最近的几层元素,相对路径写的好的话,页面变动影响最小,而且定位准确。

使用索引定位元素,索引的初始值为1://input[2]

如果一个页面中有多个相似的元素,或是一个层下面有多个同样的元素的时候,需要用索引的方法来定位,否则无法区分。

结合属性值来定位元素://input[@id='username']

属性定位也是比较常用的方法,如果元素中没有常见的id,name,class等直接有方法可调用的属性,也可以查找元素中是否有其他能唯一标识元素的属性,如果有,就可以用此方法定位。

使用多个属性定位元素://input[@id='username' and @name='userID']

多个属性联合定位,更能准确定位到元素。(注意:匹配多个属性:用and连接;  匹配属性的多个值:contains(..., ...)

使用属性名来定位元素://input[@button]

此方法可以区分同一种标签,含有不同属性名的元素。定位相对简单一些儿,但也同样存在着无法区分同种标签含有同种属性名的多个元素,这个时候要配合索引定位才行。

使用部分属性值匹配元素,用starts-with(),ends-with(),contains()://input[stars-with(@id,'user')]; //input[ends-with(@id,'name')]; //input[contains(@id,"ernam")]

此方法更加灵活,可以定位属性值不太规律,或是部分变动,中间有空格的情况。

使用任意属性值匹配元素://input[@*='username']

此方法相当于模糊查询,只要欲定位的标签,如input中任何属性值等于‘username’,就能匹配成功。缺点是可能会匹配含有这个属性值的其他元素,所以我们在定位的时候要查看一下这个元素值在页面中是否唯一。

使用文本匹配元素://input[contains(text(),'text')]

(注:获取元素的内容用text())

总结:用Xpath定位时,先看这个元素是否有明显的,唯一的属性值。如果有,我们就用相对路径加属性值定位,这是最简单准确的定位方法。如果要定位的元素不符合这个特征,例如:元素属性是动态的,无法区分这个元素,属性值中间有空格,等等。那么应该从此元素的上一层开始查找。当遇到了一个符合条件的元素时,对其写Xpath。然后从这个元素开始,一级级往下写,直到要定位的元素为止。


在python中使用Xpath选择器,我们需要安装lxml库。下面是经常用到的一些语法:

导入lxml的etree库: from lxml import etree

读取需要进行解析的网页

1. 从字符串读取:html=etree.HTML(text)

2. 从文件读取:html=etree.parse(file_path)

输出修正后的html:result=etree.tostring(html)

选取所需的节点:result=html.xpath(...)


了解了以上的知识后,我们就可以开始进行实际操练了。还是用之前的那个例子,实例网址:https://maoyan.com/board/4

实例目标:用requests库爬取猫眼电影网上top100的电影(排名,图片,电影名称,上映时间,评分),用Xpath进行解析,然后把数据保存到MongoDB。

首先,导入requests库,lxml的etree库和pymongo库:

from lxml import etree
import requests
import pymongo

爬取单个网页还是用原来的代码:

def get_one_page(url):
try:
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36'}
response=requests.get(url, headers=headers)
if response.status_code==200:
return response.text
return None
except requests.RequestException:
print("Fail")

接下来用浏览器打开网页,然后在浏览器里面选择开发者工具,在Network里查看网页源代码。下面截取一部分:

<div class="content">
<div class="wrapper">
<div class="main">
<p class="update-time">2018-12-30<span class="has-fresh-text">已更新</span></p>
<p class="board-content">榜单规则:将猫眼电影库中的经典影片,按照评分和评分人数从高到低综合排序取前100名,每天上午10点更新。相关数据来源于“猫眼电影库”。</p>
<dl class="board-wrapper">
<dd>
<i class="board-index board-index-1">1</i>
<a href="/films/1203" title="霸王别姬" class="image-link" data-act="boarditem-click" data-val="{movieId:1203}">
<img src="//ms0.meituan.net/mywww/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
<img data-src="https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c" alt="霸王别姬" class="board-img" />
</a>
<div class="board-item-main">
<div class="board-item-content">
<div class="movie-item-info">
<p class="name"><a href="/films/1203" title="霸王别姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王别姬</a></p>
<p class="star">
主演:张国荣,张丰毅,巩俐
</p>
<p class="releasetime">上映时间:1993-01-01</p> </div>
<div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">5</i></p>

可以看到,电影的排名在一个dd节点下面,紧接着还有一个i节点,我们需要以"board-index"开头的class属性的文本:

 <dd>
<i class="board-index board-index-1">1</i>

因此,相应的路径可以写为://dd/i[starts-with(@class,'board-index')]/text()

接下来,我们发现图片在一个a节点下面,但是有两张图片。经过检查,第二个img节点下的data-src属性是图片的链接:

 <img data-src="https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c" alt="霸王别姬" class="board-img" />

因此,相应的路径可以写为://a/img[2]/@data-src

再接下来,电影的名称,在一个p节点下面,class为"name",下面还有一个a节点:

<p class="name"><a href="/films/1203" title="霸王别姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王别姬</a></p>

相应的路径可以写为://p[@class='name']/a/@title

上映时间,在一个p节点下面,class为"releasetime":

<p class="releasetime">上映时间:1993-01-01</p>

相应的路径可以写为://p[@class='releasetime']/text()

评分,在一个p节点下面,class为"score",下面还有一个i节点:

<p class="score"><i class="integer">9.</i><i class="fraction">5</i></p>

相应的路径可以写为://p[@class='score']/i/text()

完整的路径如下(用|连接):

//dd/i[starts-with(@class,'board-index')]/text()|//a/img[2]/@data-src|//p[@class='name']/a/@title|//p[@class='releasetime']/text()|//p[@class='score

下面,我们再定义一个解析网页的方法:

def parse_one_page(html):
result=html.xpath("//dd/i[starts-with(@class,'board-index')]/text()|//a/img[2]/@data-src|//p[@class='name']/a/@title|//p[@class='releasetime']/text()|//p[@class='score']/i/text()")
return result

输出的匹配结果如下:

['', 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', '霸王别姬', '上映时间:1993-01-01', '9.', '', '', 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', '肖申克的救赎', '上映时间:1994-09-10(加拿大)', '9.', '', '', 'https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@160w_220h_1e_1c', '罗马假日', '上映时间:1953-09-02(美国)', '9.', '', '', 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@160w_220h_1e_1c', '这个杀手不太冷', '上映时间:1994-09-14(法国)', '9.', '', '', 'https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@160w_220h_1e_1c', '泰坦尼克号', '上映时间:1998-04-03', '9.', '', '', 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c', '唐伯虎点秋香', '上映时间:1993-07-01(中国香港)', '9.', '', '', 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', '魂断蓝桥', '上映时间:1940-05-17(美国)', '9.', '', '', 'https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@160w_220h_1e_1c', '乱世佳人', '上映时间:1939-12-15(美国)', '9.', '', '', 'https://p1.meituan.net/movie/ba1ed511668402605ed369350ab779d6319397.jpg@160w_220h_1e_1c', '天空之城', '上映时间:1992', '9.', '', '', 'https://p0.meituan.net/movie/b0d986a8bf89278afbb19f6abaef70f31206570.jpg@160w_220h_1e_1c', '辛德勒的名单', '上映时间:1993-12-15(美国)', '9.', '']

可以看出,上述的格式还是有些杂乱,让我们修改一下解析网页的方法,使其变为整齐的结构化数据:

def parse_one_page(html):
result=html.xpath("//dd/i[starts-with(@class,'board-index')]/text()|//a/img[2]/@data-src|//p[@class='name']/a/@title|//p[@class='releasetime']/text()|//p[@class='score']/i/text()")
for i in range(0,55,6):
yield {"index": result[i], "movie_name": result[i+2],\
"pic": result[i+1], "release": result[i+3],\
"score": result[i+4]+result[i+5]}

现在匹配结果变成了字典格式:

{'index': '', 'movie_name': '霸王别姬', 'pic': 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', 'release': '上映时间:1993-01-01', 'score': '9.5'}
{'index': '', 'movie_name': '肖申克的救赎', 'pic': 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', 'release': '上映时间:1994-09-10(加拿大)', 'score': '9.5'}
{'index': '', 'movie_name': '罗马假日', 'pic': 'https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@160w_220h_1e_1c', 'release': '上映时间:1953-09-02(美国)', 'score': '9.1'}
{'index': '', 'movie_name': '这个杀手不太冷', 'pic': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@160w_220h_1e_1c', 'release': '上映时间:1994-09-14(法国)', 'score': '9.5'}
{'index': '', 'movie_name': '泰坦尼克号', 'pic': 'https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@160w_220h_1e_1c', 'release': '上映时间:1998-04-03', 'score': '9.5'}
{'index': '', 'movie_name': '唐伯虎点秋香', 'pic': 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c', 'release': '上映时间:1993-07-01(中国香港)', 'score': '9.1'}
{'index': '', 'movie_name': '魂断蓝桥', 'pic': 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', 'release': '上映时间:1940-05-17(美国)', 'score': '9.2'}
{'index': '', 'movie_name': '乱世佳人', 'pic': 'https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@160w_220h_1e_1c', 'release': '上映时间:1939-12-15(美国)', 'score': '9.1'}
{'index': '', 'movie_name': '天空之城', 'pic': 'https://p1.meituan.net/movie/ba1ed511668402605ed369350ab779d6319397.jpg@160w_220h_1e_1c', 'release': '上映时间:1992', 'score': '9.1'}
{'index': '', 'movie_name': '辛德勒的名单', 'pic': 'https://p0.meituan.net/movie/b0d986a8bf89278afbb19f6abaef70f31206570.jpg@160w_220h_1e_1c', 'release': '上映时间:1993-12-15(美国)', 'score': '9.2'}

接下来将结果保存到MongoDB,先写一个保存到mongo数据库的方法:

def write_to_mongo(result):
query=result
collection.update_one(query,{'$set':result},upsert=True)

注:为了避免保存重复的数据,这里把upsert改为True。

其他步骤还和以前一样,完整代码如下:

from lxml import etree
import requests
import pymongo
import time def get_one_page(url):
try:
headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36'}
response=requests.get(url, headers=headers)
if response.status_code==200:
return response.text
return None
except requests.RequestException:
print("Fail") def parse_one_page(html):
result=html.xpath("//dd/i[starts-with(@class,'board-index')]/text()|//a/img[2]/@data-src|//p[@class='name']/a/@title|//p[@class='releasetime']/text()|//p[@class='score']/i/text()")
for i in range(0,55,6):
yield {"index": result[i], "movie_name": result[i+2],\
"pic": result[i+1], "release": result[i+3],\
"score": result[i+4]+result[i+5]} def write_to_mongo(result):
query=result
collection.update_one(query,{'$set':result},upsert=True) def main(offset):
url="https://maoyan.com/board/4?offset={}".format(offset)
html=get_one_page(url)
html=etree.HTML(html)
result=parse_one_page(html)
for i in result:
write_to_mongo(i) if __name__=='__main__':
client=pymongo.MongoClient(host='localhost',port=27017)
db=client['test']
collection=db['top100_movies']
for i in range(10):
main(offset=i*10)
time.sleep(1)

用Xpath选择器解析网页(lxml)的更多相关文章

  1. python爬虫网页解析之lxml模块

    08.06自我总结 python爬虫网页解析之lxml模块 一.模块的安装 windows系统下的安装: 方法一:pip3 install lxml 方法二:下载对应系统版本的wheel文件:http ...

  2. python网络爬虫之解析网页的XPath(爬取Path职位信息)[三]

    目录 前言 XPath的使用方法 XPath爬取数据 后言 @(目录) 前言 本章同样是解析网页,不过使用的解析技术为XPath. 相对于之前的BeautifulSoup,我感觉还行,也是一个比较常用 ...

  3. C# 使用XPath解析网页

    1.需要安装库HtmlAgilityPack ,官网http://htmlagilitypack.codeplex.com/ // From File var doc = new HtmlDocume ...

  4. 网页抓取解析,使用JQuery选择器进行网页解析

    最近开发一个小功能,数据库中一个基础表的数据从另一个网站采集. 因为网站的数据不定时更新,需要更新后自动采集最新的内容. 怎么判断更新数据没有? 好在网站有一个更新日志提示的地方,只需要对比本地保留的 ...

  5. Python爬虫与数据分析之爬虫技能:urlib库、xpath选择器、正则表达式

    专栏目录: Python爬虫与数据分析之python教学视频.python源码分享,python Python爬虫与数据分析之基础教程:Python的语法.字典.元组.列表 Python爬虫与数据分析 ...

  6. python网络爬虫-解析网页(六)

    解析网页 主要使用到3种方法提取网页中的数据,分别是正则表达式.beautifulsoup和lxml. 使用正则表达式解析网页 正则表达式是对字符串操作的逻辑公式 .代替任意字符 . *匹配前0个或多 ...

  7. 使用scrapy中xpath选择器的一个坑点

    情景如下: 一个网页下有一个ul,这个ur下有125个li标签,每个li标签下有我们想要的 url 字段(每个 url 是唯一的)和 price 字段,我们现在要访问每个li下的url并在生成的请求中 ...

  8. Python如何利用Xpath进行解析

    用Python做网络爬虫的时候,会对网页的信息进行提取,笔者接触的有正则表达式,BeautifulSoup,Xpath,前面两个都是在国内能够使用的,而Xpath是Chrome的一个插件,因此需要“F ...

  9. python网络爬虫之解析网页的BeautifulSoup(爬取电影图片)[三]

    目录 前言 一.BeautifulSoup的基本语法 二.爬取网页图片 扩展学习 后记 前言 本章同样是解析一个网页的结构信息 在上章内容中(python网络爬虫之解析网页的正则表达式(爬取4k动漫图 ...

随机推荐

  1. docker stack 部署 mssql

    =============================================== 2019/12/8_第1次修改                       ccb_warlock == ...

  2. The five Day 水平翻转图像,然后反转图像并返回结果

    """ 给定一个二进制矩阵 A,我们想先水平翻转图像,然后反转图像并返回结果. 水平翻转图片就是将图片的每一行都进行翻转,即逆序.例如,水平翻转 [1, 1, 0] 的结 ...

  3. ElasticSearch 429 Too Many Requests circuit_breaking_exception

    错误提示 { "statusCode": 429, "error": "Too Many Requests", "message& ...

  4. easyUi——datetimebox绑定数据失效

    在做easy-ui时候,绑定数据不管在怎么写,都绑定不上去,最后发现是因为 标签的ID没有写,尴尬了,记录一下,防止后期出错. ui代码 <script type="text/java ...

  5. Java自学-操作符 位操作符

    Java的位操作符 位操作符 在实际工作中使用并不常见. 示例 1 : 一个整数的二进制表达 位操作都是对二进制而言的,但是我们平常使用的都是十进制比如5. 而5的二进制是101. 所以在开始学习之前 ...

  6. Java 之 Jedis

    一.客户端 Jedis 1.Jedis Jedis 是一款java操作 redis 数据库的工具. 2.使用步骤 (1)下载 Jedis 的 jar 包 (2)使用: //1. 获取连接 Jedis ...

  7. MySQL优化——MySQL 生产环境下 my.cnf 优化配置

    MySQL 5.6/5.7 参数文件优化配置[client]port = 3306socket = /data/mysql/tmp/mysql.sockdefault-character-set = ...

  8. 在centOS 7 上部署ansible自动化运维环境(01)

    环境:         3台centos 7        mycat : 10.0.0.2 mariadb1: 10.0.0.3 mariadb2: 10.0.0.4        为了实验方便 f ...

  9. uboot中的mmc命令(转)

    转载地址:https://blog.csdn.net/a624731186/article/details/37700205 一:mmc的命令如下: 1:对mmc读操作 mmc read addr b ...

  10. Shell: sh,bash,csh,tcsh等shell的区别(转)

    转载自:http://zhidao.baidu.com/question/493376840.html, http://blog.sina.com.cn/s/blog_71261a2d0100wmbj ...