Python-playwright:一款强大的UI自动化工具、新兴爬虫利器

Python-playwright:一款强大的UI自动化工具、新兴爬虫利器

点击名片关注阿尘blog,一起学习,一起成长

本文主要分享由微软开发的实现Web UI自动化测试工具Playwright库,相比于之前学习过selenium库,playwright对于编写自动化代码绝对是更轻松了,因为它支持脚本录制,如果只求简单点可以不用写一行代码就能够实现自动化,而且playwright有许多强大的api,很多功能比起selenium都轻松简单,好了话不多说,开启正文~

playwright简介和初步使用

1.1 playwright简介

playwright优点(这里就直接摘抄一下官网的哈,不是重点)

任意浏览器 全平台 同一套 API

跨浏览器。 Playwright 支持所有现代渲染引擎,包括:Chromium、WebKit 和 Firefox。

跨平台。 适用于 Windows、Linux、macOS、本地运行、 CI、headless 和 headed。

跨语言。 在 TypeScript, JavaScript, Python, .NET, Java 中使用 Playwright API

测试移动端 Web 。 对 Android 和 Mobile Safari 的 Google Chrome 原生移动端模拟。与你的移动端和云端应用采用相同的渲染引擎。

强适应性 测试不再容易失效

自动等待(auto-waits)。 Playwright 能够自动等待元素达到可操作的状态,外加一系列丰富的内置事件。不用再人工定义超时(timeouts) —— 这是测试容易失效的主要原因。

Web 优先的断言。 Playwright 断言专门为动态加载的 web 应用设计。能够在满足需要的条件前自动重试。

可追踪。 通过配置重试策略,采用捕捉执行轨迹、视频、截图来解决测试容易失效的问题。

不用再权衡取舍 不再有限制

浏览器在不同进程中运行属于不同来源的 Web 内容。Playwright 与现代浏览器架构保持一致,并在进程外运行测试。这使 Playwright 摆脱了典型的进程内测试运行程序限制。

一切并行。 跨越多个 tabs, 多个 origins 和多个 users 的测试场景。在一个测试中能够为不同的用户创建具有不同上下文的场景,并能在你的服务器上运行。

可信事件。 元素悬停(hover)、动态控件的交互、生产可信事件。Playwright 使用与真实用户一致的输入方式(pipeline)。

测试 frames,穿透 ShadowDOM**。** Playwright 的选择器能够穿透 shadowDOM 和允许无缝输入 frame。

完全隔离 快速执行

浏览器上下文。 Playwright 为每个测试创建一个浏览器上下文。浏览器上下文相当于一个全新的浏览器配置文件。这提供了零开销的完整测试隔离。创建一个新的浏览器上下文只需要几毫秒。

一次登录。 保存上下文的身份验证状态并在所有测试中重用它。这绕过了每个测试中的重复登录操作,但提供了独立测试的完全隔离。

强大的工具

Codegen 。 通过记录您的操作来生成测试。将它们保存为各种语言。

Playwright inspector 。 检查页面,生成选择器,逐步执行测试,查看点击点,浏览执行日志。

Trace Viewer 。 捕获所有的信息来调查失败了的测试,Playwright 追踪包含测试运行截屏视频、实时 DOM 快照、动作浏览器、测试源等信息。

1.2 playwright安装与使用

playwright的安装(python3.7+)

pip install playwright 

Playwright 安装驱动,相比起selenium就简单多了

python -m playwright install 

Playwright 录制脚本

python -m playwright codegen 

通过–help 查看可以用哪些参数

python -m playwright codegen --help Usage: playwright codegen [options] [url] open page and generate code for user actions Options: -o, --output <file name> saves the generated script to a file --target <language> language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java (default: "python") --save-trace <filename> record a trace for the session and save it to a file --test-id-attribute <attributeName> use the specified attribute to generate data test ID selectors -b, --browser <browserType> browser to use, one of cr, chromium, ff, firefox, wk, webkit (default: "chromium") --block-service-workers block service workers --channel <channel> Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc --color-scheme <scheme> emulate preferred color scheme, "light" or "dark" --device <deviceName> emulate device, for example "iPhone 11" --geolocation <coordinates> specify geolocation coordinates, for example "37.819722,-122.478611" --ignore-https-errors ignore https errors --load-storage <filename> load context storage state from the file, previously saved with --save-storage --lang <language> specify language / locale, for example "en-GB" --proxy-server <proxy> specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080" --proxy-bypass <bypass> comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com" --save-har <filename> save HAR file with all network activity at the end --save-har-glob <glob pattern> filter entries in the HAR by matching url against this glob pattern --save-storage <filename> save context storage state at the end, for later use with --load-storage --timezone <time zone> time zone to emulate, for example "Europe/Rome" --timeout <timeout> timeout for Playwright actions in milliseconds, no timeout by default --user-agent <ua string> specify user agent string --viewport-size <size> specify browser viewport size in pixels, for example "1280, 720" -h, --help display help for command Examples: $ codegen $ codegen --target=python $ codegen -b webkit https://example.com 

