一、前言
上一節(jié)我們講了怎么批量下載壁紙,雖然爬蟲的代碼很簡(jiǎn)單,但是卻有一個(gè)很重要的問題,那就是 xpath路徑應(yīng)該怎么寫。
這個(gè)問題往往會(huì)被我們忽略,但 xpath路徑的寫法是很重要的。不同的 xpath路徑寫法會(huì)后續(xù)爬取代碼會(huì)產(chǎn)生很大影響,而且不同的 xpath寫法的穩(wěn)定性也不同,能不能寫出優(yōu)雅穩(wěn)定的代碼就要看 xpath寫得好不好了。
下面我們來講講為什么 xpath的寫法這么重要
二、為什么 xpath寫法很重要
我們拿幾個(gè)例子來講講不同 xpath寫法對(duì)代碼的影響,以我的個(gè)人主頁作為解析對(duì)象:
現(xiàn)在的需求是要爬取我個(gè)人主頁里的文章列表,包括文章的鏈接、標(biāo)題、訪問量、評(píng)論數(shù)和點(diǎn)贊數(shù)量

爬之前我們先分析一下
1、爬什么:文章鏈接文章的鏈接、標(biāo)題、評(píng)論數(shù)和點(diǎn)贊數(shù)量
2、怎么爬:requests請(qǐng)求網(wǎng)頁、xpath解析網(wǎng)頁
接下來正式開始爬?。?/p>
第一步:分析網(wǎng)頁,寫出圖片的 xpath路徑
第二步:用 requests庫獲取網(wǎng)頁
第三步:使用 lxml庫解析網(wǎng)頁
第四步:把爬取到的信息保存下來
我們一步一步來,首先分析網(wǎng)頁,寫出 xpath
按 F12進(jìn)入開發(fā)者模式,找到文章列表所在的標(biāo)簽

可以看到,文章列表是一個(gè) ul標(biāo)簽,ul標(biāo)簽下的每一個(gè) li標(biāo)簽分別代表一篇文章。
我們要爬的信息都在 class="content"的 div標(biāo)簽下:
- 文章鏈接是第一個(gè) a標(biāo)簽的 herf屬性值
- 文章標(biāo)題是第一個(gè) a標(biāo)簽的文本屬性的值
- 文章的評(píng)論數(shù)是 class="meta"的 div標(biāo)簽下的第二個(gè) a標(biāo)簽下的文本值
- 文章點(diǎn)贊數(shù)量是 class="meta"的 div標(biāo)簽下的 span標(biāo)簽下的文本值
這時(shí)候 xpath有很多種寫法,我寫出其中的兩種,一好一壞,大家可以試著判斷一下哪個(gè)好哪個(gè)壞
第一種寫法:
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標(biāo)簽
xpath_items = '//ul[@class="note-list"]/li'
#對(duì)每個(gè) li標(biāo)簽再提取
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之后,我們開始第二步,獲取網(wǎng)頁
獲取簡(jiǎn)書的網(wǎng)頁如果我們還像之前那樣直接請(qǐng)求的話,就會(huì)得到一個(gè) 403錯(cuò)誤,這是因?yàn)闆]有設(shè)置請(qǐng)求頭。
加上請(qǐng)求頭就能解決了[]( ̄▽ ̄)*
第一種 xpath寫法對(duì)應(yīng)的代碼:
#-*- coding: utf-8 -*
import requests
from lxml import etree
#請(qǐng)求頭和目標(biāo)網(wǎng)址
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 = 'http://www.itdecent.cn/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()'
#獲取和解析網(wǎng)頁
r = requests.get(url, headers=headers)
r.encoding = r.apparent_encoding
dom = etree.HTML(r.text)
#所有的 鏈接 標(biāo)題 評(píng)論數(shù) 點(diǎn)贊數(shù)
links = dom.xpath(xpath_link)
titles = dom.xpath(xpath_title)
comment_nums = dom.xpath(xpath_comment_num)
heart_nums = dom.xpath(xpath_heart_num)
#將每篇文章的鏈接 標(biāo)題 評(píng)論數(shù) 點(diǎn)贊數(shù)放到一個(gè)字典里
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)
#打印結(jié)果
for t in data:
print(t)
運(yùn)行結(jié)果如下:

