当前位置:首页>python>Python爬虫第4课:XPath与lxml高级解析技术

Python爬虫第4课:XPath与lxml高级解析技术

  • 2026-09-02 20:20:31
Python爬虫第4课:XPath与lxml高级解析技术

课程目标

  • 掌握XPath语法和表达式编写
  • 学会使用lxml库进行高效解析
  • 理解XPath与CSS选择器的区别
  • 掌握处理复杂XML和HTML文档的技巧

1. XPath简介

XPath(XML Path Language)是一种在XML和HTML文档中查找信息的语言。它使用路径表达式来选取文档中的节点或节点集。

1.1 XPath的优势

  • 功能强大,表达能力强
  • 支持复杂的条件查询
  • 可以进行数学运算和字符串操作
  • 浏览器原生支持

1.2 安装lxml

  1. pip install lxml

2. XPath基本语法

2.1 路径表达式

  1. from lxml import html, etree
  2. # 示例HTML
  3. html_content ="""
  4. <html>
  5. <body>
  6.     <div class="container">
  7.         <h1 id="title">主标题</h1>
  8.         <div class="content">
  9.             <p>第一段</p>
  10.             <p>第二段</p>
  11.             <ul>
  12.                 <li>项目1</li>
  13.                 <li>项目2</li>
  14.                 <li>项目3</li>
  15.             </ul>
  16.         </div>
  17.     </div>
  18. </body>
  19. </html>
  20. """
  21. # 创建解析树
  22. tree = html.fromstring(html_content)
  23. # 基本路径表达式
  24. # / : 从根节点选取
  25. # // : 从任意位置选取
  26. # . : 当前节点
  27. # .. : 父节点
  28. # @ : 属性
  29. # 绝对路径
  30. title = tree.xpath('/html/body/div/h1/text()')
  31. print(title)# ['主标题']
  32. # 相对路径
  33. paragraphs = tree.xpath('//p/text()')
  34. print(paragraphs)# ['第一段', '第二段']

2.2 节点选择

  1. # 选择所有div元素
  2. divs = tree.xpath('//div')
  3. # 选择第一个div元素
  4. first_div = tree.xpath('//div[1]')
  5. # 选择最后一个li元素
  6. last_li = tree.xpath('//li[last()]')
  7. # 选择前两个li元素
  8. first_two_li = tree.xpath('//li[position()<=2]')
  9. # 选择所有有class属性的div
  10. divs_with_class = tree.xpath('//div[@class]')
  11. # 选择class为container的div
  12. container = tree.xpath('//div[@class="container"]')

2.3 属性选择

  1. html_content ="""
  2. <div class="article">
  3.     <a href="https://example.com" title="示例">链接1</a>
  4.     <a href="https://test.com" title="测试">链接2</a>
  5.     <img src="image1.jpg" alt="图片1" width="300">
  6.     <img src="image2.png" alt="图片2" width="400">
  7. </div>
  8. """
  9. tree = html.fromstring(html_content)
  10. # 获取所有链接的href属性
  11. hrefs = tree.xpath('//a/@href')
  12. print(hrefs)# ['https://example.com', 'https://test.com']
  13. # 获取所有图片的src属性
  14. srcs = tree.xpath('//img/@src')
  15. print(srcs)# ['image1.jpg', 'image2.png']
  16. # 获取width大于300的图片
  17. wide_images = tree.xpath('//img[@width>300]/@src')
  18. print(wide_images)# ['image2.png']

3. XPath高级语法

3.1 条件表达式

  1. html_content ="""
  2. <div class="products">
  3.     <div class="product" data-price="100">
  4.         <h3>产品A</h3>
  5.         <span class="price">¥100</span>
  6.     </div>
  7.     <div class="product" data-price="200">
  8.         <h3>产品B</h3>
  9.         <span class="price">¥200</span>
  10.     </div>
  11.     <div class="product" data-price="50">
  12.         <h3>产品C</h3>
  13.         <span class="price">¥50</span>
  14.     </div>
  15. </div>
  16. """
  17. tree = html.fromstring(html_content)
  18. # 价格大于100的产品
  19. expensive_products = tree.xpath('//div[@data-price>100]/h3/text()')
  20. print(expensive_products)# ['产品B']
  21. # 包含特定文本的元素
  22. product_a = tree.xpath('//h3[text()="产品A"]')
  23. # 使用contains函数
  24. products_with_a = tree.xpath('//h3[contains(text(), "产品")]')
  25. # 使用starts-with函数
  26. price_elements = tree.xpath('//span[starts-with(@class, "price")]')

