概述

  1. DrissionPage是一个基于python的网页自动化工具
  2. 它既能控制浏览器,也能收发数据包,还能把两者合二为一
  3. 可兼顾浏览器自动化的便利性和requests的高效率(刚开始学python,requests就没去学了,直接来学这个)
  4. 它功能强大,内置无数人性化设计和便捷功能
  5. 语法真的简单而优雅,代码量少,对于我这样的新手来说很友好!

安装

1
2
3
4
5
6
#使用清华源安装
pip install DrissionPage -i https://pypi.tuna.tsinghua.edu.cn/simple
#使用豆瓣源安装
pip install DrissionPage -i https://pypi.douban.com/simple
#升级最新稳定版
pip install DrissionPage --upgrade

导入

页面类

ChromiumPage

如果只要控制浏览器,导入ChromiumPage

1
from DrissionPage import ChromiumPage

SessionPage

如果只要收发数据包,导入SessionPage

1
from DrissionPage import SessionPage

WebPage

WebPage是功能最全面的页面类,既可控制浏览器,也可收发数据包

1
from DrissionPage import WebPage

配置工具

ChromiumOptions

ChromiumOptions类用于设置浏览器启动参数
这些参数只有在启动浏览器时有用,接管已存在的浏览器时是不生效的

1
from DrissionPage import ChromiumOptions
设置浏览器的路径
1
2
3
4
from DrissionPage import ChromiumOptions

path = r'xxx\xxx\xxx\chrome.exe' # 请改为你电脑内Chrome可执行文件路径
ChromiumOptions().set_browser_path(path).save()

SessionOptions

SessionOptions类用于设置Session对象启动参数
用于配置SessionPage或WebPages 模式的连接参数

1
from DrissionPage import SessionOptions

Settings

Settings用于设置全局运行配置,如找不到元素时是否抛出异常

1
from DrissionPage.common import Settings

其它工具

keys

键盘按键类,用于键入 ctrl、alt 等按键

1
from DrissionPage.common import Keys

Actions

动作链,用于执行一系列动作
在浏览器页面对象中已有内置,无如特殊需要无需主动导入

1
from DrissionPage.common import Actions

By

与 selenium 一致的By类,便于项目迁移

1
from DrissionPage.common import By

其它工具

wait_until:可等待传入的方法结果为真
make_session_ele:从 html 文本生成ChromiumElement对象
configs_to_here:把配置文件复制到当前路径
get_blob:获取指定的 blob 资源

1
2
3
from DrissionPage.common import wait_until
from DrissionPage.common import make_session_ele
from DrissionPage.common import configs_to_here

异常

异常放在DrissionPage.errors路径

1
from DrissionPage.errors import ElementNotFoundError

衍生对象类型

Tab、Element 等对象是由 Page 对象生成,开发过程中需要类型判断时需要导入这些类型
可在DrissionPage.items路径导入

1
2
3
4
5
6
7
from DrissionPage.items import SessionElement
from DrissionPage.items import ChromiumElement
from DrissionPage.items import ShadowRoot
from DrissionPage.items import NoneElement
from DrissionPage.items import ChromiumTab
from DrissionPage.items import WebPageTab
from DrissionPage.items import ChromiumFrame

三种页面对象

SessionPage

SessionPage对象和WebPage对象的 s 模式,可用收发数据包的形式访问网页。

SessionPage是一个使用使用Session(requests 库)对象的页面,它使用 POM 模式封装了网络连接和 html 解析功能,使收发数据包也可以像操作页面一样便利。

ChromiumPage

ChromiumPage对象和WebPage对象的 d 模式,可操控浏览器。

顾名思义,ChromiumPage是 Chromium 内核浏览器的页面,它用 POM 方式封装了操控网页所需的属性和方法。

使用它,我们可与网页进行交互,如调整窗口大小、滚动页面、操作弹出框等等。

通过从中获取的元素对象,我们还可以跟页面中的元素进行交互,如输入文字、点击按钮、选择下拉菜单等等。

甚至,我们可以在页面或元素上运行 JavaScript 代码、修改元素属性、增删元素等。

可以说,操控浏览器的绝大部分操作,都可以由ChromiumPage及其衍生的对象完成,而它们的功能,还在不断增加。

除了与页面和元素的交互,ChromiumPage还扮演着浏览器控制器的角色,可以说,一个ChromiumPage对象,就是一个浏览器。

它可以对标签页进行管理,可以对下载任务进行控制。可以为每个标签页生成独立的页面对象(ChromiumTab),以实现多标签页同时操作,而无需切入切出。

