我們今天寫的爬蟲內(nèi)容是xpath,我們通過美食頁面的分析帶領大家學習xpath。
先來介紹一下xpath。 XPath即為XML路徑語言,它是一種用來確定XML(標準通用標記語言的子集)文檔中某部分位置的語言。XPath基于XML的樹狀結構,有不同類型的節(jié)點,包括元素節(jié)點,屬性節(jié)點和文本節(jié)點,提供在數(shù)據(jù)結構樹中找尋節(jié)點的能力。 跟BeautifulSoup4一樣都是用來解析頁面內(nèi)容的工具,只不過使用方式有所不同而已。
要想使用xpath,需要安裝lxml: pip install lxml
案例分析
下面我們通過豆果網(wǎng)精選美食https://www.douguo.com/jingxuan/0來帶領大家學習使用xpath。
Picture
我們要獲取菜譜的名稱、作者、瀏覽量、收藏量、圖片等信息,每頁中有24個菜譜推薦。
看一下文章的節(jié)點情況:
可以發(fā)現(xiàn)有多個li【類選擇器是item的】標簽,每個li里面包含兩部分內(nèi)容過:【a標簽和div標簽】
a標簽中包含的是圖片和視頻內(nèi)容過,
div標簽中包含的信息是:菜譜的名稱、作者、瀏覽量、收藏量
因此我們通過節(jié)點找到id=jxlist的ul標簽,就可以獲取里面的多個li標簽。
xpath節(jié)點選取語法
在介紹bs4的時候,我們已經(jīng)給大家介紹了節(jié)點的概念,比如父節(jié)點、子節(jié)點、同胞節(jié)點、先輩節(jié)點和后代節(jié)點等。
XPath 使用路徑表達式在 XML 文檔中選取節(jié)點。節(jié)點是通過沿著路徑或者 step 來選取的。
常用的路徑表達式有:
但是往往在查找的時候,我們需要獲取某個特定的節(jié)點,則需要配合下面的方式即:被嵌在方括號內(nèi),用來查找某個特定的節(jié)點或包含某個制定的值的節(jié)點。
另外還可以在使用xpath的時候使用通配符和功能函數(shù)
案例使用
使用requests獲取網(wǎng)頁信息
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36'}
response = requests.get('https://www.douguo.com/jingxuan/0', headers=headers)
# 為了寫xpath內(nèi)容,我們先將內(nèi)容保存到本地,然后爬取多頁內(nèi)容
with open('jingxuan.html', 'wb') as stream:
stream.write(response.content)
我們分析頁面內(nèi)容并使用xpath,
獲取美食的詳情頁鏈接
可以發(fā)現(xiàn)詳情頁的鏈接在a標簽的href屬性中,通過xpath獲取可以使用:
//ul[@id="jxlist"]/li/a/@href
表示獲取id叫jxlist的ul標簽,注意此處使用了//,每一層的/表示一層關系
完整代碼:
from lxml import etree
# 讀取保存在本地的網(wǎng)頁,進行xpath解析
with open('jingxuan.html', 'r') as stream:
all = stream.read()
html = etree.HTML(all)
links = html.xpath('//ul[@id="jxlist"]/li/a/@href')
print(links)
結果提取出了所有l(wèi)i中的詳情頁鏈接,當前如果訪問要添加前綴(https://www.douguo.com)進行拼接:
['/cookbook/3102422.html', '/cookbook/3102421.html', '/cookbook/3102419.html', ..... ]
美食圖片的獲取,美食圖片的鏈接在a標簽的img標簽中,所以代碼要改成:
images = html.xpath('//ul[@id="jxlist"]/li/a/img/@src')
print(images)
獲取了每個美食的圖片鏈接,結果如下:
['https://cp1.douguo.com/upload/caiku/a/6/a/400x266_a6502b09c35f4331a5c4b812f5e77bba.jpeg', 'https://cp1.douguo.com/upload/caiku/7/6/a/400x266_7639e7ea6394437042e9b2f77414f84a.jpg',
......
]
菜名的獲取,有兩種方式:
在a標簽的alt屬性中獲取
//ul[@id="jxlist"]/li/a/@alt
在div的第一個a標簽中獲取
//ul[@id="jxlist"]/li/div/a[1]/text()
本次使用的是第二種方式,其中a[1]表示div中的第一個div標簽。
names = html.xpath('//ul[@id="jxlist"]/li/div/a[1]/text()')
print(names)
結果:
['麻辣小龍蝦', '#仙女們的私藏鮮法大PK#家庭版健康雙牛漢堡', '#舌尖上的端午#藍莓醬', '蒜蓉蝦', '奶香小面包',......]
發(fā)表用戶的獲取
用戶名在div的第二個a標簽中,但是發(fā)現(xiàn)a標簽不僅有文本還有img標簽。按照上面的方式獲取(注意文本的獲取使用text())
//ul[@id="jxlist"]/li/div/a[2]/text()
發(fā)現(xiàn)結果,將文本的換行和空格內(nèi)容都包含在里面了,而且每個文本的前面都有一個空內(nèi)容:
于是我們需要進行正則的替換和獲取有用信息。
import re
users = html.xpath('//ul[@id="jxlist"]/li/div/a[2]/text()')
pattern = re.compile(r"\n+|\s+", re.S) # 查找\n和\s進行替換
users = [pattern.sub('', users[u]) for u in range(1,len(users),2)] # 刪除第一個空內(nèi)容
print(users)
結果:
['好吃的豆苗', '馬賽克姑涼', '淺夏°淡雅', '清雅wuda', '肉乎乎的小瘦子', 'dreamer...', '拒絕添加劑',......]
瀏覽量和收藏量的獲取方式是一樣的,分別在兩個span標簽中
//ul[@id="jxlist"]/li/div/div/span[1]/text() 獲取瀏覽量
//ul[@id="jxlist"]/li/div/div/span[2]/text() 獲取收藏量
代碼如下:
views = html.xpath('//ul[@id="jxlist"]/li/div/div/span[1]/text()')
print(views)
collects = html.xpath('//ul[@id="jxlist"]/li/div/div/span[2]/text()')
print(collects)
運行結果:
['3656', '1.5萬', '1.5萬', '3158', '2729', '1.2萬', '2.0萬', ...... ]
['78', '431', '504', '83', '32', '245', '484',......]
完整代碼
以下代碼添加了分頁的內(nèi)容,分頁的特點是:
https://www.douguo.com/jingxuan/0 第一頁
https://www.douguo.com/jingxuan/24 第二頁
https://www.douguo.com/jingxuan/48 第二頁
.....
import requests
import re
import csv
from lxml import etree
def get_html(page):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36'}
response = requests.get('https://www.douguo.com/jingxuan/' + str(page), headers=headers)
return response.text
def parse_html(content):
html = etree.HTML(content)
links = html.xpath('//ul[@id="jxlist"]/li/a/@href')
images = html.xpath('//ul[@id="jxlist"]/li/a/img/@src')
names = html.xpath('//ul[@id="jxlist"]/li/div/a[1]/text()')
users = html.xpath('//ul[@id="jxlist"]/li/div/a[2]/text()')
pattern = re.compile(r"\n+|\s+", re.S)
users = [pattern.sub('', users[u]) for u in range(1, len(users), 2)]
views = html.xpath('//ul[@id="jxlist"]/li/div/div/span[1]/text()')
collects = html.xpath('//ul[@id="jxlist"]/li/div/div/span[2]/text()')
return zip(names, links, users, views, collects, images)
def save_data(foods):
with open('foods.csv', 'a') as stream:
writer = csv.writer(stream)
writer.writerows(foods)
if __name__ == '__main__':
for i in range(6):
page = i*24
content = get_html(page)
foods = parse_html(content)
save_data(foods)
print(f"第{i+1}頁保存成功!")