3.2 轴(Axes)

  1. html_content ="""
  2. <div class="container">
  3.     <div class="header">头部</div>
  4.     <div class="content">
  5.         <p>段落1</p>
  6.         <p class="highlight">段落2</p>
  7.         <p>段落3</p>
  8.     </div>
  9.     <div class="footer">底部</div>
  10. </div>
  11. """
  12. tree = html.fromstring(html_content)
  13. # 获取highlight段落的父元素
  14. parent = tree.xpath('//p[@class="highlight"]/parent::div')
  15. # 获取highlight段落的前一个兄弟元素
  16. preceding_sibling = tree.xpath('//p[@class="highlight"]/preceding-sibling::p/text()')
  17. print(preceding_sibling)# ['段落1']
  18. # 获取highlight段落的后一个兄弟元素
  19. following_sibling = tree.xpath('//p[@class="highlight"]/following-sibling::p/text()')
  20. print(following_sibling)# ['段落3']
  21. # 获取所有祖先元素
  22. ancestors = tree.xpath('//p[@class="highlight"]/ancestor::*')
  23. # 获取所有后代元素
  24. descendants = tree.xpath('//div[@class="content"]/descendant::*')

3.3 函数使用

  1. # 文本函数
  2. text_content = tree.xpath('//p[normalize-space(text())!=""]/text()')
  3. # 字符串长度
  4. long_text = tree.xpath('//p[string-length(text())>5]/text()')
  5. # 位置函数
  6. first_p = tree.xpath('//p[position()=1]/text()')
  7. last_p = tree.xpath('//p[position()=last()]/text()')
  8. # 计数函数
  9. p_count = tree.xpath('count(//p)')
  10. print(f"段落数量:{p_count}")
  11. # 字符串函数
  12. uppercase_text = tree.xpath('//p[contains(translate(text(), "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), "段落")]')

4. lxml库详解

4.1 解析HTML

  1. from lxml import html
  2. import requests
  3. def parse_html_with_lxml(url):
  4. """使用lxml解析HTML"""
  5.     response = requests.get(url)
  6.     tree = html.fromstring(response.content)
  7. # 修复可能的HTML错误
  8. # lxml会自动修复一些HTML错误
  9. return tree
  10. # 从字符串解析
  11. html_string ="<div><p>Hello World</p></div>"
  12. tree = html.fromstring(html_string)
  13. # 从文件解析
  14. tree = html.parse('example.html')

4.2 解析XML

  1. from lxml import etree
  2. xml_content ="""
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <bookstore>
  5.     <book id="1" category="fiction">
  6.         <title>Python编程</title>
  7.         <author>张三</author>
  8.         <price>59.99</price>
  9.     </book>
  10.     <book id="2" category="technical">
  11.         <title>数据结构</title>
  12.         <author>李四</author>
  13.         <price>79.99</price>
  14.     </book>
  15. </bookstore>
  16. """
  17. # 解析XML
  18. root = etree.fromstring(xml_content)
  19. # 获取所有书籍标题
  20. titles = root.xpath('//title/text()')
  21. print(titles)# ['Python编程', '数据结构']
  22. # 获取技术类书籍
  23. tech_books = root.xpath('//book[@category="technical"]/title/text()')
  24. print(tech_books)# ['数据结构']
  25. # 获取价格大于60的书籍
  26. expensive_books = root.xpath('//book[price>60]/title/text()')
  27. print(expensive_books)# ['数据结构']

4.3 命名空间处理

  1. xml_with_ns ="""
  2. <?xml version="1.0"?>
  3. <root xmlns:book="http://example.com/book"
  4.       xmlns:author="http://example.com/author">
  5.     <book:catalog>
  6.         <book:item>
  7.             <book:title>Python指南</book:title>
  8.             <author:name>王五</author:name>
  9.         </book:item>
  10.     </book:catalog>
  11. </root>
  12. """
  13. root = etree.fromstring(xml_with_ns)
  14. # 定义命名空间
  15. namespaces ={
  16. 'book':'http://example.com/book',
  17. 'author':'http://example.com/author'
  18. }
  19. # 使用命名空间查询
  20. titles = root.xpath('//book:title/text()', namespaces=namespaces)
  21. authors = root.xpath('//author:name/text()', namespaces=namespaces)
  22. print(titles)# ['Python指南']
  23. print(authors)# ['王五']

