Metadata-Version: 2.4
Name: DrissionPageAsync
Version: 5.0.0b1
Summary: DrissionPage 的异步版本，基于 asyncio 的网页自动化工具。
Home-page: https://DrissionPage.cn
Author: g1879
Author-email: g1879@qq.com
License: BSD
Keywords: DrissionPage asyncio async chromium cdp webdriver
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24
Requires-Dist: websockets>=12
Requires-Dist: lxml
Requires-Dist: cssselect
Requires-Dist: tldextract>=3.4.4
Requires-Dist: psutil
Requires-Dist: ftfy
Requires-Dist: click
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# DrissionPageAsync

[![PyPI](https://img.shields.io/pypi/v/DrissionPageAsync)](https://pypi.org/project/DrissionPageAsync/)
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-BSD-green)](https://github.com/AIPythoner/DrissionPage-async/blob/master/LICENSE)

[DrissionPage](https://github.com/g1879/DrissionPage) 5.0.0b1 的异步（asyncio）版本。

功能与同步版一一对应，区别在于所有会产生通信的操作都变成了协程：等待元素、等待页面加载时会让出事件循环，
因此可以用一个线程同时驱动多个标签页。

```python
import asyncio
from DrissionPageAsync import Chromium


async def main():
    browser = await Chromium.connect()
    tab = await browser.latest_tab()
    await tab.get('https://www.baidu.com')

    await tab.ele('#kw').input('DrissionPage')
    await tab.ele('#su').click()

    print(await tab.title())
    await browser.quit()


asyncio.run(main())
```

更多可运行示例见 [`examples/`](examples/README.md)。

## 安装

```bash
pip install DrissionPageAsync
```

需要 Python 3.9+。也可以从源码安装：

```bash
pip install .
```

## 从同步版迁移

### 1. 入口改为 `await Chromium.connect(...)`

启动／接管浏览器需要通信，而 `__init__` 不能是协程：

```python
browser = Chromium()                  # 同步版
browser = await Chromium.connect()    # 异步版
```

`SessionPage` 不需要通信即可创建，用法不变：`page = SessionPage()`。

### 2. 会通信的属性变成了协程方法

这是改动最大的一处 —— 加一对括号，再 `await`：

| 同步版 | 异步版 |
| --- | --- |
| `tab.html` | `await tab.html()` |
| `tab.url` / `tab.title` | `await tab.url()` / `await tab.title()` |
| `ele.text` / `ele.tag` / `ele.attrs` | `await ele.text()` / `await ele.tag()` / `await ele.attrs()` |
| `ele.states.is_displayed` | `await ele.states.is_displayed()` |
| `ele.rect.size` / `ele.rect.location` | `await ele.rect.size()` / `await ele.rect.location()` |
| `browser.latest_tab` / `browser.tab_ids` | `await browser.latest_tab()` / `await browser.tab_ids()` |

**只返回子对象、不产生通信的访问器仍然是属性**，不要加括号：

```
tab.set    tab.wait    tab.scroll   tab.rect    tab.states
tab.listen tab.console tab.actions  ele.click   ele.select（例外，见下）
```

判断规则：`x.y` 拿到的还是个"工具对象"就是属性；拿到的是数据（文本、坐标、布尔值）就是协程方法。

`states` 和 `set` 下的成员一律是协程方法（其中少数并不通信，为了少一条例外规则而统一）。
`ele.select()` 因为要先读标签名判断是不是 `<select>`，也是协程方法。

### 3. 链式调用只需 await 一次

动作链、筛选器，以及 ``ele()`` / ``eles()`` 的后续操作，如果每步都 await 会很难读。
中间步骤只是记录下来，整条链 await 一次才真正执行。写法和同步版几乎一样：

```python
# 查找后立刻点击／取值，不必套两层 await
await tab.ele('#su').click()
title = await tab.ele('t:h1').text()
await tab.ele('#form').ele('#su').click()  # 元素内再查找，仍只 await 一次
ele = await tab.ele('#kw')                 # 仍然可以先拿到元素本身
await (await tab.ele('#su')).click()       # 旧写法继续有效

# 动作链
await tab.actions.move_to(ele).click().type('abc')
await tab.actions.hold(ele).move_to((100, 200)).release()

# 筛选器（返回多个）
eles = await tab.eles('tag:div')
r = await eles.filter.tag('div').text('abc')
r = await eles.filter.displayed().attr('class', 'box')
r = await tab.eles('tag:div').filter.tag('div').text('abc')

# 筛选器（返回一个）本身就是终结操作
e = await eles.filter_one.tag('div')
```

注意：筛选器和未 await 的 ``eles()`` 在 await 之前不能迭代／取长度／取下标，需要先 await 拿到列表。

### 4. 迭代器改为异步迭代

```python
await tab.listen.start('api/data')
async for packet in tab.listen.steps(count=3):
    print(packet.url)

async for msg in tab.console.steps(timeout=5):
    print(msg.text)
```

### 5. 数据包对象保持同步

`listen` 捕获到的 `DataPacket`、`Request`、`Response` 等只是对已收到数据的包装，
不产生任何通信，因此它们的属性**不用 await**：

```python
packet = await tab.listen.wait()
print(packet.url, packet.response.status, packet.response.body)   # 都是普通属性
```

### 6. 内部工厂函数

如果你的代码直接构造过元素或标签页对象，需要改用协程工厂（补全 id 要通信）：

| 同步版 | 异步版 |
| --- | --- |
| `ChromiumElement(owner, ...)` | `await make_chromium_ele(owner, ...)` |
| `ShadowRoot(ele, ...)` | `await make_shadow_root(ele, ...)` |
| `ChromiumTab(browser, ...)` | `await make_chromium_tab(browser, ...)` |
| `ChromiumFrame(owner, ele)` | `await make_chromium_frame(owner, ele)` |

正常使用 `tab.ele()`、`browser.new_tab()` 等接口不受影响。

## 依赖变化

| 用途 | 同步版 | 异步版 |
| --- | --- | --- |
| CDP 通信 | websocket-client | **websockets** |
| HTTP | requests | **httpx** |
| 文件下载 | DrissionGet | 内置 `_units/http_downloader.py` |
| 文件名工具 | DrissionRecord | 内置 `_functions/paths.py` |
| 大小写不敏感字典 | requests.structures | 内置 `_functions/structures.py` |
| 并发 | threading / queue | asyncio Task / Queue |

`page.session` 返回的是 `httpx.AsyncClient`，`page.response` 返回 `httpx.Response`。

## 已知限制

以下几处是底层库能力差异造成的，不是遗漏：

- **httpx 的部分设置只能在创建客户端时指定**，创建后无法修改。
  `set.verify()`、`set.cert()`、`set.proxies()`、`set.trust_env()`、`set.add_adapter()`、`set.stream()`
  会抛出 `RuntimeError` 并提示改用 `SessionOptions` 在创建前设置。
- **下载器不支持单文件分块并行下载**（同步版 DrissionGet 的 `split` 参数），传入会被忽略；
  多文件并发下载正常，由 `roads` 控制并发数。
- `SessionOptions.from_session()` 只能回读 httpx 公开的设置（headers、cookies、auth、params、
  max_redirects、event_hooks），verify/cert/proxies 等无法回读，保持原值。
- `__repr__` 不能 await，因此 `ChromiumElement` / `ChromiumFrame` 的 repr 只显示已缓存的标签名和 id，
  要看完整内容请用 `await ele.html()`。
- 事件回调既可以是普通函数也可以是协程函数，两种都支持。

## 与同步版的一处行为修正

同步版 `BaseWaiter._loading()` 接收了 `gap` 参数却没有传给 `wait_until()`，
导致 `wait.load_start()` 指定的 `.002` 轮询间隔实际未生效。异步版已接上该参数。

## 类型提示

每个模块都配有翻译后的 `.pyi` 存根，签名已同步为 `async def`，IDE 补全和类型检查照常可用。
