用Python+lxml实战解析豆果美食:从XPath零基础到循环抓取菜谱
每次看到美食网站上的诱人菜谱,你有没有想过如何批量获取这些数据?今天我们就用Python的lxml库,通过XPath语法从豆果美食抓取菜谱信息。不同于枯燥的语法记忆,我们将通过真实案例带你理解XPath的核心用法。
1. 环境准备与基础概念
在开始之前,确保你已经安装了必要的Python库。打开终端运行以下命令:
pip install requests lxml tabulate这三个库各司其职:
requests:用于获取网页HTML内容lxml:提供XPath解析功能tabulate:美化数据输出格式
XPath本质是一种在HTML/XML文档中导航和查询节点的语言。想象它就像文件系统的路径:
/html/body类似于C:/Users/Documents//div则像全局搜索所有文件夹中的div文件
from lxml import etree import requests url = 'https://www.douguo.com/' response = requests.get(url) html = etree.HTML(response.text) # 将HTML转换为XPath可解析的对象2. XPath核心语法实战解析
2.1 基础定位技巧
观察豆果美食首页的HTML结构,菜谱信息通常包含在特定CSS选择器的元素中。通过浏览器开发者工具(F12),我们可以看到菜谱区块的结构特征。
常用定位方式对比:
| 表达式 | 说明 | 示例 |
|---|---|---|
/ | 从根节点开始的绝对路径 | /html/body/div |
// | 文档任意位置的相对路径 | //div[@class="recipe"] |
@ | 属性定位 | //a[@href] |
[] | 谓语条件(相当于筛选条件) | //li[1](第一个li元素) |
# 获取第一个菜谱名称 first_recipe = html.xpath('//*[@id="content"]/ul[1]/li[1]/div/a/text()')[0] print(f"首个菜谱:{first_recipe}")2.2 动态路径与循环抓取
当我们需要获取多个相似结构的元素时,手动逐个定位效率极低。通过分析DOM结构,发现菜谱列表的<li>标签序号是连续变化的:
for i in range(1, 9): name = html.xpath(f'//*[@id="content"]/ul[1]/li[{i}]/div/a/text()') author = html.xpath(f'//*[@id="content"]/ul[1]/li[{i}]/div/p/a[1]/text()') print(f"{name[0]} - 作者:{author[0]}")循环优化技巧:
- 使用
string()函数避免空值:xpath('string(//div)') - 用
|合并多个路径:xpath('//h1|//h2') - 通过
contains()模糊匹配:xpath('//div[contains(@class,"recipe")]')
3. 数据清洗与结构化输出
原始抓取的数据往往需要进一步处理。我们使用tabulate库将结果格式化为美观的表格:
from tabulate import tabulate data = [] for i in range(1, 9): row = [ html.xpath(f'//*[@id="content"]/ul[1]/li[{i}]/div/a/text()')[0], html.xpath(f'//*[@id="content"]/ul[1]/li[{i}]/div/p/a[1]/text()')[0] ] data.append(row) print(tabulate(data, headers=['菜谱名称', '作者'], tablefmt='grid'))输出示例:
+----------------------------+----------+ | 菜谱名称 | 作者 | +============================+==========+ | 红烧肉 | 厨神老王 | | 清蒸鲈鱼 | 美食小李 | +----------------------------+----------+4. 异常处理与反爬应对
真实项目中总会遇到各种意外情况。以下是几个关键防护措施:
try: response = requests.get(url, headers={ 'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'zh-CN' }, timeout=5) response.raise_for_status() html = etree.HTML(response.text) # 添加数据存在性检查 recipes = html.xpath('//*[@id="content"]/ul[1]/li') if not recipes: print("警告:未找到菜谱数据,可能页面结构已变更") except requests.exceptions.RequestException as e: print(f"网络请求失败:{str(e)}")反爬策略备忘单:
- 随机延迟:
time.sleep(random.uniform(1,3)) - 代理IP轮换
- 模拟真实浏览器头部信息
- 检查
response.status_code
5. 项目扩展与进阶技巧
掌握了基础抓取后,可以尝试以下扩展方向:
- 多页爬取:分析分页规律,如
?page=2参数 - 详情页抓取:提取每个菜谱的详细做法和食材
- 数据存储:使用SQLite或MongoDB持久化数据
- 定时任务:结合APScheduler实现每日自动更新
# 示例:获取菜谱详情URL detail_urls = html.xpath('//*[@id="content"]/ul[1]/li/div/a/@href') for url in detail_urls: full_url = f"https://www.douguo.com{url}" detail_page = requests.get(full_url).text # 解析详情页内容...记住,实际开发中要遵守网站的robots.txt规则,控制请求频率。当遇到动态加载内容时,可能需要使用Selenium等工具配合XPath使用。