如何通过xpath在html中取出想要的值:scrapy根据xml提供了一套简洁的通过xpath或者css选择器来提取数据的接口。
4.1 关于xpath
- xpath简介:xpath使用路径表达式(就像windows系统文件的路径一样可以定位)在xml和html中进行导航,包含了很多标准函数库,xpath包含有一个标准函数库,xpath是一个w3c的标准
- xpath术语
- xpath语法
xpath节点关系:
html中被尖括号包起来的被称为一个节点。
- 父节点
上一层节点
- 子节点
下一层节点
- 兄弟节点
同胞节点
- 先辈节点
父节节点,爷爷节点
- 后代节点
儿子节点,孙子节点
4.2 xpath语法
表达式 | 说明 |
---|---|
article | 选取所有article元素的所有子节点 |
/article | 选取根元素article(html中根元素都是html;xml可以自定义根节点) |
article/a | 选取所有属于article的子元素的a元素 |
//div | 选取所有div元素(不管出现在文档里的任何地方) |
article//div | 选取所有属于article元素的后代的div元素,不管它出现在article之下的任何位置 |
//@class | 选取所有名为class的属性 |
xpath语法-谓语
表达式 | 说明 |
---|---|
/article/div[1 | 选取属于article子元素的第一个div元素 |
/article/div[last()] | 选取属于article子元素的最后一个div元素 |
/article/div[last()-1] | 选取属于article子元素的倒数第二个div元素 |
//div[@color] | 选取所有拥有color属性的div元素 |
//div[@color='red'] | 选取所有color属性值为red的div元素 |
xpath语法
表达式 | 说明 |
---|---|
/div/* | 选取属于div元素的所有子节点 |
//* | 选取所有元素 |
//div[@*] | 选取所有带属性的div 元素 |
//div/a 丨//div/p | 选取所有div元素的a和p元素 |
//span丨//ul | 选取文档中的span和ul元素 |
article/div/p丨//span | 选取所有属于article元素的div元素的p元素以及文档中所有的 span元素 |
4.3 xpath选择器在具体网站上的应用
以伯乐在线的这篇文章为例,http://blog.jobbole.com/110287/
,我们想获取,正文部分的标题,首先要做的就是F12检查元素,然后去找到这个标题对应的html节点位置:
可以看到,我们的标题标题在 html/body/div[1]/div[3]/div[1]/div[1]/h1 这个嵌套关系下,我们在用xpath解析的时候,不需要自己一个一个地看嵌套关系,在 F12下,在某个元素上面右键即copy->copy xpath就能获得该元素的xpath路径。
注意:在Firefox和chrom浏览器中右键copy xpath得到的结果可能不一样
在Firefox中,得到的路径是/html/body/div[1]/div[3]/div[1]/div[1]/h1
在chrom中,得到的是//*[@id="post-110287"]/div[1]/h1
可以发现两种路径不一样,经过测试,第一种路径不能获得标题,第二种可以,原因在于,一般元素检查看到的是动态的返回来的html信息,比如js生成的,然后有些节点可能是在后台返回信息时才创建的,对于静态的网页就是检查源代码,定位的结果可能不一样,采用第二种id确定的方式更容易标准的定位。
用xpath提取标题的案例:
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ['http://blog.jobbole.com/110287/']
def parse(self, response):
# 取任何节点。id等于post-110287.
# re_selector = response.xpath('/html/body/div[1]/div[3]/div[1]/div[1]/')
# re_selector = response.css('#post-95104 > div.entry-header > h1')
re_selector = response.xpath('//*[@id="post-110287"]/div[1]/h1')
print(re_selector)
pass
# 通过debug断点调试,可以看到返回来的re_selector是有正确信息的,定位到了h1这个节点
通过调试可以看到结果:
错误提示:
同一个页面的元素通过不同电脑的chrom浏览器进行源代码查看,标签结点信息发现不一样,在h1标签中多了个span标签,解决方法:清除浏览器缓存,以下是同一页面用一个内容的检查元素的对比图。
图1:未清除浏览器缓存前
图2:清除浏览器缓存后
4.4 使用shell命令调试及更多字段内容爬取
介绍一个技巧,每次调试启动scrapy是比较慢的,每次调试都请求了一次url,效率比较低
scrapy提供了一种shell模式,提高了调试的效率。具体操作方法是在命令行中,之前的启动scrapy的命令是scrapy crawl jobbole,现在可以在命令行中使用shell,命令为“scrapy shell 网址”,然后就进入了调试区域。步骤如下图,注意启动scrapy必须在命令行中进入相应的虚拟环境以及项目的工作目录。
获取发布时间:
response.xpath("//p[@class = 'entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace("·","").strip()
关于发布时间的标签设计:
在shell模式下的调试步骤:
获取点赞数:
int(response.xpath("//span[contains(@class,'vote-post-up')]/h10/text()").extract()[0])
# contains(属性名,内容)之所以用这个方法因为
#该span标签的class属性里面布置一个值,所以用contains()这个函数
在shell模式下的调试步骤:
获取收藏数:
response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract()[0]
# ' 收藏'
# 收藏数的标签设置和点赞数不一样,直接是收藏前面有数字,这里没有数字,其实是0收藏的意思。
# 对于含数字的话,我们应该使用正则表达式将数字部分提取出来。
import re
match_re = re.match('.*?(\d+).*',' 收藏')
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0
# 正则表达式注意要有?表示非贪婪匹配,可以获取两位数等
# 还有一点就是老师没有考虑的,如果没有收藏数,即匹配不到数字,说明收藏数为0.
获取评论数:
# 评论数和收藏数的标签设计是一样的,只需要更改xpath即可
comment_nums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
match_re = re.match('.*?(\d+).*', comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0
获取正文内容:
content = response.xpath('//div[@class="entry"]').extract()[0]
# 对于文章内容,不同网站的设计不一样,我们一般保存html格式的内容
关于extract()方法和text()方法的区别:extract()是对一个selector的内容取出这个标签内的所有内容,包括当前的节点标签。text()方法一般是在xpath的路径内部,用于获取当前节点内的所有文本内容。
获取文章类型:
tag_list = response.xpath("//p[@class = 'entry-meta-hide-on-mobile']/a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
# 有的网页在类型一栏中会得到评论数,以前的老设计,所以需要将关于评论的这一项去掉
tags = ",".join(tag_list)
4.5 xpath选择器在伯乐在线文章上的爬取实例
最后关于http://web.jobbole.com/95104/这个网页,我们一次获取了标题,创建日期,点赞数,收藏数,评论数,正文内容,以及文章类型,整个实现过程都是用的xpath选择器,实现代码如下:
# -*- coding: utf-8 -*-
import scrapy
import re
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["web.jobbole.com"]
start_urls = ['http://web.jobbole.com/95104/']
def parse(self, response):
# 取任何节点。id等于post-110287.
#re_selector = response.css('#post-95104 > div.entry-header > h1')
# re_selector = response.xpath('//*[@id="post-95104"]/div[1]/h1/text()')
# 在xpath的路径后面添加一个text()方法就可以获得标签里面的标题内容了
title = response.xpath('//*[@id="post-95104"]/div[1]/h1/text()').extract()[0]
create_date = response.xpath("//p[@class = 'entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace("·", "").strip()
praise_ums = response.xpath("//span[contains(@class,'vote-post-up')]/h10/text()").extract()[0]
if praise_ums:
praise_ums = int(praise_ums)
else:
praise_ums = 0
fav_nums = response.xpath("//span[contains(@class,'bookmark-btn')]/text()").extract()[0]
match_re = re.match('.*?(\d+).*',fav_nums)
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0
comment_nums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
match_re = re.match('.*?(\d+).*', comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0
content = response.xpath('//div[@class="entry"]').extract()[0]
# 对于文章内容,不同网站的设计不一样,我们一般保存html格式的内容
tag_list = response.xpath("//p[@class = 'entry-meta-hide-on-mobile']/a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
print(title)
print(create_date)
print(praise_ums)
print(fav_nums)
print(comment_nums)
print(content)
print(tags)
pass
网友评论