5. 实战案例:爬取电商商品信息

  1. import requests
  2. from lxml import html
  3. import csv
  4. import time
  5. import random
  6. classProductSpider:
  7. def __init__(self):
  8.         self.session = requests.Session()
  9.         self.session.headers.update({
  10. 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  11. })
  12. def get_product_list(self, url, max_pages=5):
  13. """获取商品列表"""
  14.         all_products =[]
  15. for page in range(1, max_pages +1):
  16. print(f"正在爬取第{page}页...")
  17.             page_url = f"{url}?page={page}"
  18.             products = self.parse_product_page(page_url)
  19. ifnot products:
  20. print("没有更多商品,停止爬取")
  21. break
  22.             all_products.extend(products)
  23. # 随机延时,避免被反爬
  24.             time.sleep(random.uniform(1,3))
  25. return all_products
  26. def parse_product_page(self, url):
  27. """解析商品页面"""
  28. try:
  29.             response = self.session.get(url, timeout=10)
  30.             response.raise_for_status()
  31.             tree = html.fromstring(response.content)
  32. return self.extract_products(tree)
  33. exceptExceptionas e:
  34. print(f"解析页面失败:{e}")
  35. return[]
  36. def extract_products(self, tree):
  37. """提取商品信息"""
  38.         products =[]
  39. # 使用XPath定位商品容器
  40.         product_nodes = tree.xpath('//div[@class="product-item"]')
  41. for node in product_nodes:
  42.             product = self.extract_single_product(node)
  43. if product:
  44.                 products.append(product)
  45. return products
  46. def extract_single_product(self, node):
  47. """提取单个商品信息"""
  48. try:
  49. # 商品名称
  50.             name_nodes = node.xpath('.//h3[@class="product-title"]/a/text()')
  51.             name = name_nodes[0].strip()if name_nodes else''
  52. # 商品链接
  53.             link_nodes = node.xpath('.//h3[@class="product-title"]/a/@href')
  54.             link = link_nodes[0]if link_nodes else''
  55. # 商品价格
  56.             price_nodes = node.xpath('.//span[@class="price"]/text()')
  57.             price = price_nodes[0].strip()if price_nodes else''
  58. # 商品评分
  59.             rating_nodes = node.xpath('.//div[@class="rating"]/@data-rating')
  60.             rating = rating_nodes[0]if rating_nodes else''
  61. # 评论数
  62.             review_nodes = node.xpath('.//span[@class="review-count"]/text()')
  63.             review_count = review_nodes[0].strip()if review_nodes else''
  64. # 商品图片
  65.             img_nodes = node.xpath('.//img[@class="product-img"]/@src')
  66.             image_url = img_nodes[0]if img_nodes else''
  67. # 店铺名称
  68.             shop_nodes = node.xpath('.//span[@class="shop-name"]/text()')
  69.             shop_name = shop_nodes[0].strip()if shop_nodes else''
  70. return{
  71. 'name': name,
  72. 'link': link,
  73. 'price': price,
  74. 'rating': rating,
  75. 'review_count': review_count,
  76. 'image_url': image_url,
  77. 'shop_name': shop_name
  78. }
  79. exceptExceptionas e:
  80. print(f"提取商品信息失败:{e}")
  81. returnNone
  82. def get_product_detail(self, product_url):
  83. """获取商品详情"""
  84. try:
  85.             response = self.session.get(product_url, timeout=10)
  86.             response.raise_for_status()
  87.             tree = html.fromstring(response.content)
  88. # 详细描述
  89.             desc_nodes = tree.xpath('//div[@class="product-description"]//text()')
  90.             description =''.join(desc_nodes).strip()
  91. # 规格参数
  92.             specs ={}
  93.             spec_rows = tree.xpath('//table[@class="specs-table"]//tr')
  94. for row in spec_rows:
  95.                 key_nodes = row.xpath('./td[1]/text()')
  96.                 value_nodes = row.xpath('./td[2]/text()')
  97. if key_nodes and value_nodes:
  98.                     specs[key_nodes[0].strip()]= value_nodes[0].strip()
  99. # 商品图片列表
  100.             image_nodes = tree.xpath('//div[@class="product-images"]//img/@src')
  101.             images =[img for img in image_nodes if img]
  102. return{
  103. 'description': description,
  104. 'specifications': specs,
  105. 'images': images
  106. }
  107. exceptExceptionas e:
  108. print(f"获取商品详情失败:{e}")
  109. return{}
  110. def save_to_csv(self, products, filename='products.csv'):
  111. """保存到CSV文件"""
  112. ifnot products:
  113. print("没有数据需要保存")
  114. return
  115. with open(filename,'w', newline='', encoding='utf-8')as csvfile:
  116.             fieldnames =['name','link','price','rating','review_count',
  117. 'image_url','shop_name']
  118.             writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
  119.             writer.writeheader()
  120. for product in products:
  121.                 writer.writerow(product)
  122. print(f"数据已保存到 {filename}")
  123. # 使用示例
  124. if __name__ =="__main__":
  125.     spider =ProductSpider()
  126. # 爬取商品列表
  127.     products = spider.get_product_list('https://example-shop.com/search?q=手机')
  128. # 保存基本信息
  129.     spider.save_to_csv(products)
  130. # 获取前5个商品的详细信息
  131. for i, product in enumerate(products[:5]):
  132. print(f"获取第{i+1}个商品的详细信息...")
  133.         detail = spider.get_product_detail(product['link'])
  134.         product.update(detail)
  135.         time.sleep(random.uniform(1,2))