可以看到,第一篇和第三篇的 comment_num 沒有獲取到
第二種 xpath寫法對(duì)應(yīng)的代碼:
#-*- coding: utf-8 -*
import requests
from lxml import etree
#請(qǐng)求頭和目標(biāo)網(wǎng)址
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 = 'http://www.itdecent.cn/u/472a595d244c'
#第二種寫法的 xpath
#獲取所有 li標(biāo)簽
xpath_items = '//ul[@class="note-list"]/li'
#對(duì)每個(gè) li標(biāo)簽再提取
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()'
#獲取和解析網(wǎng)頁
r = requests.get(url, headers=headers)
r.encoding = r.apparent_encoding
dom = etree.HTML(r.text)
#獲取所有的文章標(biāo)簽
items = dom.xpath(xpath_items)
#分別對(duì)每一個(gè)文章標(biāo)簽進(jìn)行操作 將每篇文章的鏈接 標(biāo)題 評(píng)論數(shù) 點(diǎn)贊數(shù)放到一個(gè)字典里
data = []
for article in items:
t = {}
t['link'] = article.xpath(xpath_link)[0]
t['title'] = article.xpath(xpath_title)[0]
#comment_num對(duì)應(yīng)的標(biāo)簽里有兩個(gè)文本標(biāo)簽 用 join方法將兩個(gè)文本拼接起來
#strip()方法去除換行和空格
t['comment_num'] = ''.join(article.xpath(xpath_comment_num)).strip()
t['heart_num'] = article.xpath(xpath_heart_num)[0].strip()
data.append(t)
#打印結(jié)果
for t in data:
print(t)
運(yùn)行結(jié)果:

這里 comment_num成功獲得了
僅僅從獲取的結(jié)果來看,我們就可以判斷第二種 xpath寫法更好。
為什么第二種寫法更好呢?
因?yàn)榈诙N方法把每一篇文章看作一個(gè)對(duì)象,這樣后續(xù)的處理都是以對(duì)象為基本單位,對(duì)數(shù)據(jù)進(jìn)行處理的時(shí)候過程更加清晰。
而第一種寫法把鏈接、標(biāo)題、評(píng)論數(shù)和點(diǎn)贊數(shù)量這四個(gè)分別用列表存儲(chǔ),這樣雖然同樣可以獲得結(jié)果,但是再進(jìn)行數(shù)據(jù)處理的時(shí)候就需要考慮怎么才能不破壞四個(gè)變量之間的一一對(duì)應(yīng)關(guān)系。
用第二種方法就沒有這個(gè)問題,因?yàn)樵谔幚頂?shù)據(jù)的時(shí)候它們都被看作同一個(gè)對(duì)象的組成部分,這本身就蘊(yùn)含著蘊(yùn)含著一種關(guān)系。
現(xiàn)在問題來了,平時(shí)我們?cè)谂廊?shù)據(jù)的時(shí)候,怎么才能判斷哪些數(shù)據(jù)是同一個(gè)對(duì)象呢?
這個(gè)其實(shí)很簡(jiǎn)單,在我們分析需求的時(shí)候就已經(jīng)知道了,我們所需要數(shù)據(jù)的一個(gè)完整組合就是一個(gè)對(duì)象。
比如在本文的例子里,我們要爬取鏈接、標(biāo)題、評(píng)論數(shù)和點(diǎn)贊數(shù)量,那么{鏈接,標(biāo)題,評(píng)論數(shù),點(diǎn)贊數(shù)量}就是一個(gè)對(duì)象。