一、练习网址
1 Drissionpage 4.0旧文档 https://drissionpage.cn/DP40Docs/
2 humanMouse.py
3 绿色版谷歌浏览器 https://download-chromium.appspot.com/
4 格式化工具 https://spidertools.cn/
5 GT-Standard-Mono-VF-Beta-V1
6 Cookie Manager浏览器插件 https://www.wikipedia.org
7 汇文明朝字体 https://github.com/wxhzhwxhzh/huiwenmincho-improved
8 银河录像局 https://nf.video/
9 CDP谷歌文档 https://chromedevtools.github.io/devtools-protocol/1-3/Network/
10 虫术 https://spiderbox.cn/
11 AichatOS https://x.cat.caichatosb.com/
12 Vscode https://code.visualstudio.com/
13 骚神网 https://wxhzhwxhzh.github.io/saossion_code_helper_online/
14 现状JS教程 https://zh.javascript.info/
15 爬虫练习场 https://scrape.center/
二、模块测试
2.1 安装模块
pip install drissionpage -i https://mirrors.aliyun.com/pypi/simple/
#日志模块
pip install loguru -i https://mirrors.aliyun.com/pypi/simple/
准备谷歌浏览器
2.2测试模块
#https://drissionpage.cn/browser_control/browser_options
from DrissionPage import Chromium
browser = Chromium()
tab = browser.new_tab('https://www.baidu.com')
input('Press Enter to continue...')
运行正常会跳转到谷歌浏览器页面
2.3 浏览器启动设置
from DrissionPage import Chromium
from DrissionPage import ChromiumOptions
#创建浏览器启动对象
options = ChromiumOptions()
#配置浏览器启动选项
#设置浏览器路径
options.set_browser_path(r'C:\Program Files\Google\Chrome\Application\chrome.exe')
options.ignore_certificate_errors() #忽略证书错误
options.no_imgs() #禁用图片
options.headless(False) #无头模式
options.set_local_port(9696) #设置浏览器debug端口
#连接浏览器并获取浏览器对象
browser = Chromium(options)
#获取标签页对象并打开网页
tab = browser.new_tab('https://www.baidu.com')
三、基本操作
3.1 标签页设置
from DrissionPage import Chromium
browser = Chromium()
#获取标签页对象并打开网站
tab1 = browser.new_tab('https://www.baidu.com')
tab2 = browser.new_tab('https://www.bing.com')
tab3 = browser.latest_tab #最新激活的网页
print(tab1.title, id(tab1))
print(tab2.title, id(tab2))
print(tab3.title, id(tab3))
browser.latest_tab.close() #关闭最新标签页
#标签页没有焦点概念,多个可以并行操作,同时打开多个标签页
from concurrent.futures import ThreadPoolExecutor
def open_url(browser,url):
browser.new_tab(url)
chinese_websites = [
'https://www.taobao.com',
'https://www.tmall.com',
'https://www.jd.com',
]
#使用线程池
with ThreadPoolExecutor(max_workers=3) as executor:
for url in chinese_websites:
executor.submit(open_url,browser,url)
一次性打开四个网页
这段代码展示了如何使用 DrissionPage 库的 Chromium 浏览器自动化操作多个标签页,并结合线程池实现并发打开多个网站。主要功能包括:
代码功能概述
这段代码展示了如何使用 DrissionPage 库的 Chromium 浏览器自动化操作多个标签页,并结合线程池实现并发打开多个网站。
- 初始化 Chromium 浏览器实例
- 依次打开百度、必应等网站
- 操作和管理标签页(获取最新标签页、关闭标签页)
- 使用线程池并发打开多个中国电商网站
代码详细解读
1. 导入模块与初始化浏览器
from DrissionPage import Chromium
browser = Chromium()
Chromium
类用于控制 Chromium 内核浏览器(如 Chrome、Edge)创建浏览器实例后,会自动启动一个浏览器窗口
2. 打开多个标签页并管理
# 获取标签页对象并打开网站
tab1 = browser.new_tab('https://www.baidu.com')
tab2 = browser.new_tab('https://www.bing.com')
tab3 = browser.latest_tab # 最新激活的网页
# 打印标签页标题和ID(内存地址)
print(tab1.title, id(tab1))
print(tab2.title, id(tab2))
print(tab3.title, id(tab3))
# 关闭最新标签页
browser.latest_tab.close()
new_tab(url)
方法用于打开新标签页并访问指定 URL
latest_tab
属性返回当前最新激活的标签页每个标签页对象有唯一的 ID(通过
id()
函数获取)
close()
方法关闭当前标签页
3. 并发打开多个网站
from concurrent.futures import ThreadPoolExecutor
def open_url(browser, url):
browser.new_tab(url)
chinese_websites = [
'https://www.taobao.com',
'https://www.tmall.com',
'https://www.jd.com',
]
# 使用线程池
with ThreadPoolExecutor(max_workers=3) as executor:
for url in chinese_websites:
executor.submit(open_url, browser, url)
线程池技术:
ThreadPoolExecutor
提供了线程池功能,可并发执行多个任务并发打开网站:将
browser.new_tab(url)
方法提交到线程池执行参数说明:
max_workers=3
:最多同时运行 3 个线程executor.submit(func, *args)
:异步提交任务到线程池。*args即函数需要传入的参数
关键技术点
标签页管理:
- 通过
new_tab()
创建新标签页- 使用
latest_tab
获取最新标签页- 标签页对象可直接操作(如
tab.title
、tab.close()
)并发控制:
- 线程池适合 I/O 密集型任务(如网页加载)
- 控制并发数量可避免资源耗尽
- DrissionPage 的标签页操作是线程安全的
执行流程:
- 主线程负责初始化和协调
- 工作线程负责打开标签页
- 所有操作共享同一个浏览器实例
3.2 元素定位之普通定位
from DrissionPage import Chromium
browser = Chromium()
tab = browser.new_tab('https://www.baidu.com')
# wenku_button = tab.ele('文库')
# wenku_button.click()
input_box = tab.ele('@id=kw')
input_box.input('123')
search_button = tab.ele('tag:input@@type=submit@@id=su@@value=百度一下') #最精确
search_button.click()
代码作用:定位到输入框位置,输入123,再点击搜索按钮
代码解释:
1. 文本定位 ('文库'
)
# 注释掉的代码:通过文本内容定位元素
# wenku_button = tab.ele('文库')
# wenku_button.click()
语法:
ele('文本内容')
或ele('t:文本内容')
作用:查找包含指定文本的元素(默认优先匹配按钮、链接)
示例:
'文库'
会匹配文本为 "文库" 的按钮或链接注意:此代码被注释,可能因页面结构变化导致定位失败
2. 属性定位 ('@id=kw'
)
input_box = tab.ele('@id=kw') # 定位百度搜索框
input_box.input('123')
语法:
ele('@属性名=属性值')
作用:通过元素的属性值定位
示例:
@id=kw
:匹配id="kw"
的元素(百度搜索框)@name=username
:匹配name="username"
的元素
3. 复合定位 ('tag:input@@type=submit@@id=su@@value=百度一下'
)
search_button = tab.ele('tag:input@@type=submit@@id=su@@value=百度一下')
search_button.click()
语法:多条件组合,用
@@
分隔支持的条件类型:
tag:标签名
:按标签类型过滤(如tag:input
)
@属性名=值
:按属性值过滤(如@id=su
)
text:文本内容
:按文本内容过滤(如text:百度一下
)
示例拆解:
tag:input # 标签为 input
@@type=submit # 且 type 属性为 submit
@@id=su # 且 id 属性为 su
@@value=百度一下 # 且 value 属性为 "百度一下"
定位逻辑:
- 先筛选所有
<input>
标签- 在结果中筛选
type="submit"
的元素- 继续筛选
id="su"
的元素- 最终筛选
value="百度一下"
的元素
DrissionPage 定位方法总结
定位方式 | 语法示例 | 说明 |
---|---|---|
文本定位 | '文本内容' 或 't:文本内容' | 匹配包含指定文本的元素 |
属性定位 | '@属性名=属性值' | 通过属性值定位 |
标签定位 | 'tag:标签名' | 通过标签类型定位 |
CSS 选择器 | 'css:选择器内容' | 标准 CSS 选择器语法 |
XPath | 'xpath:表达式' | 标准 XPath 语法 |
复合定位 | '条件1@@条件2@@条件3' | 多条件组合定位 |
3.3 元素定位之iframe定位
from DrissionPage import Chromium
#只要是同域名的,无论跨多少层<iframe>都能用页面对象直接获取。
tab = Chromium().latest_tab
tab.get('https://DrissionPage.cn/demos/iframe_diff_domain.html')
# iframe = tab.get_frame('t:iframe') #最规范
# ele = iframe.ele('网易首页')
# print(ele)
iframe = tab.ele('t:iframe') #最规范
ele = iframe('网易首页')
print(ele)
代码功能概述
这段代码的主要目的是:
- 打开包含跨域 iframe 的演示页面
- 定位 iframe 元素
- 在 iframe 内部查找文本为 "网易首页" 的元素
代码详细解读
1. 导入模块并初始化浏览器
from DrissionPage import Chromium
tab = Chromium().latest_tab
创建 Chromium 浏览器实例并获取最新标签页
等价于以下写法:
browser = Chromium() tab = browser.latest_tab
2. 访问目标页面
tab.get('https://DrissionPage.cn/demos/iframe_diff_domain.html')
打开演示页面,该页面包含至少一个跨域 iframe
跨域 iframe 指源域名与主页面不同的 iframe(如主页面是
.cn
,iframe 是.com
)
3. 定位 iframe 元素(原注释代码)
# iframe = tab.get_frame('t:iframe') # 最规范
# ele = iframe.ele('网易首页')
get_frame()
方法:专门用于定位 iframe
- 参数
't:iframe'
表示通过文本定位 iframe 元素- 定位成功后返回一个 Frame 对象
iframe.ele('网易首页')
:在 iframe 内部查找文本为 "网易首页" 的元素
4. 实际使用的定位方法
iframe = tab.ele('t:iframe') # 定位 iframe 元素
ele = iframe('网易首页') # 在 iframe 内部查找元素
print(ele)
tab.ele('t:iframe')
:
- 使用
ele()
方法直接定位 iframe 元素't:iframe'
表示通过文本内容定位(这里文本可能是 iframe 的 title 属性或内部文本)
iframe('网易首页')
:
- 在已定位的 iframe 元素上下文中查找文本为 "网易首页" 的元素
- 等价于
iframe.ele('网易首页')
关键技术点
iframe 定位方式:
get_frame()
方法:返回 Frame 对象,专门用于操作 iframeele()
方法:返回 Element 对象,可通过该对象访问 iframe 内部元素跨域处理:
- DrissionPage 声称 "同域名的 iframe 可直接获取"
- 但示例中的页面是跨域的(网易域名与演示页面不同)
- 此处能成功定位可能是因为:
- 演示页面使用了特殊技术允许跨域访问
- DrissionPage 内部处理了跨域限制
元素定位语法:
't:文本内容'
是 DrissionPage 的简化定位语法- 等价于
'text:文本内容'
iframe 基础概念
iframe 是 HTML 中用于在当前页面嵌入其他网页的标签,例如:
<iframe src="https://example.com"></iframe>
该标签会在当前页面中创建一个独立的窗口,显示 example.com
的内容。
跨域的定义
当 iframe 的 src
域名、端口或协议与当前页面不同时,即为跨域 iframe。例如:
当前页面:
https://www.baidu.com
iframe 源:
https://news.baidu.com
(同域名,不算跨域)iframe 源:
https://www.google.com
(域名不同,跨域)iframe 源:
http://www.baidu.com
(协议不同,跨域)iframe 源:
https://www.baidu.com:8080
(端口不同,跨域)
3.4 数据监听和抓包
from DrissionPage import Chromium,ChromiumOptions
from pprint import pprint
options = ChromiumOptions()
#连接浏览器并获取浏览器对象
browser = Chromium(options)
tab = browser.latest_tab
tab.listen.start('spa1.scrape.center/api/movie') #开始监听,指定获取包含该文本的数据包,注意是先开启监听再打开对应界面
tab.get('https://spa1.scrape.center/') #访问网址,这行产生的数据包不抓取
for packet in tab.listen.steps():
pprint(packet.response.body) #打印数据包url
# tab = Chromium().latest_tab
# tab.get('https://gitee.com/explore/all') # 访问网址,这行产生的数据包不监听
#
# tab.listen.start('gitee.com/explore') # 开始监听,指定获取包含该文本的数据包
# for _ in range(5):
# tab('@rel=next').click() # 点击下一页
# res = tab.listen.wait() # 等待并获取一个数据包
# print(res.url) # 打印数据包url
获取电影信息和网站
代码详细解读
1. 导入模块并配置浏览器选项
from DrissionPage import Chromium, ChromiumOptions
from pprint import pprint
options = ChromiumOptions()
browser = Chromium(options)
ChromiumOptions:用于配置浏览器启动参数(如无头模式、代理等)
Chromium:创建浏览器实例,支持 Chrome 或 Edge
2. 开始监听网络请求
tab = browser.latest_tab
tab.listen.start('spa1.scrape.center/api/movie')
- tab.listen.start():开始监听网络请求
- 参数
'spa1.scrape.center/api/movie'
:指定要捕获的请求 URL 包含的文本
- 只要请求 URL 中包含该字符串,就会被捕获
- 例如:
https://spa1.scrape.center/api/movie/1
会被捕获
3. 访问目标网站
tab.get('https://spa1.scrape.center/')
打开目标网站,但此行代码执行前已启动监听
注意:此行代码产生的数据包不会被捕获(因为监听在访问前已启动)
4. 处理捕获的数据包
for packet in tab.listen.steps():
pprint(packet.response.body)
tab.listen.steps():迭代获取所有捕获的数据包
packet.response.body:获取响应的内容(通常是 JSON 数据)
pprint():美化打印 JSON 数据,使其更易读
关键技术点
网络请求监听机制:
- DrissionPage 通过注入浏览器扩展或拦截器实现请求监听
- 可监听的内容包括:请求 URL、请求头、响应内容等
数据包结构:
packet.request
:包含请求信息(URL、方法、头信息等)packet.response
:包含响应信息(状态码、内容、头信息等)packet.response.body
:响应的主体内容(如 JSON 数据)监听时机:
- 必须在产生请求前调用
listen.start()
- 示例中先监听再访问网站,确保所有请求被捕获
潜在问题与改进建议
数据包丢失风险:
- 如果在
listen.start()
前已经有请求发生,这些请求将不会被捕获- 改进:在创建浏览器实例后立即启动监听
过滤条件优化:
- 当前过滤条件
'spa1.scrape.center/api/movie'
可能匹配过多请求- 改进:使用更精确的匹配模式(如正则表达式)
异步处理:
- 对于大量数据包,可使用异步方式处理以提高性能
- 改进:结合
asyncio
和AsyncChromium
数据解析:
- 直接打印
body
可能输出二进制或乱码- 改进:添加 JSON 解析逻辑:
import json try: data = json.loads(packet.response.body) pprint(data) except json.JSONDecodeError: print("非 JSON 数据:", packet.response.body)
3.5 多线程操作标签页
# from DrissionPage import SessionPage
# from pprint import pprint
# page = SessionPage()
# page.get('https://www.yznnw.com/txt/1655.html')
# eles_list = page('xpath:/html/body/div[2]/div[1]/div[4]/ul').eles('t:a')
# print(eles_list.get.texts()) # 获取所有元素的文本
# chapter_dict = {}
# for i in eles_list:
# print(i.tag,i.text,i.attr('href'))
# chapter_dict[i.text] = i.attr('href')
# pprint(chapter_dict)
# list_chapter = list(chapter_dict.keys())
from DrissionPage import Chromium,ChromiumOptions
from pprint import pprint
import threading
import concurrent.futures
#设置浏览器选项,使用系统用户路径
# co = ChromiumOptions().use_system_user_path()
#连接浏览器并获取对象
browser = Chromium()
#获取标签页对象并打开目标网址
tab_instance = browser.new_tab('https://www.yznnw.com/txt/1655.html')
#Xpath定位章节列表的无序列表
chapter_dict = {}
#从网页提取标题和链接
#tab_instance('xpath:/html/body/div[2]/div[1]/div[4]/ul')
#tab_instance(#id or '@class=section-list')
for chapter in tab_instance('xpath:/html/body/div[3]/div[2]/div/ul').eles('t:a'):
chapter_dict[chapter.text] = chapter.attr('href')
#打印提取到的章节字典
pprint(chapter_dict)
list_chapter = list(chapter_dict.keys())
def fetch_and_save(chapter_title,chapter_url):
new_tab = browser.new_tab(chapter_url) #打开章节链接
chapter_content = new_tab('@class=wp ov').text
print(chapter_content)
print(f'正在下载{chapter_title}')
tab_instance.wait(1)
#保存
with open(chapter_title+'.txt','w',encoding='utf-8') as f:
f.write(chapter_content)
threading.Thread(target=new_tab.close).start() #关闭新标签页
#new_tab.close() #单线程
#单线程
# for a in list_chapter:
# fetch_and_save(a,chapter_dict[a])
#多线程
def main():
#创建一个线程池,最多允许4个线程同时运行
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
#提交任务到线程池
futures = [executor.submit(fetch_and_save,chapter_title,chapter_url)
for chapter_title,chapter_url in chapter_dict.items()]
if __name__ == '__main__':
main()
多线程下载小说
代码功能概述
这个爬虫程序主要完成以下工作:
- 打开小说目录页,提取所有章节标题和链接
- 使用线程池并发下载多个章节内容
- 将每个章节内容保存为单独的文本文件
- 自动管理标签页,下载完成后关闭
代码详细解读
1. 导入模块与初始化浏览器
from DrissionPage import Chromium, ChromiumOptions
from pprint import pprint
import threading
import concurrent.futures
browser = Chromium() # 初始化 Chromium 浏览器
tab_instance = browser.new_tab('https://www.yznnw.com/txt/1655.html') # 打开小说目录页
2. 提取章节信息
chapter_dict = {}
for chapter in tab_instance('xpath:/html/body/div[3]/div[2]/div/ul').eles('t:a'):
chapter_dict[chapter.text] = chapter.attr('href') # 提取章节标题和链接
定位逻辑:
- 使用 XPath 定位章节列表的
<ul>
元素- 通过
eles('t:a')
提取所有章节链接(a
标签)- 将章节标题和链接存入字典
chapter_dict
3. 单线程下载实现(注释部分)
# 单线程下载
# for a in list_chapter:
# fetch_and_save(a, chapter_dict[a])
4. 多线程下载实现
def fetch_and_save(chapter_title, chapter_url):
new_tab = browser.new_tab(chapter_url) # 打开新标签页
chapter_content = new_tab('@class=wp ov').text # 提取章节内容
print(f'正在下载{chapter_title}')
# 保存为文本文件
with open(chapter_title + '.txt', 'w', encoding='utf-8') as f:
f.write(chapter_content)
# 在新线程中关闭标签页
threading.Thread(target=new_tab.close).start()
def main():
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(fetch_and_save, title, url)
for title, url in chapter_dict.items()]
核心逻辑:
- 线程池:使用
ThreadPoolExecutor
创建 4 个工作线程- 并发下载:每个线程负责打开一个章节页面,提取内容并保存
- 标签页管理:下载完成后在后台线程中关闭标签页
关键技术点
元素定位方式:
'@class=wp ov'
:通过类名定位元素't:a'
:通过文本定位链接元素- 结合 XPath 和 DrissionPage 简化语法
多线程设计:
- 线程池实现并发下载,提高效率
- 每个线程操作独立标签页,避免干扰
- 使用守护线程关闭标签页,避免阻塞主线程
文件保存:
- 使用章节标题作为文件名
- 指定
utf-8
编码避免中文乱码
潜在问题与改进建议
-
路径处理:
- 章节标题可能包含非法文件名的字符(如
/
、*
等) - 改进:添加文件名过滤:
import re safe_title = re.sub(r'[\\/:*?"<>|]', '_', chapter_title)
- 章节标题可能包含非法文件名的字符(如
-
异常处理:
- 当前代码没有处理网络异常或页面加载失败
- 改进:添加重试机制和异常捕获:
from tenacity import retry, stop_after_attempt, wait_fixed @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) def fetch_and_save(...): # 函数内容
-
性能优化:
- 线程数量可能不是最优,建议根据网络情况调整
- 改进:动态调整线程数:
max_workers = min(10, len(chapter_dict)) # 根据章节数量调整
-
进度监控:
- 无法直观了解整体下载进度
- 改进:使用
tqdm
显示进度条:from tqdm import tqdm with ThreadPoolExecutor(max_workers=4) as executor: list(tqdm(executor.map(lambda args: fetch_and_save(*args), chapter_dict.items()), total=len(chapter_dict)))
3.6 异步操作浏览器标签页
import asyncio #异步库
from DrissionPage import Chromium
from DataRecorder import Recorder
from loguru import logger #日志库
#定义异步采集方法
async def collect(tab,recoder,title,page=1):
#遍历所有标题元素
for i in tab.eles('.title project-namespace-path'):
# 获取某页所有库名称,记录到记录器
recoder.add_data((title,i.text,page))
logger.info((title,i.text,page))
#获取下一页按钮
btn = tab('@rel=next',timeout=2)
#如果有下一页,点击翻页并递归调用自身
if btn:
btn.click(by_js=True)
await asyncio.sleep(1)
#增加页数并递归调用
await collect(tab,recoder,title,page+1)
#主函数
async def main():
#新建浏览器对象
browser = Chromium()
#第一页标签页访问网址
tab1 = browser.new_tab('https://gitee.com/explore/ai')
#新建一个标签页并访问一个网址,返回其对象
tab2 = browser.new_tab('https://gitee.com/explore/machine-learning')
#新建记录器对象
recoder = Recorder('data.csv')
#创建异步任务
task1 = asyncio.create_task(collect(tab1,recoder,'ai')) #collect定义的函数
task2 = asyncio.create_task(collect(tab2,recoder,'机器学习'))
#等待任务完成
await task1
await task2
print('所有任务完成')
recoder.record()
if __name__ == '__main__':
asyncio.run(main())
这段代码使用 异步编程 和 DrissionPage 框架实现了一个从 Gitee 网站采集项目信息的爬虫程序。它通过创建多个异步任务同时处理不同分类的项目列表,并将结果保存到 CSV 文件中。
代码功能概述
该程序主要完成以下工作:
- 异步采集:同时抓取 Gitee 上 "AI" 和 "机器学习" 分类的项目信息
- 自动翻页:递归处理每个分类下的所有页面
- 数据记录:使用
DataRecorder
将项目名称保存到 CSV 文件- 日志管理:使用
loguru
记录采集过程
代码详细解读
1. 导入模块与工具
import asyncio
from DrissionPage import Chromium
from DataRecorder import Recorder
from loguru import logger
asyncio:Python 内置的异步编程库
Chromium:DrissionPage 的浏览器驱动类
Recorder:自定义数据记录器,用于保存 CSV 文件
logger:日志工具,提供比
2. 定义异步采集函数
async def collect(tab, recoder, title, page=1):
# 遍历当前页所有项目标题元素
for i in tab.eles('.title project-namespace-path'):
recoder.add_data((title, i.text, page)) # 记录数据
logger.info((title, i.text, page)) # 记录日志
# 查找下一页按钮
btn = tab('@rel=next', timeout=2)
if btn:
btn.click(by_js=True) # 使用 JavaScript 点击
await asyncio.sleep(1) # 等待页面加载
await collect(tab, recoder, title, page+1) # 递归处理下一页
元素定位:使用 CSS 选择器
.title project-namespace-path
定位项目标题递归翻页:通过检查
rel="next"
的按钮实现自动翻页异步等待:使用
asyncio.sleep(1)
等待页面加载,避免阻塞其他任务
3. 主函数与异步任务
async def main():
browser = Chromium() # 创建浏览器实例
# 打开两个分类页面
tab1 = browser.new_tab('https://gitee.com/explore/ai')
tab2 = browser.new_tab('https://gitee.com/explore/machine-learning')
recoder = Recorder('data.csv') # 创建数据记录器
# 创建并启动异步任务
task1 = asyncio.create_task(collect(tab1, recoder, 'ai'))
task2 = asyncio.create_task(collect(tab2, recoder, '机器学习'))
# 等待所有任务完成
await task1
await task2
print('所有任务完成')
recoder.record() # 将数据写入 CSV 文件
多标签页操作:在同一个浏览器实例中打开多个标签页
任务并行:使用
asyncio.create_task()
同时执行多个采集任务数据持久化:通过
Recorder
将采集的数据保存到文件
关键技术点
异步爬虫设计:
- 使用
async def
定义异步函数- 通过
asyncio.create_task()
创建并行任务- 使用
await
等待 IO 操作,释放 CPU 资源浏览器自动化:
- DrissionPage 的
tab.eles()
方法定位多个元素btn.click(by_js=True)
:使用 JavaScript 执行点击,避免元素遮挡问题数据记录与日志:
Recorder
类封装数据收集和 CSV 写入逻辑logger.info()
提供结构化日志,支持时间戳、级别等信息
潜在问题与改进建议
-
CSS 选择器问题:
.title project-namespace-path
可能无法正确定位元素(可能应为.title.project-namespace-path
)- 改进:使用浏览器开发者工具验证选择器
-
递归深度限制:
- 递归翻页可能导致
RecursionError
(默认递归深度约 1000) - 改进:改用迭代方式实现翻页:
while True: # 处理当前页 btn = tab('@rel=next', timeout=2) if not btn: break btn.click(by_js=True) await asyncio.sleep(1)
- 递归翻页可能导致
-
异常处理:
- 缺少网络异常、元素定位失败等处理
- 改进:添加
try-except
块:async def collect(...): try: # 原有代码 except Exception as e: logger.error(f"采集失败: {e}") return
-
性能优化:
- 单浏览器实例多标签页可能不稳定
- 改进:为每个任务创建独立浏览器实例:
async def worker(url, category): browser = Chromium() tab = browser.new_tab(url) # 执行采集 browser.quit() # 关闭浏览器 # 在 main 函数中创建多个 worker 任务