python xpath解析网页_python中用xpath解析网页的基本方法

1. 背景

目前爬虫解析网页的技术有:Json, 正则表达式,BeautifulSoup,PyQuery,XPath

XPath 教程 官方文档:

http://www.w3school.com.cn/xpath/index.asp

2. XPath简述

2.1. 什么是XPath?

XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。

2.2. XPath 开发工具

1.开源的XPath表达式编辑工具: XMLQuire(XML格式文件可用)

2.Chrome插件 XPath Helper

3.Firefox插件 XPath Checker

2.3. XPath语法的解析库 —— lxml库

lxml 是一个HTML/XML的解析器,主要的功能是解析和提取 HTML/XML 数据,我们可以利用XPath语法,来快速的定位特定元素以及节点信息。

lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器。

lxml python 官方文档: http://lxml.de/index.html

安装lxml库:需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)

3. 什么是XML?

XML 指可扩展标记语言(EXtensible Markup Language)

XML 是一种标记语言,很类似 HTML

XML 的设计宗旨是传输数据,而非显示数据

XML 的标签需要我们自行定义。

XML 被设计为具有自我描述性。

XML 是 W3C 的推荐标准

XML 官方文档:http://www.w3school.com.cn/xml/index.asp

3.1. XML 和 HTML 的区别

数据格式描述设计目标

XMLExtensible Markup Language (可扩展标记语言)被设计为传输和存储数据,其焦点是数据的内容。

HTMLHyperText Markup Language (超文本标记语言)显示数据以及如何更好显示数据。

HTML DOMDocument Object Model for HTML (文档对象模型)通过 HTML DOM,可以访问所有的 HTML 元素,连同它们所包含的文本和属性。可以对其中的内容进行修改和删除,同时也可以创建新的元素。

3.2. XML文档示例

可以在 http://www.w3school.com.cn/example/xmle_examples.asp 找到很多XML文档示例。

<?xml version="1.0" encoding="utf-8"?>

Everyday Italian

Giada De Laurentiis

2005

30.00

Harry Potter

J K. Rowling

2005

29.99

XQuery Kick Start

James McGovern

Per Bothner

Kurt Cagle

James Linn

Vaidyanathan Nagarajan

2003

49.99

Learning XML

Erik T. Ray

2003

39.95

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

3.3. HTML DOM 结构

HTML DOM 定义了访问和操作 HTML 文档的标准方法。DOM 将 HTML 文档表达为树结构。

3.4. XML的节点关系

3.4.1. 约定

3.4.2. 关系

父(Parent):

每个元素以及属性都有一个父。

上图中book元素 是 title,author(多个),year , price 元素的父辈。

子(Children):

元素节点可有零个、一个或多个孩子元素。

上图中,title,author,year,price元素都是 book元素的子:

兄弟(Sibling):

上图中,title,author,year,price元素都是兄弟。

祖先(Ancestor):

某节点的父辈、父辈的父辈,往上递推。

上图中,title元素的祖先是 book元素。

后代(Descendant):

某节点的子,子的子,往后递推。

上图中,book元素的后代是title元素。

4. XPath基本语法

# http://blog.csdn.net/zheng12tian/article/details/40617303

# XPath 基础表达式:

/node 表示在xml文档的根目录查找结点名称为node的结点

./node 表示在当前结点下查找结点名称为node的结点

//node 表示在xml文档中递归查找结点名称为node的节点

//* 表示在xml文档中查询所有的结点,但是排除文本节点

//node() 表示在xml文档中查询所有结点,包含文本节点

//text() 表示在xml文档中递归查找所有的文本节点

//*/text()[contains(., 'test')] 表示在xml文档中递归查找所有结点,条件为该结点的文本节点包含"test"

//node[@id] 表示在xml文档中递归查找结点名称为node的结点,条件为该结点必须含有id属性

//node[id] 表示在xml文档中递归查找结点名称为node的结点,条件为该结点必须含有结点名称为id的结点

//nodes[node/id] 表示递归查找nodes结点,条件为nodes结点下必须有node结点,且node结点下必须有id结点

//nodes[@id]/node[id] 表示递归查找含有id属性的nodes结点下的node结点,条件为node结点下必须含有id结点

//nodes[@id]/node[0] 表示递归查找含有id属性的nodes结点下的第一个node结点

//nodes[@id]/node[last()] 表示递归查找含有id属性的nodes结点下的最后一个node结点

//nodes/node[position() < 4] 表示递归查找nodes结点下索引小于4的node结点

//nodes[@id]/node[position() < last()] 递归查找含有id属性的nodes结点下除最后一个结点外的node结点

/nodes/child::node()[name()='node'] 表示查找nodes结点下结点名称为node的子结点

/nodes/child::node 等同于/nodes/node表示查找nodes下的node子结点

