Python 之 PyQuery 解析网页数据

PyQuery是一个模仿 jQuery 语法的 Python 库,让我们能够用熟悉的 CSS 选择器语法来解析和操作 HTML 文档。这是一个第三方包,使用前需要使用 pip 进行安装。

安装

复制代码
pip install pyquery

PyQuery对象

使用 pyquery 解析 html 文档时,需要生成对应的 PyQuery 对象,一般可以从文件,url链接 和 html 字符串等方式来创建 PyQuery 对象。

python 复制代码
import pyquery

# 从HTML字符串创建
html_str = "<html><body><div class='container'>内容</div></body></html>"
doc = pyquery.PyQuery(html_str)
print(doc)

# 从URL创建
doc = pyquery.PyQuery(url='https://www.example.com')
print(doc)
# print(doc('title').text())

# 从文件创建
doc = pyquery.PyQuery(filename='example.html')
print(doc)

# 从lxml对象创建
from lxml import etree

element = etree.HTML("<div>内容</div>")
doc = pyquery.PyQuery(element)
print(doc)
python 复制代码
<html><body><div class="container">内容</div></body></html>
<html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="data:,"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Example Domain</title><style>html{color-scheme:light dark;background:light-dark(#eee,#222)}body{font:16px/1.6 system-ui,sans-serif;max-width:26em;margin:auto;padding:25vh 2em 2em;text-align:center}</style></head><body><p>This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.</p><script src="/s.js"/></body></html>
<html>
<body>
<div class="container">Example Content</div>
</body>
</html>
<html><body><div>内容</div></body></html>

CSS选择器

python 复制代码
import pyquery

html = """
<div id="container">
    <h1 class="title">PyQuery示例</h1>
    <div class="content">
        <p>这是第一段内容</p>
        <p class="important">这是重要内容</p>
    </div>
    <ul id="menu">
        <li><a href="/">首页</a></li>
        <li><a href="/about">关于</a></li>
        <li><a href="/contact" target="_blank">联系我们</a></li>
    </ul>
</div>
"""
doc = pyquery.PyQuery(html)

# 选择所有div元素
divs = doc('div')
print(f"找到 {len(divs)} 个div元素")

# 通过ID选择
container = doc('#container')
print(f"容器标题: {container.find('h1').text()}")

# 通过class类名选择
content = doc('.content')
print(f"内容部分: {content.text()}")

# 组合选择器
important_p = doc('p.important')
print(f"重要段落: {important_p.text()}")

# 后代层级选择器
menu_links = doc('#menu a')
print(f"菜单链接数量: {len(menu_links)}")

# 直接子元素选择器
direct_children = doc('#menu > li')
print(f"直接子元素数量: {len(direct_children)}")

# 属性选择器
external_links = doc('a[target="_blank"]')
print(f"外部链接: {external_links.attr('href')}")
python 复制代码
找到 2 个div元素
容器标题: PyQuery示例
内容部分: 这是第一段内容
这是重要内容
重要段落: 这是重要内容
菜单链接数量: 3
直接子元素数量: 3
外部链接: /contact

元素操作

PyQuery 提供了丰富的方法来获取和操作元素。

python 复制代码
import pyquery

html = """
<div class="container">
    <h2 class="title">PyQuery操作示例</h2>
    <div class="content">
        <p>这是<b>加粗</b>文本和<a href="https://example.com">链接</a></p>
    </div>
    <div class="footer">页脚信息</div>
</div>
"""
doc = pyquery.PyQuery(html)

# 获取子节点的HTML内容
html_content = doc('.content').html()
print(f"HTML内容: {html_content}")

# 获取纯文本内容
text_content = doc('.content').text()
print(f"文本内容: {text_content}")

# 获取属性
link = doc('a')
link_url = link.attr('href')
print(f"链接URL: {link_url}")
# 或者使用这种方式
link_url = link.attr.href
print(f"链接URL: {link_url}")

# 获取所有类名
class_names = doc('.container').attr('class')
print(f"容器类名: {class_names}")

# 遍历元素
print("所有div内容:")
for div in doc('div').items():
    print(f"- {div.text()}")
python 复制代码
HTML内容: 
        <p>这是<b>加粗</b>文本和<a href="https://example.com">链接</a></p>
    
文本内容: 这是加粗文本和链接
链接URL: https://example.com
链接URL: https://example.com
容器类名: container
所有div内容:
- PyQuery操作示例
这是加粗文本和链接
页脚信息
- 这是加粗文本和链接
- 页脚信息

过滤和遍历

python 复制代码
import pyquery

html = """
<ul id="fruits">
    <li class="apple">苹果</li>
    <li class="banana">香蕉</li>
    <li class="orange important">橙子</li>
    <li class="pear">梨</li>
    <li class="grape important">葡萄</li>
</ul>
<p>其他内容</p>
"""
doc = pyquery.PyQuery(html)

# eq(i) - 获取指定索引为 i 的元素
first_item = doc('#fruits li').eq(0)
print(f"第一个水果: {first_item.text()}")

# filter() - 根据选择器过滤
important_items = doc('#fruits li').filter('.important')
print(f"重要水果: {', '.join(item.text() for item in important_items.items())}")

# find() - 查找后代元素
fruits = doc('#fruits').find('li')
print(f"找到 {len(fruits)} 个水果")

# children() - 获取子元素
list_items = doc('ul').children()
print(f"列表项数量: {len(list_items)}")

# parent() - 获取父元素
parent_element = doc('li').eq(0).parent()
print(f"父元素标签: {parent_element[0].tag}")

# siblings() - 获取兄弟元素
other_fruits = doc('li.apple').siblings()
print(f"其他水果数量: {len(other_fruits)}")

# each() - 遍历元素
fruits_list = []


def collect_fruits(i, elem):
    item = pyquery.PyQuery(elem)
    fruits_list.append((i, item.text()))


doc('li').each(collect_fruits)
print(f"水果列表: {fruits_list}")
python 复制代码
第一个水果: 苹果
重要水果: 橙子, 葡萄
找到 5 个水果
列表项数量: 5
父元素标签: ul
其他水果数量: 4
水果列表: [(0, '苹果'), (1, '香蕉'), (2, '橙子'), (3, '梨'), (4, '葡萄')]

链式调用

示例中有涉及到 find() 和 filter() 方法的使用,他们的区别:

  • find() 往下找 :在当前元素内部的后代里查,返回新集合。

  • filter() 原地筛 :在当前集合自身的元素里挑符合条件的,不往下找。

python 复制代码
import pyquery

html = """
<div class="container">
    <div class="article">
        <h2 class="title">文章标题1</h2>
        <h2>普通标题</h2>
        <h2 class="important title">重要标题</h2>
        <p>段落内容</p>
    </div>
</div>
"""
doc = pyquery.PyQuery(html)

# 链式调用示例
titles = doc('.article').find('h2').filter('.important').text()
print(f"通过链式调用找到的标题: {titles}")

# 等价于
articles = doc('.article')
h2_elements = articles.find('h2')
important_h2 = h2_elements.filter('.important')
titles = important_h2.text()
print(f"通过分步调用找到的标题: {titles}")

DOM对象修改

PyQuery 支持针对 DOM 对象的添加,修改,删除,替换等操作。

python 复制代码
import pyquery

html = """
<div class="container">
    <a href="https://example.com">链接</a>
    <div class="content">原始内容</div>
    <div class="ads">广告内容</div>
</div>
"""
doc = pyquery.PyQuery(html)

# 添加类
doc('div').addClass('new-class')
print("添加类后:")
print(doc)

# 删除类
# doc('div').removeClass('new-class')
doc('div').remove_class('new-class')
print("\n删除类后:")
print(doc)

# 添加属性
doc('a').attr('target', '_blank')
print("\n添加属性后:")
print(doc('a'))

# 设置内容
doc('.content').html('<p>修改内容</p>')
print("\n设置内容后:")
print(doc('.content'))

# 插入内容
doc('.container').append('<div class="footer">页脚</div>')
print("\n添加页脚后:")
print(doc)

# 移除元素
doc('.ads').remove()
print("\n移除广告后:")
print(doc)

# 写入 test.html
print("修改内容写入 test.html 文件")
with open("test.html", 'w', encoding='utf-8') as f:
    f.write(doc.outer_html())
python 复制代码
添加类后:
<div class="container new-class">
    <a href="https://example.com">链接</a>
    <div class="content new-class">原始内容</div>
    <div class="ads new-class">广告内容</div>
</div>

删除类后:
<div class="container">
    <a href="https://example.com">链接</a>
    <div class="content">原始内容</div>
    <div class="ads">广告内容</div>
</div>

添加属性后:
<a href="https://example.com" target="_blank">链接</a>
    

设置内容后:
<div class="content"><p>修改内容</p></div>
    

添加页脚后:
<div class="container">
    <a href="https://example.com" target="_blank">链接</a>
    <div class="content"><p>修改内容</p></div>
    <div class="ads">广告内容</div>
<div class="footer">页脚</div></div>

移除广告后:
<div class="container">
    <a href="https://example.com" target="_blank">链接</a>
    <div class="content"><p>修改内容</p></div>
    
<div class="footer">页脚</div></div>
修改内容写入 test.html 文件

伪类选择器

常用的伪类选择器及pyquery的支持程度如下:

结构位置伪类

伪类 含义 pyquery 支持
:first-child 父元素的第一个子元素 ✅ 支持
:last-child 父元素的最后一个子元素 ✅ 支持
:only-child 父元素唯一的子元素 ✅ 支持
:nth-child(n) 父元素下第 n 个子元素 ✅ 支持(简单参数)
:nth-last-child(n) 倒数第 n 个子元素 ✅ 支持
:empty 没有子节点(含文本)的元素 ✅ 支持
:first-of-type 等 同类型元素中的第一个 ⚠️ 有条件支持
:nth-of-type(n) 等 同类型元素中的第 n 个 ⚠️ 有条件支持
:root 文档根元素 ❌ 不支持

表单状态伪类

伪类 含义 pyquery 支持
:checked 被选中的 checkbox / radio ✅ 支持(依据 checked 属性)
:selected 被选中的 option ✅ 支持(依据 selected 属性)
:disabled 禁用的表单元素 ✅ 支持(依据 disabled 属性)
:enabled 可用的表单元素 ✅ 支持
:required / :optional 必填 / 选填 ❌ 不支持
:valid / :invalid 校验通过 / 不通过 ❌ 不支持

jQuery扩展伪类

伪类 含义 pyquery 支持
:first / :last 结果集中的第一个 / 最后一个 ✅ 支持
:eq(n) 结果集中索引为 n 的元素(从 0 开始) ✅ 支持
:lt(n) / :gt(n) 索引小于 / 大于 n 的元素 ✅ 支持
:even / :odd 索引为偶数 / 奇数的元素 ✅ 支持
:button / :checkbox 等 特定 type 的 input 元素 ✅ 支持
python 复制代码
import pyquery

html = """
<ul id="fruits">
    <li>苹果</li>
    <li>香蕉</li>
    <li>橙子</li>
    <li>梨</li>
    <li>葡萄</li>
</ul>
<div class="empty"></div>
<div>包含文本</div>
"""
doc = pyquery.PyQuery(html)

# 第一个元素
first_item = doc('li:first')
print(f"第一个水果: {first_item.text()}")
# first_item = doc('li').eq(0)
# print(f"第一个水果: {first_item.text()}")

# 最后一个元素
last_item = doc('li:last')
print(f"最后一个水果: {last_item.text()}")
# last_item = doc('li').eq(len(doc('li')) - 1)
# print(f"最后一个水果: {last_item.text()}")

# 偶数索引的元素 (索引从0开始,所以是1,3,5...)
even_items = doc('li:even')
print(f"偶数索引水果: {', '.join(item.text() for item in even_items.items())}")

# 奇数索引的元素
odd_items = doc('li:odd')
print(f"奇数索引水果: {', '.join(item.text() for item in odd_items.items())}")

# 第n个元素
nth_item = doc('li:eq(2)')  # 索引从0开始,这是第3个元素
print(f"第3个水果: {nth_item.text()}")

# 包含特定文本的元素
contains_items = doc('div:contains("包含文本")').filter(
    lambda i, e: e.text and '包含文本' in e.text
)
# print(contains_items)
print(f"包含特定文本的div数量: {len(contains_items)}")

# 空元素
empty_elements = doc('div:empty')
print(f"空div数量: {len(empty_elements)}")
python 复制代码
第一个水果: 苹果
最后一个水果: 葡萄
偶数索引水果: 苹果, 橙子, 葡萄
奇数索引水果: 香蕉, 梨
第3个水果: 橙子
包含特定文本的div数量: 1
空div数量: 1

表单处理

PyQuery 的 css 选择器里边,如果使用空格隔开,则表示是对应的子元素。如:

复制代码
doc('.box .item') 表示 DOM 中类名为 box 的子元素,子元素的类名为 item 的元素。
python 复制代码
import pyquery

html = """
<form id="login-form">
    <input id="username" name="username" value="test_user">
    <input id="password" name="password" type="password">
    <input id="remember-me" name="remember" type="checkbox" checked>
    <select id="role">
        <option value="user">普通用户</option>
        <option value="admin" selected>管理员</option>
        <option value="guest">访客</option>
    </select>
</form>
"""
doc = pyquery.PyQuery(html)

# 获取表单元素的值
print(doc('#username'))
username = doc('#username').val()
print(f"用户名: {username}")

# 设置表单元素的值
doc('#username').val('new_user')
print(doc('#username'))
print(f"修改后的用户名: {doc('#username').val()}")

# 检查复选框是否被选中
is_checked = doc('#remember-me').is_(':checked')
print(f"记住我选项是否选中: {is_checked}")

# 获取下拉框选中的值
selected_role = doc('#role option:selected')
print(f"选中的角色: {selected_role.text()} (值: {selected_role.val()})")
python 复制代码
<input id="username" name="username" value="test_user"/>
    
用户名: test_user
<input id="username" name="username" value="new_user"/>
    
修改后的用户名: new_user
记住我选项是否选中: True
选中的角色: 管理员 (值: admin)

表格提取

以下是使用 pyquery 提取 html 中表格数据的示例。

python 复制代码
import pandas as pd
import pyquery

html = """
<table class="data-table">
  <thead>
    <tr>
      <th>名称</th>
      <th>价格</th>
      <th>库存</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>产品A</td>
      <td>¥100</td>
      <td>50</td>
    </tr>
    <tr>
      <td>产品B</td>
      <td>¥200</td>
      <td>30</td>
    </tr>
    <tr>
      <td>产品C</td>
      <td>¥150</td>
      <td>0</td>
    </tr>
  </tbody>
</table>
"""


def extract_table(html):
    """从HTML提取表格数据并转换为DataFrame"""
    doc = pyquery.PyQuery(html)
    table = doc('table.data-table')

    # 提取表头
    headers = [th.text() for th in table.find('thead th').items()]
    # print(headers)

    # 提取行数据
    rows = []
    for tr in table.find('tbody tr').items():
        row = [td.text() for td in tr('td').items()]
        rows.append(row)

    # 创建DataFrame
    df = pd.DataFrame(rows, columns=headers)
    return df


# 提取表格数据
df = extract_table(html)
print("提取的表格数据:")
print(df)
python 复制代码
提取的表格数据:
    名称    价格  库存
0  产品A  ¥100  50
1  产品B  ¥200  30
2  产品C  ¥150   0

选择器优化

和 XPath 类似,浏览器生成的 CSS 选择器通常很长且脆弱。其优化策略基本上与 XPath 路径的优化策略是一致的。在构造选择器的时候,我们尽量可以优先选元素标签附近的 ID 或 类名来辅助定位,其次再是使用标签,属性等,最主要目的是尽量不要使用过深的嵌套。

python 复制代码
import pyquery

html = """
<div id="main">
  <div class="container">
    <div id="content" class="article-content main-content">
      <h2 class="title">文章标题</h2>
      <p>内容...</p>
    </div>
  </div>
</div>
"""
doc = pyquery.PyQuery(html)

# 浏览器生成的复杂选择器 - 太具体,脆弱
complex_selector = '#main > div.container > div#content.article-content.main-content > h2.title'
print(f"复杂选择器找到: {doc(complex_selector).text()}")

# 优化策略1: 使用ID - 最快且可靠
id_selector = '#content h2'
print(f"ID选择器找到: {doc(id_selector).text()}")

# 优化策略2: 使用类名 - 通常比标签更稳定
class_selector = '.article-content .title'
print(f"类选择器找到: {doc(class_selector).text()}")

# 优化策略3: 使用属性 - 当没有好的ID或类时
attr_selector = 'div[class*="content"] h2'
print(f"属性选择器找到: {doc(attr_selector).text()}")

# 优化策略4: 避免过深嵌套
simple_selector = '.title'  # 如果类名是唯一的
print(f"简单选择器找到: {doc(simple_selector).text()}")
python 复制代码
复杂选择器找到: 文章标题
ID选择器找到: 文章标题
类选择器找到: 文章标题
属性选择器找到: 文章标题
简单选择器找到: 文章标题
相关推荐
2601_9679791211 天前
0 到 1 学爬虫:用 Python 爬取网页数据指南一(附实战案例)
爬虫·python·实战案例·html解析·网页数据
缘空如是9 个月前
基础工具之jsoup工具
java·jsoup·html解析
Code_流苏2 年前
Python星球日记 - 第16天:爬虫基础(仅学习使用)
beautifulsoup·python爬虫·requests库·数据提取·html解析
唐叔在学习2 年前
【Python爬虫高级技巧】BeautifulSoup高级教程:数据抓取、性能调优、反爬策略,全方位提升爬虫技能!
python·beautifulsoup·html解析·爬虫进阶·反爬策略
熊文豪2 年前
XPath语法完全指南(实战详解版)
自动化测试·xpath·网页爬虫·数据提取·零基础入门·html解析·xpath教程
Shadow℘Coder2 年前
爬虫实战:数据请求与解析(4种不同方式的数据解析)
爬虫·python·xpath·数据解析·pyquery·beautiful soup
程序无涯海2 年前
Python爬虫教程第5篇-使用BeautifulSoup查找html元素几种常用方法
爬虫·python·html·beautifulsoup·html解析
程序无涯海2 年前
Python爬虫教程第4篇-使用BeautifulSoup解析html
爬虫·python·beautifulsoup·html解析