6. XPath调试技巧

6.1 浏览器调试

  1. # 在浏览器开发者工具中测试XPath
  2. # 1. 按F12打开开发者工具
  3. # 2. 在Console中输入:
  4. # $x('//div[@class="content"]')  // 测试XPath表达式
  5. # $x('//div[@class="content"]')[0]  // 获取第一个匹配元素

6.2 Python调试

  1. def debug_xpath(tree, xpath_expr):
  2. """调试XPath表达式"""
  3. try:
  4.         result = tree.xpath(xpath_expr)
  5. print(f"XPath: {xpath_expr}")
  6. print(f"结果数量: {len(result)}")
  7. if result:
  8. print("前3个结果:")
  9. for i, item in enumerate(result[:3]):
  10. if hasattr(item,'text'):
  11. print(f"  {i+1}: {item.text}")
  12. elif hasattr(item,'tag'):
  13. print(f"  {i+1}: <{item.tag}>")
  14. else:
  15. print(f"  {i+1}: {item}")
  16. else:
  17. print("没有找到匹配的元素")
  18. exceptExceptionas e:
  19. print(f"XPath表达式错误: {e}")
  20. # 使用示例
  21. tree = html.fromstring(html_content)
  22. debug_xpath(tree,'//div[@class="product"]//h3/text()')

7. 性能优化

7.1 选择合适的解析器

  1. # lxml比BeautifulSoup更快
  2. from lxml import html
  3. import time
  4. def benchmark_parsers(html_content, iterations=1000):
  5. """比较解析器性能"""
  6. # lxml
  7.     start_time = time.time()
  8. for _ in range(iterations):
  9.         tree = html.fromstring(html_content)
  10.         titles = tree.xpath('//h1/text()')
  11.     lxml_time = time.time()- start_time
  12. print(f"lxml: {lxml_time:.4f}秒")
  13. # BeautifulSoup
  14. from bs4 importBeautifulSoup
  15.     start_time = time.time()
  16. for _ in range(iterations):
  17.         soup =BeautifulSoup(html_content,'html.parser')
  18.         titles = soup.find_all('h1')
  19.     bs4_time = time.time()- start_time
  20. print(f"BeautifulSoup: {bs4_time:.4f}秒")
  21. print(f"lxml比BeautifulSoup快 {bs4_time/lxml_time:.2f}倍")

7.2 XPath优化技巧

  1. # 优化前:低效的XPath
  2. slow_xpath ='//*[@class="content"]//*[@class="item"]//*[@class="title"]'
  3. # 优化后:更具体的路径
  4. fast_xpath ='//div[@class="content"]//div[@class="item"]/h3[@class="title"]'
  5. # 使用索引而不是last()
  6. # 慢://li[last()]
  7. # 快://li[3] (如果知道具体位置)
  8. # 避免使用//开头,如果知道具体路径
  9. # 慢://div//span//text()
  10. # 快:/html/body/div/span/text()

8. 实践练习

练习1:爬取新闻网站

使用XPath爬取新闻网站的文章列表,提取标题、链接、发布时间等信息。

练习2:解析XML数据

处理一个包含商品信息的XML文件,提取所有商品的详细信息。

练习3:复杂表格解析

使用XPath解析包含合并单元格的复杂HTML表格。

9. 课程小结

本课程我们学习了:

  1. XPath语法和基本表达式
  2. XPath高级功能和函数
  3. lxml库的使用方法
  4. XML和HTML的解析技巧
  5. 命名空间处理
  6. 实战案例和性能优化

10. 下节预告

下一课我们将学习:

  • 正则表达式在爬虫中的应用
  • 数据清洗和预处理技术
  • 处理各种数据格式
  • 数据验证和质量控制

11. 作业

  1. 使用XPath爬取一个电商网站的商品信息
  2. 练习编写复杂的XPath表达式
  3. 比较XPath和CSS选择器的性能差异
  4. 处理包含命名空间的XML文档

提示:XPath是强大的数据提取工具,熟练掌握其语法可以大大提高爬虫的效率和准确性。

最新文章

随机文章