WebPage

WebPage对象整合了SessionPage和ChromiumPage,实现了两者之间的互通。

它既可以操控浏览器,也可以收发数据包,并且会在两者之间同步登录信息。

它有 d 和 s 两种模式,分别对应操控浏览器和收发数据包。

WebPage可灵活的在两种模式间切换,从而实现一些有趣的用法。

比如,网站登录代码非常复杂,用数据包实现过于烧脑,我们可以用浏览器处理登录,再通过切换模式用收发数据包的方式去采集数据。

两种模式的使用逻辑是一致的,跟ChromiumPage没有区别,易于上手。

DrissionPage事件

元素定位查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 根据属性查找,@ 后面可跟任意属性
page.ele('@id:ele_id', timeout=2) # 查找 id 为 ele_id 的元素,设置等待时间2秒
page.eles('@class') # 查找所有拥有 class 属性的元素
page.eles('@class:class_name') # 查找所有 class 含有 ele_class 的元素
page.eles('@class=class_name') # 查找所有 class 等于 ele_class 的元素

# 根据 class 或 id 查找
page.ele('#ele_id') # 等价于 page.ele('@id=ele_id')
page.ele('#:ele_id') # 等价于 page.ele('@id:ele_id')
page.ele('.ele_class') # 等价于 page.ele('@class=ele_class')
page.ele('.:ele_class') # 等价于 page.ele('@class:ele_class')

# 根据 tag name 查找
page.ele('tag:li') # 查找第一个 li 元素
page.eles('tag:li') # 查找所有 li 元素

# 根据 tag name 及属性查找
page.ele('tag:div@class=div_class') # 查找 class 为 div_class 的 div 元素
page.ele('tag:div@class:ele_class') # 查找 class 含有 ele_class 的 div 元素
page.ele('tag:div@class=ele_class') # 查找 class 等于 ele_class 的 div 元素
page.ele('tag:div@text():search_text') # 查找文本含有 search_text 的 div 元素
page.ele('tag:div@text()=search_text') # 查找文本等于 search_text 的 div 元素

# 根据文本内容查找
page.ele('search text') # 查找包含传入文本的元素
page.eles('text:search text') # 如文本以 @、tag:、css:、xpath:、text: 开头,则应在前加上 text: 避免冲突
page.eles('text=search text') # 文本等于 search_text 的元素

# 根据 xpath 或 css selector 查找
page.eles('xpath://div[@class="ele_class"]')
page.eles('css:div.ele_class')

# 根据 loc 查找
loc1 = By.ID, 'ele_id'
loc2 = By.XPATH, '//div[@class="ele_class"]'
page.ele(loc1)
page.ele(loc2)

# 查找下级元素
element = page.ele('@id:ele_id')
element.ele('@class:class_name') # 在 element 下级查找第一个 class 为 ele_class 的元素
element.eles('tag:li') # 在 ele_id 下级查找所有li元素

# 根据位置查找
element.parent # 父元素
element.next # 下一个兄弟元素
element.prev # 上一个兄弟元素

# 获取 shadow-root,把它作为元素对待。只支持 open 的 shadow-root
ele1 = element.shadow_root.ele('tag:div')

# 串连查找
page.ele('@id:ele_id').ele('tag:div').next.ele('some text').eles('tag:a')

# 简化写法
eles = page('@id:ele_id')('tag:div').next('some text').eles('tag:a')
ele2 = ele1('tag:li').next('some text')

元素操作

1
2
3
4
5
6
7
8
9
10
11
12
element.click(by_js)  # 点击元素,可选择是否用 js 方式点击
element.input(value) # 输入文本
element.run_script(js) # 对元素运行 JavaScript 脚本
element.submit() # 提交
element.clear() # 清空元素
element.screenshot(path, filename) # 对元素截图
element.select(text) # 根据文本选择下拉列表
element.set_attr(attr, value) # 设置元素属性值
element.remove_attr(attr) # 删除属性
element.drag(x, y, speed, shake) # 拖动元素相对距离,可设置速度和是否随机抖动
element.drag_to(ele_or_loc, speed, shake) # 拖动元素到另一个元素或某个坐标,可设置速度和是否随机抖动
element.hover() # 在元素上悬停鼠标