有很多,这里只挑几个简单的常用的来说

-o:指定脚本录制输出位置;

–target:指定脚本语言,告诉他用什么语言写,后面一大堆,还有个python-pytest!,默认用python

-b:指定浏览器驱动,默认用的谷歌

那我们来小试牛刀:

python -m playwright codegen -o "D:PythonProjectplaywright_projectplaywright_code.py" 

运行之后我们用它自动打开的谷歌浏览器,打开百度,输入漂亮小姐姐并查找,结束录制,看看录制脚本

from playwright.sync_api import Playwright, sync_playwright, expect def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto("https://www.baidu.com/") page.locator("#kw").click() page.locator("#kw").fill("漂亮小姐姐") page.get_by_role("button", name="百度一下").click() # --------------------- context.close() browser.close() with sync_playwright() as playwright: run(playwright) 

查看是否成功运行

ff286d4b4d0cb2c20aa9335d9d1fc9da.png

OK,完全可以,但是我们对它的脚本,有哪些功能怎么使用还完全不了解,下面来系统学习一下

1.3 playwright的脚本编写

playwright代码的编写,有两种模式:同步模式和异步模式

同步模式就如同selenium,上面录制脚本的示例就采用的同步模式,同步模式的语法如下

# 同步模式 # 导入Playwright类和sync_palywright 同步类 from playwright.sync_api import Playwright,sync_playwright def main(playwright): browser = playwright.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto("https://www.baidu.com/") print(page.title()) context.close() browser.close() with sync_playwright() as p: main(p) 

同步模式主要用的sync_playwright的api

异步模式语法:

# 异步模式 import asyncio from playwright.async_api import Playwright,async_playwright async def main(): async with async_playwright() as p: browser = await p.chromium.launch(headless=False) context = await browser.new_context() page = await context.new_page() await page.goto("https://www.baidu.com/") print(await page.title()) await context.close() await browser.close() asyncio.run(main()) 

它俩很相似,异步模式主要用了async_playwright的api,主要采用async/await 关键字

接下来详细解释下里面的方法、类:

1、Playwright类

主要用于定义设备的信息,为page服务或者context服务

2、生成浏览器驱动:
browser = playwright.chromium.launch(headless=False) 

可支持的浏览器有_‘chromium’__,_ ‘webkit’orfirefox

launch函数的使用

