一、前言
上一节我们讲了怎么批量下载壁纸,虽然爬虫的代码很简单,但是却有一个很重要的问题,那就是 xpath路径应该怎么写。
这个问题往往会被我们忽略,但 xpath路径的写法是很重要的。不同的 xpath路径写法会后续爬取代码会产生很大影响,而且不同的 xpath写法的稳定性也不同,能不能写出优雅稳定的代码就要看 xpath写得好不好了。
下面我们来讲讲为什么 xpath的写法这么重要
二、为什么 xpath写法很重要
我们拿几个例子来讲讲不同 xpath写法对代码的影响,以我的个人主页作为解析对象:
现在的需求是要爬取我个人主页里的文章列表,包括文章的链接、标题、访问量、评论数和点赞数量
爬之前我们先分析一下
1、爬什么:文章链接文章的链接、标题、评论数和点赞数量
2、怎么爬:requests请求网页、xpath解析网页
接下来正式开始爬取:
第一步:分析网页,写出图片的 xpath路径
第二步:用 requests库获取网页
第三步:使用 lxml库解析网页
第四步:把爬取到的信息保存下来
我们一步一步来,首先分析网页,写出 xpath
按 F12进入开发者模式,找到文章列表所在的标签
可以看到,文章列表是一个 ul标签,ul标签下的每一个 li标签分别代表一篇文章。
我们要爬的信息都在 class="content"的 div标签下:
- 文章链接是第一个 a标签的 herf属性值
- 文章标题是第一个 a标签的文本属性的值
- 文章的评论数是 class="meta"的 div标签下的第二个 a标签下的文本值
- 文章点赞数量是 class="meta"的 div标签下的 span标签下的文本值
这时候 xpath有很多种写法,我写出其中的两种,一好一坏,大家可以试着判断一下哪个好哪个坏
第一种写法:
xpath_link = '//ul[@class="note-list"]/li/div/a/@href'
xpath_title = '//ul[@class="note-list"]/li/div/a/text()'
xpath_comment_num = '//ul[@class="note-list"]/li/div/div[@class="meta"]/a[2]/text()'
xpath_heart_num = '//ul[@class="note-list"]/li/div/div[@class="meta"]/span/text()'
第二种写法:
#获取所有 li标签
xpath_items = '//ul[@class="note-list"]/li'
#对每个 li标签再提取
xpath_link = './div/a/@href'
xpath_title = './div/a/text()'
xpath_comment_num = './/div[@class="meta"]/a[2]/text()'
xpath_heart_num = './/div[@class="meta"]/span/text()'
写好 xpath之后,我们开始第二步,获取网页
获取简书的网页如果我们还像之前那样直接请求的话,就会得到一个 403错误,这是因为没有设置请求头。
加上请求头就能解决了[]( ̄▽ ̄)*
第一种 xpath写法对应的代码:
#-*- coding: utf-8 -*
import requests
from lxml import etree
#请求头和目标网址
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'
}
url = 'https://www.jianshu.com/u/472a595d244c'
#第一种写法的 xpath
xpath_link = '//ul[@class="note-list"]/li/div/a/@href'
xpath_title = '//ul[@class="note-list"]/li/div/a/text()'
xpath_comment_num = '//ul[@class="note-list"]/li/div/div[@class="meta"]/a[2]/text()'
xpath_heart_num = '//ul[@class="note-list"]/li/div/div[@class="meta"]/span/text()'
#获取和解析网页
r = requests.get(url, headers=headers)
r.encoding = r.apparent_encoding
dom = etree.HTML(r.text)
#所有的 链接 标题 评论数 点赞数
links = dom.xpath(xpath_link)
titles = dom.xpath(xpath_title)
comment_nums = dom.xpath(xpath_comment_num)
heart_nums = dom.xpath(xpath_heart_num)
#将每篇文章的链接 标题 评论数 点赞数放到一个字典里
data = []
for i in range(len(links)):
t = {}
t['link'] = links[i]
t['title'] = titles[i]
t['comment_num'] = comment_nums[i].strip()
t['heart_num'] = heart_nums[i].strip()
data.append(t)
#打印结果
for t in data:
print(t)
运行结果如下:
可以看到,第一篇和第三篇的 comment_num 没有获取到
第二种 xpath写法对应的代码:
#-*- coding: utf-8 -*
import requests
from lxml import etree
#请求头和目标网址
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'
}
url = 'https://www.jianshu.com/u/472a595d244c'
#第二种写法的 xpath
#获取所有 li标签
xpath_items = '//ul[@class="note-list"]/li'
#对每个 li标签再提取
xpath_link = './div/a/@href'
xpath_title = './div/a/text()'
xpath_comment_num = './/div[@class="meta"]/a[2]/text()'
xpath_heart_num = './/div[@class="meta"]/span/text()'
#获取和解析网页
r = requests.get(url, headers=headers)
r.encoding = r.apparent_encoding
dom = etree.HTML(r.text)
#获取所有的文章标签
items = dom.xpath(xpath_items)
#分别对每一个文章标签进行操作 将每篇文章的链接 标题 评论数 点赞数放到一个字典里
data = []
for article in items:
t = {}
t['link'] = article.xpath(xpath_link)[0]
t['title'] = article.xpath(xpath_title)[0]
#comment_num对应的标签里有两个文本标签 用 join方法将两个文本拼接起来
#strip()方法去除换行和空格
t['comment_num'] = ''.join(article.xpath(xpath_comment_num)).strip()
t['heart_num'] = article.xpath(xpath_heart_num)[0].strip()
data.append(t)
#打印结果
for t in data:
print(t)
运行结果:
这里 comment_num成功获得了
仅仅从获取的结果来看,我们就可以判断第二种 xpath写法更好。
为什么第二种写法更好呢?
因为第二种方法把每一篇文章看作一个对象,这样后续的处理都是以对象为基本单位,对数据进行处理的时候过程更加清晰。
而第一种写法把链接、标题、评论数和点赞数量这四个分别用列表存储,这样虽然同样可以获得结果,但是再进行数据处理的时候就需要考虑怎么才能不破坏四个变量之间的一一对应关系。
用第二种方法就没有这个问题,因为在处理数据的时候它们都被看作同一个对象的组成部分,这本身就蕴含着蕴含着一种关系。
现在问题来了,平时我们在爬取数据的时候,怎么才能判断哪些数据是同一个对象呢?
这个其实很简单,在我们分析需求的时候就已经知道了,我们所需要数据的一个完整组合就是一个对象。
比如在本文的例子里,我们要爬取链接、标题、评论数和点赞数量,那么{链接,标题,评论数,点赞数量}就是一个对象。