Python爬虫必杀技:XPath美食网站案例
XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言。XPath基于XML的树状结构,有不同类型的节点,包括元素节点,属性节点和文本节点,提供在数据结构树中找寻节点的能力。跟BeautifulSoup4一样都是用来解析页面内容的工具,只不过使用方式有所不同而已。
要想使用xpath,需要安装lxml: pip install lxml
案例分析
下面我们通过豆果网精选美食https://www.douguo.com/jingxuan/0来带领大家学习使用xpath。

看一下文章的节点情况:

a标签中包含的是图片和视频内容过,
div标签中包含的信息是:菜谱的名称、作者、浏览量、收藏量
因此我们通过节点找到id=jxlist的ul标签,就可以获取里面的多个li标签。
xpath节点选取语法
在介绍bs4的时候,我们已经给大家介绍了节点的概念,比如父节点、子节点、同胞节点、先辈节点和后代节点等。
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
常用的路径表达式有:




案例使用
使用requests获取网页信息
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内容,我们先将内容保存到本地,然后爬取多页内容
with open('jingxuan.html', 'wb') as stream:
stream.write(response.content)
我们分析页面内容并使用xpath,
1.获取美食的详情页链接

//ul[@id="jxlist"]/li/a/@href
表示获取id叫jxlist的ul标签,注意此处使用了//,每一层的/表示一层关系
完整代码:
from lxml import etree
# 读取保存在本地的网页,进行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)
结果提取出了所有li中的详情页链接,当前如果访问要添加前缀(https://www.douguo.com)进行拼接:
['/cookbook/3102422.html', '/cookbook/3102421.html', '/cookbook/3102419.html', ..... ]
2.美食图片的获取,美食图片的链接在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',
......
]
3. 菜名的获取,有两种方式:
1.在a标签的alt属性中获取
//ul[@id="jxlist"]/li/a/@alt
2.在div的第一个a标签中获取
//ul[@id="jxlist"]/li/div/a[1]/text()

names = html.xpath('//ul[@id="jxlist"]/li/div/a[1]/text()')print(names)
结果:
['麻辣小龙虾', '#仙女们的私藏鲜法大PK#家庭版健康双牛汉堡', '#舌尖上的端午#蓝莓酱', '蒜蓉虾', '奶香小面包',......]
4.发表用户的获取
用户名在div的第二个a标签中,但是发现a标签不仅有文本还有img标签。按照上面的方式获取(注意文本的获取使用text())
//ul[@id="jxlist"]/li/div/a[2]/text()
发现结果,将文本的换行和空格内容都包含在里面了,而且每个文本的前面都有一个空内容:

import reusers = 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)] # 删除第一个空内容print(users)
结果:
['好吃的豆苗', '马赛克姑凉', '浅夏°淡雅', '清雅wuda', '肉乎乎的小瘦子', 'dreamer...', '拒绝添加剂',......]
5.浏览量和收藏量的获取方式是一样的,分别在两个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',......]
完整代码
以下代码添加了分页的内容,分页的特点是:
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}页保存成功!")