获取元素属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
element.html  # 返回元素 outerHTML
element.inner_html # 返回元素 innerHTML
element.tag # 返回元素 tag name
element.text # 返回元素 innerText 值
element.comments # 返回元素内注释列表
element.link # 返回元素 href 或 src 绝对 url
element.texts() # 返回元素内所有直接子节点的文本,包括元素和文本节点,可指定只返回文本节点
element.attrs # 返回元素所有属性的字典
element.attr(attr) # 返回元素指定属性的值
element.css_path # 返回元素绝对 css 路径
element.xpath # 返回元素绝对 xpath 路径
element.parent # 返回元素父元素
element.next # 返回元素后一个兄弟元素
element.prev # 返回元素前一个兄弟元素
element.parents(num) # 返回第 num 级父元素
element.nexts(num, mode) # 返回后面第几个元素或节点
element.prevs(num, mode) # 返回前面第几个元素或节点
element.ele(loc_or_str, timeout) # 返回当前元素下级第一个符合条件的子元素、属性或节点文本
element.eles(loc_or_str, timeout) # 返回当前元素下级所有符合条件的子元素、属性或节点文本

示例

语法较简单,简单看了下语法,弄了几个示例上手一下

多线程操作标签页

此示例演示如何使用多个线程同时控制一个浏览器的多个标签页进行采集

目标网址:

https://gitee.com/explore/ai

https://gitee.com/explore/machine-learning

按F12,可以看到每个标题元素的class属性均为title project-namespace-path,可批量获取
虽然 gitee 开源项目列表可以用 s 模式采集,但现在为了演示多标签页操作,还是使用浏览器进行操作
使用ChromiumPage的get_tab()方法,分别获取两个标签页的对象,传入不同线程进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from threading import Thread

from DrissionPage import ChromiumPage
from DataRecorder import Recorder


def collect(tab, recorder, title):
"""用于采集的方法
:param tab: ChromiumTab 对象
:param recorder: Recorder 记录器对象
:param title: 类别标题
:return: None
"""
num = 1 # 当前采集页数
while True:
# 遍历所有标题元素
for i in tab.eles('.title project-namespace-path'):
# 获取某页所有库名称,记录到记录器
recorder.add_data((title, i.text, num))

# 如果有下一页,点击翻页
btn = tab('@rel=next', timeout=2)
if btn:
btn.click(by_js=True)
tab.wait.load_start()
num += 1

# 否则,采集完毕
else:
break


def main():
# 新建页面对象
page = ChromiumPage()
# 第一个标签页访问网址
page.get('https://gitee.com/explore/ai')
# 获取第一个标签页对象
tab1 = page.get_tab()
# 新建一个标签页并访问另一个网址
tab2 = page.new_tab('https://gitee.com/explore/machine-learning')
# 获取第二个标签页对象
tab2 = page.get_tab(tab2)

# 新建记录器对象
recorder = Recorder('data.csv')

# 多线程同时处理多个页面
Thread(target=collect, args=(tab1, recorder, 'ai')).start()
Thread(target=collect, args=(tab2, recorder, '机器学习')).start()


if __name__ == '__main__':
main()

多线程操作多个浏览器

此示例演示如何使用多个线程同时控制多个浏览器进行采集

目标网址:

https://gitee.com/explore/ai

https://gitee.com/explore/machine-learning

按F12,可以看到每个标题元素的class属性均为title project-namespace-path,可批量获取虽然 gitee 开源项目列表可以用 s 模式采集,但现在为了演示多标签页操作,还是使用浏览器进行操作使用ChromiumOptions的auto_port()方法,可设置独立的浏览器环境,每个浏览器需一个ChromiumOptions对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from threading import Thread

from DrissionPage import ChromiumPage, ChromiumOptions
from DataRecorder import Recorder


def collect(page, recorder, title):
"""用于采集的方法
:param page: ChromiumTab 对象
:param recorder: Recorder 记录器对象
:param title: 类别标题
:return: None
"""
num = 1 # 当前采集页数
while True:
# 遍历所有标题元素
for i in page.eles('.title project-namespace-path'):
# 获取某页所有库名称,记录到记录器
recorder.add_data((title, i.text, num))

# 如果有下一页,点击翻页
btn = page('@rel=next', timeout=2)
if btn:
btn.click(by_js=True)
page.wait.load_start()
num += 1

# 否则,采集完毕
else:
break


def main():
# 创建两个配置对象,并设置自动分配端口
co1 = ChromiumOptions().auto_port()
co2 = ChromiumOptions().auto_port()
# 新建2个页面对象,各自使用一个配置对象
page1 = ChromiumPage(co1)
page2 = ChromiumPage(co2)
# 第一个浏览器访问第一个网址
page1.get('https://gitee.com/explore/ai')
# 第二个浏览器访问另一个网址
page2.get('https://gitee.com/explore/machine-learning')

