JavaScript动态渲染页爬取——Playwright的使用

Playwright的使用

Playwright是微软在2020年年初开源的新一代自动化测试工具,其功能和Selenium、Pyppeteer等类似,都可以驱动浏览器进行各种自动化操作。Playwright对市面上的主流浏览器都提供了支持,API功能简洁又强大,虽然诞生比较晚,但是现在发展的非常火热。

1. Playwright的特点

  • Playwright支持当前所有的主流浏览器,包括Chrome和Edge(基于Chromium)、Firefox、Safari(基于WebKit),提供完善的自动化控制API。
  • Playwright支持移动端页面测试,使用设备模拟技术,可以让我们在移动Web浏览器中测试响应式Web应用程序。
  • Playwright支持所有浏览器的无头模式和非无头模式的测试。
  • Playwright的安装和配置过程非常简单,安装过程中会自动安装对应的浏览器和驱动,不需要额外配置WebDriver等
  • Playwright提供和自动等待相关的API,在页面加载时会自动等待对应的节点加载,大大减小了API编写的复杂度。

2. 安装

命令如下:

pip3 install playwright

完成后需要进行一些初始化操作:

playwright install

这时Playwright会安装Chromium、Firefox和WebKit浏览器并配置一些驱动,我们不必关心具体的配置过程,Playwright会自动为我们配置好。

3. 基本使用

Playwright支持两种编写模式,一种是和Pyppeteer一样的异步模式,一种是和Selenium一样的同步模式,可以根据实际需要选择使用不同的模式。

先来看一个同步模式的例子:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    for browser_type in [p.chromium, p.firefox, p.webkit]:
        browser = browser_type.launch(headless=False)
        page = browser.new_page()
        page.goto('<https://www.baidu.com>')
        page.screenshot(path=f'screenshot-{browser_type.name}.png')
        print(page.title())
        browser.close()

注意:如果不把headless参数设置为False,就会以默认的无头模式启动浏览器,我们将看不到任何窗口。

这里我们首先导入并直接调用了sync_playwright方法,该方法的返回值是一个PlaywrightContextManager对象,可以理解为一个浏览器上下文管理器,我们将其赋值为p变量。然后依次调用p的Chromium、firefox和webkit属性创建了Chromium、Firefox以及Webkit浏览器实例。接着用一个for循环依次执行了这3个浏览器的launch方法,同时设置headless参数为False。运行一下这段代码,可以看到有3个浏览器依次启动,分别是Chromium、Firefox和Webkit浏览器,启动后都是加载百度首页,页面加载后,生成页面截图,然后把页面标题打印到控制台,就退出了。

10

控制台的运行结果如下:

百度一下,你就知道
百度一下,你就知道
百度一下,你就知道

可以发现,我们非常方便地启动了三种浏览器,完成了自动化操作,并通过几个API获取了页面的截图和数据,整个过程速度非常快,这就是Playwright最为基本的用法。

当然,除了同步模式,Playwright还提供了支持异步模式的API, 如果我们的项目里面使用了asyncio关键字,就应该使用异步模式,写法如下:

from playwright.async_api import async_playwright
import asyncio

async def main():
    async with async_playwright() as p:
        for browser_type in [p.chromium, p.firefox, p.webkit]:
            browser = await browser_type.launch()
            page = await browser.new_page()
            await page.goto('<https://www.baidu.com>')
            await page.screenshot(path=f'screenshot-{browser_type.name}.png')
            print(await page.title())
            await browser.close()
            
asyncio.run(main())

写法和同步模式基本一样,只不过是这里导入的是async_playwright方法,不再是sync_playwright方法,以及写法上添加了async/await 关键字,最后的运行效果和同步模式是一样的。

4. 代码生成