/nodes/node/attribute::id 等同于/nodes/node/@id表示查找nodes结点下的node结点的id属性

//nodes[@id='1001']/node[starts-with(@id, '1')] 表示查找id属性为1001的nodes结点下的id属性以1开头的node结点

//@*[ends-with(., '1')] 表示查找以1结尾所有属性

(//* | //@*)[substring(name(), 1, 5) = 'class'] 查找所有结点名称或属性名称的1到5之间的字符等于'class'的结点

//node[@attr!='-2' and @attr!='2'] 查找所有node节点,其attr属性不等于2和-2

# XPath 文档轴用途:

self 选择当前节点

parent 选择当前节点的父节点

child 选择当前节点的所有子节点

attribute 选择当前节点的所有属性

ancestor 选择当前节点的所有祖先,包括父节点、父节点的父节点等等

ancestor-or-self 选择当前节点的祖先以及当前节点本身

descendant 选择当前节点的所有后代,包括子节点、子节点的子节点等等

descendant-or-self 选择当前节点的后代以及当前节点本身

preceding 选择整个文档中出现在当前节点前面的所有节点

preceding-sibling 选择文档中出现在当前节点前面的所有同胞节点(即与当前节点同级的节点)

following 选择整个文档中出现在当前节点后面的所有节点

following-sibling 选择文档中出现在当前节点后面的所有同胞节点(即与当前节点同级的节点)

namespace 选择当前节点的所有名称空间节点

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

5. 一些实际案例

5.1. 寻找页面通用地址,进行翻页

def parseResponse(self, response):

pageUrl = response.xpath('//*[@id="pagn"]//a/@href').extract_first(default = '')

# 去掉不必要的内容,以便达到更好的去重效果

followUrl = re.sub(r'&qid=.+&spIA=.+', '', pageUrl, count=1)

# numberRes = ['1', '2', '3', '95']

numberRes = response.xpath('string(//*[@id="pagn"])').re(r'\d+') # numberRes 重要,所以要确保正确得到总页数

for page in range(1, int(numberRes [-1]) + 1):

yield response.follow(

# 按照page值构造其他页链接

url = re.sub(r'page=\d+', f'page={page}', followUrl, count = 1),

meta = {'dont_redirect': True},

callback = self.parseNextPage,

errback = self.error

)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

5.2. 结合re使用

# 获取搜索结果数

countRes = response.xpath('//*[@id="s-result-count"]/text()').re_first(r'[0-9,]+ of ([0-9,]+)', '0').replace(',', '')

count = int(countRes)

# count = 1699

1

2

3

4

5.3. 寻找相似性,利用starts-with用法全部提取

所有商品列表

# 全部以相同的字符result_开始

# 解决方法: 用starts-with(@属性名称, 属性相同的部分)

liRes = response.xpath('//li[starts-with(@id,"result_")]')

1

2

3

4

标题

title = listElem.xpath('.//a[@title]/@title').extract_first()

1

评分

avgStar = listElem.xpath('string(//*[@name=$val])', val = asin).extract_first(default='0')

1

评论数

totalReviews = listElem.xpath('//*[@name=$val]/following-sibling::a/text()', val = asin).extract_first(default='0')

1

价格

price = listElem.xpath('.//span[@aria-label]/@aria-label | .//span[contains(.,"$")]/text()').extract_first(default='0')

1

商标:brand,直接通过chrome工具解析出来

brand = listElem.xpath('string(div/div/div/div[2]/div[last()-1]/div[2])').extract_first(default='')

1

搜索结果序号

resultID = int(listElem.xpath('@id').re_first(r'result_(\d+)'))

1

图片链接

image_url = listElem.xpath('.//img/@src').extract_first()

1

卖家总数

listElem.xpath('.//*[contains(.,"offer")]//text()').extract()

1

# 获取搜索列表下,每个商品的唯一标识号,标题

liRes = response.xpath('//li[starts-with(@id,"result_")]')

if liRes:

for listElem in liRes:

# 商品唯一标识号

# It returns None if no element was found

asin = listElem.xpath('@data-asin').extract_first()

# 标题

title = listElem.xpath('.//a[@title]/@title').extract_first()

# 评分

avgStar = listElem.xpath('string(//*[@name=$val])', val = asin).extract_first(default='0')

# 评论数

totalReviews = listElem.xpath('//*[@name=$val]/following-sibling::a/text()', val = asin).extract_first(default='0')

# 价格, contains(.,"$")中的.代表text

price = listElem.xpath('.//span[@aria-label]/@aria-label | .//span[contains(.,"$")]/text()').extract_first(default='0')

# 商标,chrome工具的结果是://*[@id="result_1"]/div/div/div/div[2]/div[1]/div[2]/span[2]

# 然后进行改造

brand = listElem.xpath('string(div/div/div/div[2]/div[last()-1]/div[2])').extract_first(default='')

# 搜索结果序号

resultID = int(listElem.xpath('@id').re_first(r'result_(\d+)'))

# 图片链接

image_url = listElem.xpath('.//img/@src').extract_first()

# 卖家总数

sellerNum = 0

for eachOfferText in listElem.xpath('.//*[contains(.,"offer")]//text()').extract():

result = re.findall(r'(\d+)\s.*?offer', eachOfferText)

if result:

sellerNum = sum(int(num) for num in result)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

5.4. 利用string(.)获取某个标签下连续的一句话

# 标签套标签,一句话分散在几个标签中,如何提取成一句完整的话?

# 解决方法: string(.)

from lxml import etree

html = '''

我左青龙,

右白虎,

  • 上朱雀,

下玄武.

老牛在当中,

龙头在胸口.

'''

selector = etree.HTML(html)

data = selector.xpath('//div[@id="class3"]')[0]

allText = data.xpath('string(.)') # 实际上是去除了div中间的其他标签

print(f"allText = {allText}")

textRes = allText.replace('\n', '').replace(' ', '')

print(f"textRes = {textRes}")

# 输出结果:

allText =

我左青龙,

右白虎,

上朱雀,

下玄武.

老牛在当中,

龙头在胸口.

textRes = 我左青龙,右白虎,上朱雀,下玄武.老牛在当中,龙头在胸口.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

5.5. 价格:文本模糊匹配,并利用上下文关系获取信息

# --- 价格 price1 : with deal or sale or price

# 发现价格的数据紧跟在这几个字串后面

priceInfoRes = response.xpath("//div[@id='price']//text()").extract()

priceInfoResLst = []

for priceInfo in priceInfoRes:

priceInfoStripLower = priceInfo.strip().lower()

if priceInfoStripLower != '':

priceInfoResLst.append(priceInfoStripLower)

priceIdx = 0

# elemIdx = 0

for elemIdx in range(0, len(priceInfoResLst)):

elem = priceInfoResLst[elemIdx].strip().lower()

if (elem == 'sale:') or (elem == 'with deal:') or (elem == 'price:'):

priceIdx = elemIdx + 1

break

if (priceIdx != 0) and (priceIdx < len(priceInfoResLst)):

price1 = priceInfoResLst[priceIdx].strip()

else:

price1 = ''

if price1 != '':

# 处理价格:price = $64.32'

priceRe = re.search(r'([\d\.]+)', price1)

priceFloat = float(priceRe.group(1)) if priceRe else 0.0

detailParseResults['price1'] = priceFloat

else:

detailParseResults['price1'] = 0.0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

5.6. 模糊匹配,以及特征提取

item = {}

#--- 解析小品类信息category:

unicorn = response.xpath('string(//*[contains(.,"Best Sellers Rank")])').extract_first(default='')

rankResults = re.findall(r'#([0-9,]+)\s+in\s+(.+)[^\.#]\s', unicorn)

isHasRank = False

# 解析topRank和 topCategory信息

topRank = 0

topCategory = 'unknown'

if len(rankResults) > 0:

# 说明是大品类信息

if rankResults[0][1].find(' > ') == -1:

topRank = int(rankResults[0][0].replace(',', '').strip())

categoryLst = rankResults[0][1].split('>')

topCategory = reCategory = ''

if len(categoryLst) > 0:

reCategory = re.search(r"(.*?)\(", categoryLst[-1])

if not reCategory:

reCategory = categoryLst[-1]

else:

reCategory = reCategory.group(1)

topCategory = reCategory.strip()

if isHasRank == True:

startIndex = 1

else:

startIndex = 0

item['topRank'] = topRank

item['topCategory'] = topCategory

smallCategoryIndex = 1

for i in range(startIndex, len(rankResults)):

item[f"rank{smallCategoryIndex}"] = int(rankResults[i][0].replace(',', '').strip())

smallCategoryLst = rankResults[i][1].split('>')

smallCategory = reSmallCategory = ''

if len(smallCategoryLst) > 0:

reSmallCategory = re.search(r"(.*?)\(", smallCategoryLst[-1])

if not reSmallCategory:

reSmallCategory = smallCategoryLst[-1]

else:

reSmallCategory = reSmallCategory.group(1)

smallCategory = reSmallCategory.strip()

item[f"category{smallCategoryIndex}"] = smallCategory

smallCategoryIndex += 1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

解析结果

5.7. 筛选属性的组合操作

# 属性的联合操作

price = response.xpath('//*[@id="priceblock_ourprice" or @id="priceblock_saleprice"]/text()').extract_first(default = '0')

title = response.xpath('string(//*[@id="productTitle" or contains(@class,"product-title")])').extract_first()

1

2

3

————————————————

版权声明:本文为CSDN博主「Kosmoo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zwq912318834/article/details/78178316

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值