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选择器找到: 文章标题
类选择器找到: 文章标题
属性选择器找到: 文章标题
简单选择器找到: 文章标题