Playwright还有一个强大的功能,可以录制我们在浏览器中操作并自动生成代码,有了这个功能,我们甚至一行代码都不用写。这个功能可以通过playwright命令行调用codegen实现,先来看看codegen命令都有什么参数,输入如下命令:

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, java-junit (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,代表会生成同步模式的操作代码。

了解这些用法后,我们来尝试启动一个Firefox浏览器,然后将操作结果输出到script.py文件,命令如下:

playwright codegen -o script.py -b firefox

8

可以看到,浏览器中会高亮显示我们正在操作的页面节点,同时显示对应的选择器字符串 page.locator(“#kw”).fill(“nba”),右侧代码窗口如下图:

7

在操作浏览器的过程中,该窗口中的代码会跟着实时变化,现在这里已经生成了刚刚一系列操作对应的代码,例如:

    page.locator("#kw").fill("nba")

这行代码就对应在搜索框中输入nba的操作,所有操作完毕之后,关闭浏览器,Playwright会生成一个script.py文件,内容如下:

from playwright.sync_api import Playwright, sync_playwright, expect

def run(playwright: Playwright) -> None:
    browser = playwright.firefox.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto("<https://www.baidu.com/>")
    page.locator("#kw").click()
    page.locator("#kw").press("CapsLock")
    page.locator("#kw").fill("nba")

    # ---------------------
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

可以看到这里生成的代码和我们之前写的示例代码几乎差不多,而且也可以运行,运行之后会看到它在浮现我们刚才所做的操作。所以,有了代码生成功能,只通过简单的可视化点击操作就能生成代码,可谓非常方便。

5. 支持移动端浏览器

Playwright的另一个特色就是支持模拟移动端浏览器,例如模拟打开iPhone12 Pro Max上的Safari浏览器。

示例代码如下:

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')
    page = context.new_page()
    page.goto('<https://www.whatismybrowser.com/>')
    page.wait_for_load_state(state='networkidle')
    page.screenshot(path='browser-iphone.png')
    browser.close()

这里我们先用PlaywrightContextManager对象的devices属性指定了一台移动设备,传入的参数是移动设备的型号,例如iPhone 12 Pro Max,当然也可以传入其他内容,例如iPhone 8、Pixel 2等。

运行一下代码,可以发现弹出了一个移动版浏览器,然后加载出了对应的页面,如下图所示:

6

输出的截图也是浏览器中显示的结果,可以看到,这里显示的浏览器信息是iPhone上的Safari浏览器。也就是我们成功模拟了一个移动端浏览器。

6. 选择器

Playwright扩展了方便好用的规则,直接根据文本内容筛选、根据节点层级结构筛选等。

  • 文本选择

文本选择支持直接使用text=这样的语法进行筛选,示例如下:

page.click("text=Log in")
  • CSS选择器

根据id或class筛选:

page.click("button")
page.click("#nav-bar .contact-us-item")

根据特定的节点属性筛选:

page.click("[data-test=login-button]")
page.click("[aria-label='Sign in']")
  • CSS选择器+文本值

可以使用CSS选择器结合文本值的方式进行筛选,比较常用的方法是has-text和text,前者代表节点中包含指定的字符串,后者代表节点中的文本值和指定的字符串完全匹配,示例如下:

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

第一行代码就是选择文本值中包含Playwright字符串的article节点,第二行代码是选择id为nav-bar的节点中文本值为Contact us的节点。

  • CSS选择器 + 节点关系

CSS选择器还可以结合节点关系来筛选节点,例如使用has指定另外一个选择器,示例如下:

page.click(".item-description:has(.item-promo-banner")
  • XPath

当然,XPath也是支持的,不过xpath这个关键字需要我们自行指定,示例如下:

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

这里在开头指定“xpath=字符串”,代表这个字符串是一个XPath表达式。

7. 常用的操作方法

所有的方法都可以从Page对象的API文档查找,文档地址是https://playwright.dev/python/docs/api/class-page。

  • 事件监听

Page对象提供一个on方法,用来监听页面中发送的各个事件,例如close、console、load、request、response等。

这里监听reponse事件,在每次网络请求得到响应的时候会出发这个事件,我们可以设置回调方法来获取响应中的全部信息,示例如下:

from playwright.sync_api import sync_playwright

def on_response(response):
    print(f'Statue {response.status}: {response.url}')

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.on('response', on_response)
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    browser.close()

创建Page对象后,开始监听response事件,同时将回调方法设置为on_response,on_response接收一个参数,然后输出响应中的状态码和链接。

运行上述代码后,可以看到控制台输出如下结果:

Statue 200: <https://spa6.scrape.center/>
Statue 200: <https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css>
Statue 200: <https://spa6.scrape.center/css/app.ea9d802a.css>
Statue 200: <https://spa6.scrape.center/js/chunk-vendors.77daf991.js>
Statue 200: <https://spa6.scrape.center/js/app.5ef0d454.js>
Statue 200: <https://spa6.scrape.center/js/chunk-2f73b8f3.8f2fc3cd.js>
....
....
Statue 200: <https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@464w_644h_1e_1c>
Statue 200: <https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c>

可以发现,这个输出结果其实正好对应浏览器Network面板中的所有请求和响应,和下图的内容一一对应。

3

这个网站,真实数据都是Ajax加载的,同时Ajax请求中还带有加密参数,不好轻易获取。但有了on_response方法,如果我们想截获Ajax请求,岂不是就非常容易了?改写一下这里的判定条件,输出对应的JSON的结果,代码如下:

from playwright.sync_api import sync_playwright

def on_response(response):
    if '/api/movie/' in response.url and response.status == 200:
        print(response.json())

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.on('response', on_response)
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    browser.close()

控制台的输出结果如下:

{'count': 102, 'results': [{'id': 1, 'name': '霸王别姬', 'alias': 'Farewell My Concubine', 'cover': '<https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@_644h_1e_1c>', 
'categories': ['剧情', '爱情'], 'published_at': '1993-07-26', 'minute': 171, 'score': 9.5, 'regions': ['中国内地', '中国香港']}, {'id': 2, 'name': '这个杀手不on', 'cover': '<https://p1.meituan.net/movie/6bea9af4524dfbd0b6>

....
113e174332.jpg@464w1e_1c', 'categories': ['剧情', '喜剧', '爱情'], 'published_at': '1999-02-13', 'minute': 85, 'score': 9.5, 'regions': ['中国香港']}, {'id': 9, 'name': '楚门的世界', 'alias':ow', 'cover': '<https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c>', 'categories': ['剧情', '科幻'], 'published_at': None, 'minute': 103core': 9.0, 'regions': ['美国']}, {'id': 10, 'name': '狮子王', 'alias': 'The Lion King', 'cover': '<https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@644h_1e_1c>', 
 'categories': ['动画', '歌舞', '冒险'], 'published_at': '1995-07-15', 'minute': 89, 'score': 9.0, 'regions': ['美国']}]}

通过on_response方法拦截了Ajax请求,直接拿到了响应结果,即使这个Ajax请求中有加密参数,也不用担心,因为我们截获的是最后的响应结果。

  • 获取页面源代码

获取页面源代码的过程其实很简单,直接调用Page对象的content方法就行,用法如下:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('<https://spa6.scrape.center>')
    page.wait_for_load_state('networkidle')
    html = page.content()
    print(html)
    browser.close()

运行结果就是页面源代码,获取了页面源代码之后,借助一些解析工具就可以提取想要的信息了。

  • 页面点击

实现页面点击的方法,就是click方法。click方法的API定义如下:

page.click(selector, **kwargs)

必须传入的参数是selector,其他参数都是可选的。selector代表选择器,用来匹配想要点击的节点,如果有多个节点和传入的选择器相匹配,那么只使用第一个节点。

  • 文本输入

文本输入对应的方法是fill,其API定义如下:

page.fill(selector, value, **kwargs)

这个方法有两个必传参数,第一个也是selector,依然代表选择器;第二个是value,代表输入的文本内容;

  • 获取节点属性

除了操作节点本身,我们还可以获取节点的属性,方法是get_attribute,其API定义如下:

page.get_attribute(selector, name, **kwargs)

两个必传参数,第一个还是selector,第二个是name,代表要获取的属性的名称;还可以通过timeout参数指定查找对应节点的最长等待时间。示例如下:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    href = page.get_attribute('a.name', 'href')
    print(href)
    browser.close()

调用get_attribute方法,传入的selector参数值是a.name,代表查找class为name的a节点,name参数值传入了href,代表获取链接的内容,输出结果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
  • 获取多个节点

使用query_selector_all方法可以获取所有节点,它会返回节点列表,通过遍历得到其中的单个节点后,可以接着调用上面介绍的针对单个节点的方法完成一些操作和获取属性,示例如下:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    elements = page.query_selector_all('a.name')
    for element in elements:
        print(element.get_attribute('href'))
        print(element.text_content())
    browser.close()

这里通过query_selector_all方法获取了所有匹配到的节点,每个节点各对应一个ElementHandle对象,可以调用ElementHandle对象的get_attribute方法获取节点属性,也可以通过text_content方法获取节点文本。

运行结果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王别姬 - Farewell My Concubine
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy
这个杀手不太冷 - Léon
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz
.......
乱世佳人 - Gone with the Wind
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4
喜剧之王 - The King of Comedy
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5
楚门的世界 - The Truman Show
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA==
狮子王 - The Lion King
  • 获取单个节点

获取单个节点也有特定的方法,就是query_selector,如果传入的选择器匹配到多个节点,那它只会返回第一个,示例如下:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    element = page.query_selector('a.name')
    print(element.get_attribute('href'))
    print(element.text_content())
    browser.close()

运行结果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王别姬 - Farewell My Concubine
  • 网络劫持

实用方法——route,利用这个方法可以实现网络劫持和修改操作,例如修改request的属性,修改响应结果等,示例如下:

from playwright.sync_api import sync_playwright
import re

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    def cancel_request(route, request):
        route.abort()

    page.route(re.compile(r"(\\.png)|(\\.jpg)"), cancel_request)
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    page.screenshot(path='no_picture.png')
    browser.close()

这里调用了route方法,第一个参数通过正则表达式传入了URL路径,这里的(.png)| (.jpg)代表所有包含.png或.jpg的链接,遇到这样的请求,会回调cancle_request方法做处理。cancel_request方法接收两个参数,一个是route,代表一个CallalbeRoute对象;另一个是request,代表Request对象。这里我们直接调用CallableRoute对象的abort方法,取消了这次请求,导致最终的结果如是取消全部图片的加载。

运行结果如下图,可以看到图片全都加载失败了。

2

这个设置看起来没什么用啊?其实是有用的,图片资源都是二进制文件,我们在爬取过程中可能并不想关心具体的二进制文件的内容,而只关系图片的URL是什么,所以浏览器中是否把图片加载出来就不重要了,如此设置可以提高整个页面的加载速度,提高爬取效率。

利用这个功能,还可以对一些响应内容进行修改,例如直接将响应结果修改为自定义的文本内容。这里首先定一个HTML文本文件,命名为custom_response.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hack Response</title>
</head>
<body>
    <h1>Hack Response</h1>
</body>
</html>

代码编写如下:

from playwright.sync_api import sync_playwright
import time
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    def modify_response(route, request):
        route.fulfill(path="./custom_response.html")

    page.route('**/*', modify_response)
    page.goto("<https://spa6.scrape.center/>")
    time.sleep(10)
    browser.close()

这里我们使用CallableRoute对象的fulfill方法指定了一个本地文件,就是刚才我们定义的HTML文件,运行结果如图所示:

1

可以看到,响应结果已经被我们修改了,URL依然不变,但结果已经变成我们修改后的HTML代码。所以通过route方法,可以灵活地控制请求和响应的内容,从而在某些场景下达成某些目的。

获取更多体验可以访问[小蜜蜂AI][https://zglg.work]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/501970.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx界面管理工具之nginxWebUI 搭建与使用

nginx界面管理工具之nginxWebUI 搭建与使用 一、nginxWebUI 1.nginx网页配置工具 官网地址: http://www.nginxwebui.cn 源码地址&#xff1a;https://git.chihiro.org.cn/chihiro/nginxWebUI 2.功能说明 本项目可以使用WebUI配置nginx的各项功能, 包括http协议转发, tcp协议…

EXCEL VBA将word里面的指定的关键词替换掉后并标记红色字体

EXCEL VBA将word里面的指定的关键词替换掉后并标记红色字体 Sub 开关() Call 新建副本 Call ReplaceAndHighlightInFolder End Sub Sub 新建副本()fpath ThisWorkbook.Path & "\"Dim MyFile As ObjectSet MyFile CreateObject("Scripting.FileSystemObjec…

FireWorks:加速科学计算和工作流程的强大工具

FireWorks&#xff1a;加速科学计算和工作流程的强大工具 FireWorks是一个功能强大的开源工具&#xff0c;旨在简化和加速科学计算和工作流程。它提供了一个灵活的编排框架&#xff0c;使研究人员和工程师能够高效地设计、管理和执行复杂的计算任务和工作流。本文将深入探讨Fir…

Hadoop安装部署-DataNode集群版

Hahoop分布式文件系统支持DataNode节点的大规模扩展&#xff0c;本文主要描述DataNode集群版的安装部署。 如上所示&#xff0c;Hadoop分布式文件系统中每个文件是以数据块的方式存储在不同的DataNode中&#xff0c;每个数据块都支持高可用性&#xff0c;当其中一个数据块对应的…

vue watch 深度监听

vue2文档&#xff1a;API — Vue.js vue3文档&#xff1a;侦听器 | Vue.js watch 可以用来监听页面中的数据&#xff0c;但如果监听的源是对象或数组&#xff0c;则使用深度监听&#xff0c;强制深度遍历源&#xff0c;以便在深度变更时触发回调。 一&#xff0c;监听 <t…

项目管理—项目合同签订后客户又不认了

大家好&#xff0c;我是不会魔法的兔子&#xff0c;是一名执业律师&#xff0c;创建[项目管理者的法小院儿]&#xff0c;持续从法律的角度分享项目管理中的风险及预防问题&#xff0c;让项目管理者能够提早发现与解决项目执行过程中的风险&#xff0c;同时欢迎大家一起交流&…

从快递公司内部辞职的快递员才告诉你的寄快递的真相!

我们知道现在寄快递有很多种方法&#xff0c;但是每种方法也都花费不少&#xff0c;但是我们知道最省钱的寄快递的方法吗&#xff1f;别急&#xff0c;小编了解到快递员告诉我许多快递物流的真相。我们寄快递无非就是去快递驿站寄快递&#xff0c;然后称重&#xff0c;然后计算…

七大 QC 工具图的定义与示例(看这篇就够了)

前言 七大 QC 工具图是通过数值的方式进行数据分析的工具&#xff0c;分别是鱼骨图、直方图、柏拉图、散布图、管制图、检查图和层别图。其实&#xff0c;我们在日常生活与工作中经常看到它们&#xff0c;只是样子和名字对不上而已&#xff0c;今天写这篇文章就是为了帮助自己…

蓝桥杯单片机---第十届省赛题目解析

文章目录 比赛题目一、代码相关定义、声明1.头文件声明2.变量声明 二、主要函数1.main函数2.按键扫描3.数码管显示4.LED显示5.定时器中断 三、次要函数1.初始化函数Init2.按键函数Key3.LED函数Led4.数码管函数Seg5.iic函数中6.onewire函数中 总结 比赛题目 这里因为我没有这个题…

如何搭建属于自己的Docker私有仓库

华子目录 Docker registry仓库介绍分类registry组成&#xff08;repository和index&#xff09;Repositoryindex 拉取上床仓库镜像拉取上传 知名docker仓库在docker hub商创建自己的docker registry将镜像上传到自己的registry从registry仓库中拉取镜像 搭建私有仓库Distributi…

备战蓝桥杯---树学初步1

LCA&#xff08;最近公共祖先&#xff09; 定义&#xff1a;有根树的两个节点u,v&#xff0c;他们的LCA是一个节点x,其中x是他们的公共祖先并且X的深度尽可能大。 法1---Tarjan算法&#xff1a; 核心&#xff1a;DFS并查集 在并查集中建立仅有u的集合&#xff0c;设该集合祖…

Redis入门到实战-第十三弹

Redis入门到实战 Redis中JSON数据类型常见操作官网地址Redis概述JSON常见操作更新计划 Redis中JSON数据类型常见操作 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是…

Java 扫描某包下所有类的注解并获得注解值

背景 &#xff1a; 需求 需要获取某个包下的所有的注解 并不是全部项目的 所以 只用针对某个包 进行扫描 获取注解 数据就行 百度了一圈 spring boot 没有自带的 获取注解集合的方法 在看 php 中 hyperf 框架 看到了 这个方法 就是因为 我需求是 php 和java 合体 微服务开发 …

c语言游戏实战(5):走迷宫

前言&#xff1a; 制作一个迷宫游戏是一个有趣的编程挑战。首先&#xff0c;我们需要设计一个二维数组来表示迷宫的布局&#xff0c;其中每个元素代表迷宫中的一个格子。我们可以使用不同的值来表示空格、墙壁和起点/终点。接下来&#xff0c;我们需生成迷宫。在生成迷宫的过程…

【Go】三、Go指针

文章目录 1、指针2、说明 1、指针 &符号变量 就可以获取这个变量内存的地址*int 是一个指针类型 &#xff08;可以理解为 指向int类型的指针&#xff09; package main import("fmt" ) func main(){var age int 18//&符号变量 就可以获取这个变量内存的地…

武汉星起航:一站式跨境电商服务,助力企业扬帆远航

武汉星起航电子商务有限公司&#xff0c;作为业界知名的自营亚马逊跨境电商与孵化服务提供商&#xff0c;凭借优质的服务和卓越的口碑&#xff0c;赢得了众多企业的信赖与青睐。公司以其独特的一站式跨境电商服务优势&#xff0c;为合作伙伴提供了全方位、个性化的解决方案&…

[RAM] 3D RAM 能否复制 3D NAND 神话?

主页&#xff1a; 元存储博客 文章目录 前言挑战Lam Research 3D RAMNeo 3D X-RAM展望 前言 人工智能时代&#xff0c;DRAM的容量扩展受到限制&#xff0c; 需要迫切解决&#xff0c;以满足应用的要求[2]。 3D DRAM是指以垂直方向存储位的体系结构&#xff0c;类似于3D NAND[…

Python环境下一种改进小波分解方法-用于多分量信号的分解

小波通俗的讲就是一种振幅表现为在正负之间震荡的波形。小波变换在基于短时傅立叶变换的前提下&#xff0c;又加入了其所没有的可随频率变化的“时间-频率”窗口&#xff0c;其能对时间、频率进行局部化分析&#xff0c;并且对待处理信号通过多尺度处理使其表现为时-频细分的特…

【详细讲解MNN介绍,安装和编译】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

26. UE5 RPG同步面板属性(二)

在上一篇&#xff0c;我们解析了UI属性面板的实现步骤&#xff1a; 首先我们需要通过c去实现创建GameplayTag&#xff0c;这样可以在c和UE里同时获取到Tag创建一个DataAsset类&#xff0c;用于设置tag对应的属性和显示内容创建AttributeMenuWidgetController实现对应逻辑 并且…