def launch( self, *, executable_path: typing.Optional[typing.Union[str, pathlib.Path]] = None, # 传入一个浏览器可执行的文件必须绑定支持的浏览器 channel: typing.Optional[str] = None, #传入浏览器的版本,有"chrome", "chrome-beta", "chrome-dev", "chrome-canary", # "msedge", "msedge-beta", "msedge-dev", "msedge-canary",msedge是Microsoft edge,还有其他版本可以查看官网 args: typing.Optional[typing.List[str]] = None, #传递给浏览器的其他参数,以列表形式传入 ignore_default_args: typing.Optional[ typing.Union[bool, typing.List[str]] ] = None, # 如果为'true',Playwright不会传递自己的配置参数,而只使用来自'args'的配置参数。如果数组是 # 给定,然后过滤掉给定的默认参数。危险选项;小心使用。默认为“False”。 handle_sigint: typing.Optional[bool] = None, # 通过Ctrl+c关闭浏览器的进程,默认开启 handle_sigterm: typing.Optional[bool] = None, # 收到SIGTERM信号正常关闭浏览器进程,默认开启 handle_sighup: typing.Optional[bool] = None, # 收到SIGHUP信号终止浏览器进程,默认开启 timeout: typing.Optional[float] = None, # 等待浏览器实例启动的最长时间(以毫秒为单位)。默认为“30000”(30秒)。传递“0”以禁用超时。 env: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, # 指定浏览器可见的环境变量。默认为process. env headless: typing.Optional[bool] = None, # 是否在没有窗口下运行浏览器,默认开启,开启后你就看不到程序打开浏览器,一般传入False devtools: typing.Optional[bool] = None, # **仅Chromium**是否为每个选项卡自动打开开发人员工具面板。如果此选项为“真”,则“无头”选项将设置为“false”。 proxy: typing.Optional[ProxySettings] = None, # 网络代理设置 downloads_path: typing.Optional[typing.Union[str, pathlib.Path]] = None, # 如果指定,接受的下载将下载到此目录中。否则,将创建临时目录并当浏览器关闭时被删除。在任何一种情况下,下载都会在它们所在的浏览器上下文关闭时被删除。 slow_mo: typing.Optional[float] = None, # 将Playwright操作减慢指定的毫秒数。这个很有用,以便您可以看到正在发生的事情。 traces_dir: typing.Optional[typing.Union[str, pathlib.Path]] = None, # 如果指定,跟踪将保存到此目录中。 chromium_sandbox: typing.Optional[bool] = None, # 启用Chromium沙盒。默认为False firefox_user_prefs: typing.Optional[ typing.Dict[str, typing.Union[str, float, bool]] ] = None # 一些火狐偏好设置 ) -> "Browser": 
3、生成浏览器上下文(context)对象

浏览器上下文对象是浏览器实例中一个独立隐身会话,资源、cookie缓存这些不与其他上下文共享,对于自动化多用户、多场景测试非常有意义,相当于打开两个浏览器,两个进程,可以每个测试用例单独开一个上下文,需要注意的是,浏览器上下文最好显式的关闭,也就是

context.close() 

上下文的方法,这里就简单说一下两个参数,其余不详细说了,如果有需要可以查看api

viewport : Union[{width: int, height: int}, None] # 为每个页面设置一致的视口。默认为1280x720视口 screen : Union[{width: int, height: int}, None] # 通过“window.screen”模拟网页内可用的一致窗口屏幕大小 ,只能在viewport设置之后使用 

context常用操作

context.pages :获取context所有page对象

context.new_page():生成一个新的page对象

context.close():关闭context

**context.add_cookies()****:**将cookie添加到此浏览器上下文中。此上下文中的所有页面都将安装这些cookie。

只能传入列表

List[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None],expires_: Union[float, None]_

context.clear_cookies():清除context的cookie

**context.grant_permissions()****:**授予浏览器上下文的指定权限,具体见api

**context.clear_permissions()****:**清除授权

4、生成浏览器页面page
page = context.new_page() 

page就相当于浏览器的选项卡,一般在浏览器context上创建,后续打开网页 ,业绩元素定位,页面操作都是基于page

首先来了解一下page的常用参数

async def new_page( self, viewport: ViewportSize = None,#为每个页面设置一致的视口。默认为1280x720视口 screen: ViewportSize = None, # 通过“window.screen”模拟网页内可用的一致窗口屏幕大小 ,只能在viewport设置之后使用 noViewport: bool = None,# 不强制固定视口,允许在标题模式下调整窗口大小 ignoreHTTPSErrors: bool = None, javaScriptEnabled: bool = None, bypassCSP: bool = None, userAgent: str = None, # 设置代理用于上下文 locale: str = None, #指定用户区域,设置将影响Accept-Language'请求标头值以及数字和日期格式规则 timezoneId: str = None, geolocation: Geolocation = None, permissions: List[str] = None, extraHTTPHeaders: Dict[str, str] = None, offline: bool = None, httpCredentials: HttpCredentials = None, deviceScaleFactor: float = None, isMobile: bool = None, #设备相关,不用管 hasTouch: bool = None, colorScheme: ColorScheme = None, #Union["dark", "light", "no-preference", "null", None] #设置颜色主题 forcedColors: ForcedColors = None, #Union["active", "none", "null", None] reducedMotion: ReducedMotion = None, acceptDownloads: bool = None, defaultBrowserType: str = None, proxy: ProxySettings = None, recordHarPath: Union[Path, str] = None, recordHarOmitContent: bool = None, recordVideoDir: Union[Path, str] = None, recordVideoSize: ViewportSize = None, storageState: Union[StorageState, str, Path] = None, baseURL: str = None, strictSelectors: bool = None, serviceWorkers: ServiceWorkersPolicy = None, recordHarUrlFilter: Union[Pattern[str], str] = None, recordHarMode: HarMode = None, recordHarContent: HarContentPolicy = None, ) -> Page: 

page常用操作

page.goto(url,timeout,waitUntil,referer)****:打开一个新的网页

url传入一个字符串,导航到该地址

timeout 最大等待时间,传入毫秒,默认30s,为0则禁用,不等待

waitUntil__:定义触发事件,当定义事件触发将被认为操作成功

可选[“commit”, “domcontentloaded”, “load”, “networkidle”, None]

domcontentloaded:出现【文件加载中】就被认为操作成功

load:出现【加载】事件就被认为操作成功

networkidle:当发生【没有网络(网络空闲)】被认为操作成功,超过500__ms__没有网络就触发了【不建议使用】

commit:当网络响应被接受,文件开始加载,就认为操作成功了

referer:可以传入__str__或者none,引用头部,如果设置就有限用这个作为header

page.locator():playwright的核心功能之一,元素定位器,常用的元素定位方法在后面单独拎出来讲

元素定位的常用操作

page.locator().click():点击元素

page.locator().dblclick():双击元素,double click

page.locator().fill():输入str

click和fill操作只可以直接在页面操作

比如

page.fill('css=[name="username"]',"017575863") page.fill('css=[name="password"]',"06523244") 

page.locator().count():返回匹配元素的个数

page.locator().clear():清空input的内容

page.locator().drag_to():把一个元素拖到另一个元素位置然后释放

page.locator().frame_locator():使用iframe时,您可以创建一个帧定位器,该定位器将进入iframe并允许选择iframe中元素。

示例:

locator = page.frame_locator("#my-iframe").get_by_text("Submit") await locator.click() locator = page.frame_locator("#my-iframe").get_by_text("Submit") locator.click() 

page.locator().all_text_contents():返回对应元素所有文本内容,基于代码不会格式化,返回的是列表

page.locator().all_inner_texts():返回对应元素所有文本内容,基于页面,格式化,返回列表

page.locator().get_attribute():返回匹配元素的属性值

page.locator().wait_for():等待元素加载,没必要,playwright会自动等待直到触发load事件。

page.locator().filter():此方法根据选项缩小现有定位器,例如按文本过滤。它可以多次过滤。

示例:

await row_locator.filter(has_text="text in column 1").filter( has=page.get_by_role("button", name="column 2 button") ).screenshot() 

**page.frame():返回指定的frame,必须传入name 或者urlpage.frame_locator():**定位一个frame,并可以切换进去定位元素

locator = page.frame_locator("#my-iframe").get_by_text("Submit") await locator.click() 

page.content():返回页面内容

page.set_default_navigation_timeout():设置超时默认时间

page.keyboard.press():键盘操作(直接在page页面),可以输入的键有以下

F1-F12__,Digit0__-Digit9__,KeyA__-KeyZ__,Backquote__,Minus__,Equal__,Backslash__,Backspace__,Tab,Delete__,Escape__,ArrowDown__,End__,Enter__,Home__,Insert__,PageDown__,PageUp__,ArrowRight__, _ArrowUp__,_etc.

page.screenshot():截屏

page.press(selector,key):找到元素,然后调用键盘的按下、放开某个键的操作

page.keyboard.press("Shift+O") page.press("body", "Shift+O") 

page.close():关闭页面

page.wait_for_load_state(state):当达到所需的加载状态时返回。state有[“domcontentloaded”, “load”, “networkidle”]

当页面达到所需的加载状态时,默认为“加载”。调用此方法时导航必须是已经发送请求了。如果当前文档已经达到所需状态,则立即解析。

page.route():网络劫持,路由提供了修改页面发出的网络请求的能力。一个强大api

page = await browser.new_page() await page.route(re.compile(r"(\.png$)|(\.jpg$)"), lambda route: route.abort()) await page.goto("https://example.com") await browser.close() 

劫持响应结果

我们要让百度详情结果为我们的定义的页面:custom_response.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hack test</title> </head> <body> <h1>hack test</h1> </body> </html> from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.route('**/*',lambda route: route.fulfill(path='./custom_response.html')) page.goto("https://www.baidu.com") browser.close() 

page.expect_download():下载文件

示例

# 开始下载 with page.expect_download() as download_info: # 点击下载的按钮 page.locator("button#delayed-download").click() download = download_info.value # 等待下载 print(download.path()) # 保存文件 download.save_as("/path/to/save/download/at.txt") 
5、定位方法

playwright定位主要通过slector选择器定位支持文本定位、css定位、xpath定位、组合定位

文本选择器

page.click('text=登录') # 没有加引号(单引号或者双引号),模糊匹配,对大小写不敏感 page.click('text="登录"')# 有引号,精确匹配,对大小写敏感 

has_text()查找子代或后代所有包含对应文本的,相反的也有has_not_text()

text()查找第一个文本等于…的元素

比如:<article><div >Playwright</div></article>__.

page.locator(':has_text("Playwright")').click() # 也可以这样写,指定标签 page.locator('article:has_text("Playwright")').click() # 也可以这样 page.locator(":text('Playwright')").click() # 还可以这样 page.locator('article:has'text=Playwright').click() 

css选择器

page.locator('button').click() # 根据标签 page.locator('#nav-bar .contact-us-item').click() # 通过id +class page.locator('[data-test=login-button]').click() # 属性定位 page.locator("[aria-label='Sign in']").click() #css定位前面可以加个css=,但是不加也可以程序也会自动匹配 

Xpath 定位

page.locator("xpath=//button").click() 

组合定位:text、css、xpath三者可以两组合定位

css+text组合定位

page.locator("article:has-text('Playwright')").click() page.locator("#nav-bar :text('Contact us')").click() 

css+css组合定位

page.locator(".item-description:has(.item-promo-banner)").click() Xpath + css 组合定位 page.fill('//div[@class="SignFlow-account"] >>css=[name="username"]',"0863") xpath+xpath组合定位 page.fill('//div[@class="SignFlowInput"] >> //input[@name="password"]',"ma160065") 

playwright推荐的内置定位

page.get_by_text()通过文本内容定位。

page.get_by_label()通过关联标签的文本定位表单控件。page.get_by_placeholder()按占位符定位输入。

page.get_by_title()通过标题属性定位元素。

page.get_by_role()通过显式和隐式可访问性属性进行定位。page.get_by_alt_text()通过替代文本定位元素,通常是图像。page.get_by_test_id()根据data-testid属性定位元素(可以配置其他属性)。

6、事件监听

Page 对象提供了一个 on 方法,它可以用来监听页面中发生的各个事件,比如 close、console、load、request、response 等等

示例:

from playwright.sync_api import sync_playwright def run(playwright): chromium = playwright.chromium browser = chromium.launch() page = browser.new_page() # 监听请求和响应事件 page.on("request", lambda request: print(">>", request.method, request.url)) page.on("response", lambda response: print("<<", response.status, response.url)) page.goto("https://example.com") browser.close() with sync_playwright() as playwright: run(playwright) 
7、移动端浏览器支持

移动端支持Safari 浏览器、谷歌、不支持火狐,可以传入的设备有iPhone和Pixel 2 (Pixel2 是谷歌的一款安卓手机

示例:模拟打开 iPhone 12 Pro Max 上的 Safari 浏览器,然后手动设置定位,并打开百度地图并截图

故宫的经纬度是 39.913904, 116.39014,我们可以通过 geolocation 参数传递给 Webkit 浏览器并初始化

from playwright.sync_api import sync_playwright with sync_playwright() as p: iphone_12_pro_max = p.devices['iPhone 12 Pro Max'] browser = p.webkit.launch(headless=False) context = browser.new_context( **iphone_12_pro_max, locale='zh-CN', geolocation={'longitude': 116.39014, 'latitude': 39.913904}, permissions=['geolocation'] ) page = context.new_page() page.goto('https://amap.com') page.wait_for_load_state(state='networkidle') page.screenshot(path='location-iphone.png') browser.close() 
8、处理新的窗口、弹窗,iframe

selenium处理iframe比较麻烦,但是playwright就比较简单,有不同方法

第一种,直接定位一个frame,在frame基础上操作

示例

# ********同步********* # Get frame using the frame's name attribute frame = page.frame('frame-login') # Get frame using frame's URL frame = page.frame(url=r'.*domain.*') # Interact with the frame frame.fill('#username-input', 'John') # *********异步*********** # Get frame using the frame's name attribute frame = page.frame('frame-login') # Get frame using frame's URL frame = page.frame(url=r'.*domain.*') # Interact with the frame await frame.fill('#username-input', 'John') 

第二种,直接定位到frame再定位到上面的元素,在元素基础上操作

示例:

username = page.frame_locator('.frame-class').locator('#username-input') username.fill('jonas') 

处理弹窗,一般注册、或者点击一些按钮容易出现弹窗,我们可以利用page.expect_popup()来获取新窗口的iframe

示例:

with page.expect_popup() as popup_info: # iframe中的id如果是动态的,所以我们只匹配关键字 page.frame_locator("iframe[id^=x-URS-iframe]").locator("text=注册新帐号").click() register_page = popup_info.value # 点击邮箱地址输入框 register_page.locator("input[id="username"]").click() # 输入邮箱 register_page.locator("input[id="username"]").fill("TesterRoad") # 点击密码输入框 register_page.locator("input[id="password"]").click() # 输入密码 register_page.locator("input[id="password"]").fill("TesterRoad@126") 

本次分享结束,更多功能和操作大家可以参考官方文档,下期将主要分享脚本录制

参考文章:

https://blog.ZEEKLOG.net/m0_51156601/article/details/126886040

https://blog.ZEEKLOG.net/m0_58026506/article/details/127263263

https://cuiqingcai.com/36045.html

https://www.playwright.pro/docs/api/class-playwright

扫描二维码关注阿尘blog,一起交流学习

Read more

猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 过去几年里,科技公司几乎都在同一件事上加速:让 AI 参与写代码。 从自动补全、自动生成函数,到直接修改系统配置,生成式 AI 已经逐渐走进真实生产环境。但最近发生在亚马逊的一连串事故,却给整个行业泼了一盆冷水——当 AI 开始真正参与生产环境开发时,事情可能远比想象复杂。 最近,多家媒体披露,本周二亚马逊内部紧急召开了一场工程“深度复盘(deep dive)”会议,专门讨论最近频繁出现的系统故障——其中,一个被反复提及的关键词是:AI 辅助代码。 一周 4 次严重事故,亚马逊内部紧急复盘 事情的起点,是最近一段时间亚马逊系统稳定性明显下降。 负责亚马逊网站技术架构的高级副总裁 Dave Treadwell 在一封内部邮件中坦言:“各位,正如大家可能已经知道的,最近网站及相关基础设施的可用性确实不太理想。” 为此,公司决定把原本每周例行举行的技术会议

By Ne0inhk
这回真的“装”到了!来OpenClaw全国纵深行,你只需要带一台电脑……

这回真的“装”到了!来OpenClaw全国纵深行,你只需要带一台电脑……

AI Agent 的风,已经从 GitHub 吹到了线下。 过去几个月,越来越多开发者开始讨论一个问题: 当 AI 不再只是聊天,而是可以执行任务,软件会变成什么样? 在这股浪潮中,一个开源项目迅速进入开发者视野——OpenClaw,在 GitHub 上获得大量关注,相关教程、实践案例不断出现。有人用它自动整理资料,有人用它管理开发流程,还有人尝试让它执行复杂的工作流。 很多开发者第一次意识到: AI 不只是工具,它可能成为“执行者”。 不过,在技术社区之外,大多数人对 Agent 的理解仍停留在概念层面。 * AI Agent 到底是什么? * 如何在自己的电脑上运行? * 普通开发者能否真正用起来? 带着这些问题,一场围绕 OpenClaw 的开发者城市行动正在展开。 ZEEKLOG 发起的OpenClaw 全国纵深行将走进 20 个城市,用最直接的方式回答一个问题——如果

By Ne0inhk
将若依(RuoYi)框架从适配 Spring Boot 2 的版本升级到 Spring Boot 3

将若依(RuoYi)框架从适配 Spring Boot 2 的版本升级到 Spring Boot 3

你想要将若依(RuoYi)框架从适配 Spring Boot 2 的版本升级到 Spring Boot 3,这是一个涉及依赖、配置、API 兼容等多方面的系统性升级工作。 一、升级前准备 1. 确认若依版本:优先选择若依官方已适配 Spring Boot 3 的版本(如 RuoYi v4.7.0+ 有适配分支),若使用自定义改造版本,需逐一处理兼容性问题。 2. 环境要求:Spring Boot 3 要求 JDK 17+(放弃 JDK 8/11 支持),需先升级本地 / 服务器 JDK 到 17 及以上。

By Ne0inhk

Visual Studio Code中实现Go语言自动导包教程

一、在VS Code中安装Go需要的扩展 首先,按住Ctrl + Shift + P调出命令面板,输入 Go:Install/Update Tools 全选安装,等待安装完毕 显示这行输出,就代表安装成功了 tips:如果你出现了下面的报错 2026-01-08 15:25:29.074 [info] dlv: failed to install dlv(github.com/go-delve/delve/cmd/dlv@latest): Error: Command failed: A:\Go\bin\go.exe install -v github.com/go-delve/delve/

By Ne0inhk