# 新建记录器对象
recorder = Recorder('data.csv')

# 多线程同时处理多个页面
Thread(target=collect, args=(page1, recorder, 'ai')).start()
Thread(target=collect, args=(page2, recorder, '机器学习')).start()


if __name__ == '__main__':
main()

下载功能示例

这个示例用于演示下载功能将用两种方法下载最新版 QQ 客户端

使用浏览器下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用click.to_download()方法
from DrissionPage import ChromiumPage

# 创建页面对象
page = ChromiumPage()
# 访问网页
page.get('https://im.qq.com/pcqq/index.shtml')
# 获取按钮元素
ele = page('全新体验版下载')
# 等待按钮元素生成
ele.wait.has_rect()
# 点击按钮触发下载,并设置下载路径和文件名
mission = ele.click.to_download(save_path='tmp', rename='QQ.exe')
# 等待下载任务完成
mission.wait()

使用download()方法下载

1
2
3
4
5
6
# 直接下载
from DrissionPage import SessionPage

page = SessionPage()
url = 'https://dldir1.qq.com/qqfile/qq/QQNT/4a642cfb/QQ9.9.7.21357_x64.exe'
page.download(url, goal_path='tmp', rename='QQ.exe')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 可以拦截浏览器下载任务,获取下载信息,转用download()下载
from DrissionPage import ChromiumPage

# 创建页面对象
page = ChromiumPage()
# 访问网页
page.get('https://im.qq.com/pcqq/index.shtml')
# 获取按钮元素
ele = page('全新体验版下载')
# 等待按钮元素生成
ele.wait.has_rect()
# 设置保存路径
page.set.download_path('.')
# 点击按钮触发下载
ele.click()
# 等待触发下载,同时取消该任务
mission = page.wait.download_begin(cancel_it=True)
# 转用download()方法下载
page.download(mission.url, goal_path='tmp', rename='QQ.exe')

自动登录gitee

此示例演示使用控制浏览器的方式自动登录 gitee 网站

目标网址:https://gitee.com/login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from DrissionPage import ChromiumPage

# 用 d 模式创建页面对象(默认模式)
page = ChromiumPage()
# 跳转到登录页面
page.get('https://gitee.com/login')

# 定位到账号文本框并输入账号
page.ele('#user_login').input('您的账号')
# 定位到密码文本框并输入密码
page.ele('#user_password').input('您的密码')

# 点击登录按钮
page.ele('@value=登 录').click()

采集猫眼电影榜

这个示例演示用浏览器采集数据

目标网址:https://www.maoyan.com/board/4

采集目标:排名、电影名称、演员、上映时间、分数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from DrissionPage import ChromiumPage
from DataRecorder import Recorder

# 创建页面对象
page = ChromiumPage()
# 创建记录器对象
recorder = Recorder('data.csv')
# 访问网页
page.get('https://www.maoyan.com/board/4')

while True:
# 遍历页面上所有 dd 元素
for mov in page.eles('t:dd'):
# 获取需要的信息
num = mov('t:i').text
score = mov('.score').text
title = mov('@data-act=boarditem-click').attr('title')
star = mov('.star').text
time = mov('.releasetime').text
# 写入到记录器
recorder.add_data((num, title, star, time, score))

# 获取下一页按钮,有就点击
btn = page('下一页', timeout=2)
if btn:
btn.click()
page.wait.load_start()
# 没有则退出程序
else:
break

recorder.record()

下载星巴克产品图片

这个示例用于演示download()方法的功能

目标网址:https://www.starbucks.com.cn/menu/

采集目标:下载所有产品图片,并以产品名称命名

编码思路:按照页面规律,我们可以获取所有class属性为preview circle的元素,然后遍历它们,逐个获取图片路径,以及在后面一个元素中获取产品名称。再将其下载。并且这个网址页面结构非常简单,没有使用 js 生成的页面,可以直接使用 s 模式访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from DrissionPage import SessionPage
from re import search

# 以s模式创建页面对象
page = SessionPage()
# 访问目标网页
page.get('https://www.starbucks.com.cn/menu/')

# 获取所有class属性为preview circle的元素
divs = page.eles('.preview circle')
# 遍历这些元素
for div in divs:
# 用相对定位获取当前div元素后一个兄弟元素,并获取其文本
name = div.next().text

# 在div元素的style属性中提取图片网址并进行拼接
img_url = div.attr('style')
img_url = search(r'"(.*)"', img_url).group(1)
img_url = f'https://www.starbucks.com.cn{img_url}'

# 执行下载
page.download(img_url, r'.\imgs', rename=name)