一、页面操作
首先,前期我们将Chrome驱动添加到环境变量了,所以我们可以直接初始化界面。
from selenium import webdriver
# 初始化浏览器为chrome浏览器
driver = webdriver.Chrome()
...
...
...
# 关闭浏览器
driver.close()
Selenium是一个用于自动化浏览器操作的工具,可以用于模拟用户在网页上的各种操作。以下是Selenium常用的页面操作:
1、打开网页
- 使用
driver.get(url)
方法可以打开指定的网页。
# 访问百度首页
driver.get(r'https://www.baidu.com/')
2、查找元素
- 可以使用不同的定位方式(如ID、class、XPath等)来查找页面上的元素,例如
driver.find_element_by_id("element_id")
。
# 通过 类 属性查找语法,查找出类属性值为 s_ipt 的标签
element = browser.find_element(by=By.CLASS_NAME, value='s_ipt')
# 通过 id 属性查找语法,查找出 id 属性值为 kw 的标签
element = browser.find_element(by=By.ID, value='kw')
3、输入文本
- 使用
element.send_keys("text")
方法可以向文本输入框中输入指定的文本。
# 找到输入框元素
input_box = driver.find_element(By.ID,"input_box_id")
# 清空输入框内容
input_box.clear()
# 输入文本
input_box.send_keys("Hello, World!")
4、点击按钮
- 使用
element.click()
方法可以点击指定的按钮或链接。
# 找到按钮元素
button = driver.find_element(By.ID,"button_id")
# 点击按钮
button.click()
5、提交表单
- 对于表单元素,可以使用
element.submit()
方法来提交表单。
form = driver.find_element(By.ID,'form_id')
form.submit()
6、获取元素属性
- 可以使用
element.get_attribute("attribute_name")
方法来获取指定元素的属性值。
# 找到元素
element = driver.find_element(By.ID, "element_id")
# 获取元素的属性值
attribute_value = element.get_attribute("attribute_name")
7、获取元素文本
- 使用
element.text
属性可以获取指定元素的文本内容。
# 使用 XPath 定位到具有类名为 "example" 的 <div> 元素
element = driver.find_element(By.XPATH, '//div[@class="example"]')
# 使用 element.text 获取该元素的文本内容
text_content = element.text
8、切换窗口
- 使用
driver.switch_to.window(window_handle)
方法可以切换到指定的窗口。
# 获取当前窗口句柄
current_window = driver.current_window_handle
# 获取所有窗口句柄
all_windows = driver.window_handles
# 切换到指定窗口
driver.switch_to.window(window_handle)
9、切换frame
- 使用
driver.switch_to.frame(frame_reference)
方法可以切换到指定的frame。
# 切换到 frame 的名称为 "frame_name"
driver.switch_to.frame("frame_name")
# 或者通过 frame 元素来切换
frame_element = driver.find_element(By.XPATH, '//iframe[@id="frame_id"]')
driver.switch_to.frame(frame_element)
10、执行JavaScript代码
- 使用
driver.execute_script("javascript_code")
方法可以执行指定的JavaScript代码。
# 在当前页面执行 JavaScript 代码
driver.execute_script("alert('Hello, this is an alert from JavaScript code!');")
# 在当前页面滚动到页面底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 在当前页面获取页面标题
title = driver.execute_script("return document.title;")
print("Page title:", title)
二、元素定位
1、八大选择器介绍
- 我们在实际使用浏览器的时候,很重要的操作有输入文本、点击确定等等。
- 对此,Selenium提供了一系列的方法来方便我们实现以上操作。
- 通过webdriver对象的 find_element(by=“属性名”, value=“属性值”),主要包括以下这八种。
属性 | 函数 | 参数说明 |
---|---|---|
CLASS | find_element(by=By.CLASS_NAME, value=‘’) | |
XPATH | find_element(by=By.XPATH, value=‘’) | |
LINK_TEXT | find_element(by=By.LINK_TEXT, value=‘’) | |
PARTIAL_LINK_TEXT | find_element(by=By.PARTIAL_LINK_TEXT, value=‘’) | |
TAG | find_element(by=By.TAG_NAME, value=‘’) | |
CSS | find_element(by=By.CSS_SELECTOR, value=‘’) | |
ID | find_element(by=By.ID, value=‘’) | |
NAME | find_element(by=By.NAME, 'element_name') |
2、查找方法介绍
方法 | 作用 | 参数说明 |
---|---|---|
find_element | 得到首个被匹配的节点 | By参数——By.ID通过标签ID属性匹配,By.CSS_SELECTOR,使用CSS选择器进行定位匹配,By.Name通过属性名进行匹配 Value参数——匹配的属性值 |
find_element | 得到所有被匹配的节点,返回值为WebElement元素列表 |
3、获取单个元素(find_element())
- 用于查找单个元素
driver.find_element(By.ID,"inputOriginal")
driver.find_element(By.CSS_SELECTOR,"#inputOriginal")
driver.find_element(By.TAG_NAME,"div")
driver.find_element(By.NAME,"username")
driver.find_element(By.LINK_TEXT,"下一页")
ps: 如果找不到相应的元素会报错
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: xx
4、获得多个元素(find_elements())
- 用于查找多个元素,返回的是一个元素列表,而不是单个元素。
driver.find_elements(By.ID,"inputOriginal")
driver.find_elements(By.CSS_SELECTOR,"#inputOriginal")
driver.find_elements(By.TAG_NAME,"div")
driver.find_elements(By.NAME,"username")
driver.find_elements(By.LINK_TEXT,"下一页")
5、示例
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
# css选择器进行筛选
# 如果有多个元素被匹配,find_element只会获取第一个
result = driver.find_element(by=By.CSS_SELECTOR, value='.mnav.c-font-normal.c-color-t')
print(result, '\n', '---------------')
# 匹配多个find_elements
result_list = driver.find_elements(by=By.CSS_SELECTOR, value='.mnav.c-font-normal.c-color-t')
for item in result_list:
print(item)
print('---------------')
result_byid = driver.find_element(by=By.NAME, value='tj_more')
print(result_byid)
"""
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.9")>
---------------
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.9")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.10")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.11")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.12")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.13")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.14")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.15")>
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.16")>
---------------
<selenium.webdriver.remote.webelement.WebElement (session="2e9b80d6bfea0303dc16be6364c853e1", element="f.79275F9BA38B2FA61B86C88F4B74A9AC.d.092D951EAC90B6C7324352F27B039E85.e.17")>
"""
三、元素操作(节点交互)
在Web开发中,元素操作(节点交互)是指使用工具或技术与网页上的各种元素进行交互的过程。这些元素可以是文本框、按钮、下拉菜单、链接、图像等,通过操作这些元素,可以实现用户与网页的互动。
而在自动化测试、网络爬虫和数据采集等场景中,也经常需要对网页上的元素进行操作。常用的工具包括 Selenium(用于自动化浏览器操作)、Beautiful Soup(用于解析HTML和XML文档)、Requests(用于发送HTTP请求)等。
接下来进行selenium中有哪些元素操作
首先常用的有:
- 输入文字时用
send_keys()
方法, - 清空文字时用
clear()
方法, - 点击按钮时用
click()
方法。
其他方法有:
kw1.clear() #清除元素的值
kw1.click() #点击元素
kw1.id #Selenium所使用的内部ID
kw1.get_property('background') #获取元素的属性的值
kw1.get_attribute('id') #获取元素的属性的值
kw1.location #不滚动获取元素的坐标
kw1.location_once_scrolled_into_view #不滚动且底部对齐并获取元素的坐标
kw1.parent #父元素
kw1.send_keys('') #向元素内输入值
kw1.size #大小
kw1.submit #提交
kw1.screenshot('2.png') #截取元素形状并保存为图片
kw1.tag_name #标签名
kw1.text #内容,如果是表单元素则无法获取
kw1.is_selected() #判断元素是否被选中
kw1.is_enabled() #判断元素是否可编辑
kw1.is_displayed #判断元素是否显示
kw1.value_of_css_property('color') #获取元素属性的值
kw1._upload('2.png') #上传文件
平常使用比较多的是
当我们使用find_element方法仅仅只获取了元素对象,接下来就可以对元素执行以下操作
(1)从定位到的元素中提取数据的方法
element.get_attribute(key) # 获取key属性名对应的属性值
element.text # 获取开闭标签之间的文本内容(2)对定位到的元素的操作
element.click() # 对元素执行点击操作
element.submit() # 对元素执行提交操作
element.clear() # 清空可输入元素中的数据
element.send_keys(data) # 向可输入元素输入数据
四、补充
1、设置浏览器大小
- set_window_size()方法可以用来设置浏览器大小(就是分辨率)
from selenium import webdriver
import time
browser = webdriver.Chrome()
# 访问百度页面
browser.get('https://www.baidu.com')
time.sleep(2)
# 设置分辨率 500*500
browser.set_window_size(500, 500)
time.sleep(2)
# 关闭浏览器
browser.close()
- maximize_window则是设置浏览器为全屏。
from selenium import webdriver
import time
browser = webdriver.Chrome()
# 设置浏览器大小:全屏
browser.maximize_window()
browser.get('https://www.baidu.com')
time.sleep(2)
# 关闭浏览器
browser.close()
2、前进后退
- 前进后退也是我们在使用浏览器时非常常见的操作
- forward()方法可以用来实现前进
- back()可以用来实现后退。
# 导入浏览器启动程序
from selenium import webdriver
import time
browser = webdriver.Chrome()
# 设置浏览器全屏
browser.maximize_window()
# 打开百度页面
browser.get('https://www.baidu.com')
time.sleep(2)
# 打开哔哩哔哩页面
browser.get('https://www.bilibili.com/')
time.sleep(2)
# 后退到百度页面
browser.back()
time.sleep(2)
# 前进的哔哩哔哩页面
browser.forward()
time.sleep(2)
# 关闭浏览器
browser.close()
3、获取页面基础属性
- 当我们用selenium打开某个页面,有一些基础属性如网页标题、网址、浏览器名称、页面源码等信息
from selenium import webdriver
# 实例化得到浏览器对象
browser = webdriver.Chrome()
# 访问百度页面
browser.get('https://www.baidu.com')
# 网页标题
print(browser.title)
# 百度一下,你就知道
# 当前网址
print(browser.current_url)
# https://www.baidu.com/
# 浏览器名称
print(browser.name)
# chrome
# 网页源码
print(browser.page_source)
4、切换选项卡
from selenium import webdriver
import time
# 创建 Chrome 浏览器的实例
browser = webdriver.Chrome()
# 打开百度网页
browser.get('https://www.baidu.com/')
# 隐式等待2秒,确保页面加载完全
browser.implicitly_wait(2)
# 打印当前窗口句柄
print(browser.window_handles)
# 在当前浏览器实例中新开一个选项卡
browser.execute_script('window.open()')
# 获取所有选项卡的句柄列表
all_handles = browser.window_handles
# 切换到第二个选项卡(下标为1)
browser.switch_to.window(all_handles[1])
# 在第二个选项卡中打开淘宝网页
browser.get('http://www.taobao.com')
# 等待2秒
time.sleep(2)
# 切换回第一个选项卡(下标为0)
browser.switch_to.window(all_handles[0])
# 在第一个选项卡中打开hao123网页
browser.get('http://www.hao123.com')
# 等待2秒
time.sleep(2)
# 关闭当前选项卡
browser.close()
# 关闭浏览器实例
browser.quit()
五、执行js代码
1、为什么需要使用selenium执行js代码?
因为网页上有些操作是不能通过Selenium元素定位来操作成的,如滚动条,时间控件等。此时需要借助JS脚本来执行。目前,有比较火基于js的自动化测试框架,如cypress框架。
2、怎么使用selenium执行js代码?
在Selenium中执行JavaScript代码是一种强大的技术,可以帮助你在浏览器中执行各种操作,例如模拟用户行为、操作DOM元素、获取动态内容等。
在Selenium中,你可以使用execute_script()
方法来执行JavaScript代码。这个方法允许你在当前页面的上下文中执行自定义的JavaScript代码。
(1)execute_script() 方法
- 语法
driver.execute_script(script, *args)
script
: 要执行的JavaScript代码。*args
: 可选参数,用于传递给JavaScript代码的参数。
(2)如何使用 execute_script() 方法
- 执行简单的JavaScript代码:
driver.execute_script("alert('Hello, World!');")
- 操作DOM元素:
# 点击一个按钮
button = driver.find_element(By.XPATH, "//button[@id='myButton']")
driver.execute_script("arguments[0].click();", button)
# 设置输入框的值
input_box = driver.find_element(By.ID, "myInput")
driver.execute_script("arguments[0].value = 'Hello';", input_box)
3、具体使用
(1)滚动到页面底部
import time
from selenium import webdriver
# 初始化WebDriver
browser = webdriver.Chrome()
# 打开京东网页
browser.get('https://www.jd.com/')
# 使用JavaScript执行滚动到页面底部的操作
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# 等待3秒,以便页面加载完成
time.sleep(3)
(2)点击元素
# 使用JavaScript点击一个按钮
element = browser.find_element(By.ID,'button_id')
browser.execute_script('arguments[0].click();', element)
- 这里使用了
execute_script
方法执行JavaScript脚本来点击按钮。 arguments[0]
表示传递给JavaScript脚本的第一个参数(即element
),通过调用click()
方法实现点击操作。
(3)输入文本
# 使用JavaScript输入文本到文本框
element = browser.find_element(By.ID,'input_id')
text = 'Hello, World!'
browser.execute_script('arguments[0].value = arguments[1];', element, text)
- 同样地,使用
execute_script
方法执行JavaScript脚本来向文本框输入文本。 arguments[0]
表示传递给JavaScript脚本的第一个参数(即element
),通过将value
属性设置为arguments[1]
来输入文本。
(4)拖动元素
# 使用JavaScript拖动一个可拖动的元素到目标位置
source_element = browser.find_element(By.ID,'source_element_id')
target_element = browser.find_element(By.ID,'target_element_id')
browser.execute_script('''
var source = arguments[0], target = arguments[1];
var offsetX = target.getBoundingClientRect().left - source.getBoundingClientRect().left;
var offsetY = target.getBoundingClientRect().top - source.getBoundingClientRect().top;
var event = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window });
source.dispatchEvent(event);
event = new MouseEvent('mousemove', { bubbles: true, cancelable: true, view: window, clientX: offsetX, clientY: offsetY });
source.dispatchEvent(event);
event = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window });
source.dispatchEvent(event);
''', source_element, target_element)
- 上述示例中,我们使用
execute_script
方法执行了一段JavaScript代码来实现拖动操作。 - 首先,获取源元素和目标元素的位置信息,然后创建鼠标事件并分发到源元素上,模拟鼠标按下、移动和松开的动作,从而实现拖动操作。
(5)获取元素属性
# 使用JavaScript获取元素的属性值
element = browser.find_element(By.ID,'element_id')
attribute_value = browser.execute_script('return arguments[0].getAttribute("attribute_name");', element)
print(attribute_value)
- 通过
execute_script
方法执行JavaScript脚本来获取目标元素的属性值。 - 这里使用
getAttribute
方法传递属性名参数来返回对应的属性值。
4、具体示例
- 百度检索
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
# 导入浏览器驱动
executable_path = 'chromedriver.exe'
service = Service(executable_path=executable_path)
# 创建浏览器实例化对象
bro = webdriver.Chrome(service=service)
url = 'https://www.baidu.com/'
# 打开浏览器
bro.get(url=url)
top_search = bro.find_element(By.CLASS_NAME,'title-content-title')
attribute_value = bro.execute_script('return arguments[0].getAttribute("class");', top_search)
print(attribute_value)
input_text = bro.find_element(By.ID, 'kw')
search_text = "牛马"
bro.execute_script('arguments[0].value = arguments[1];', input_text, search_text)
time.sleep(3)
search_btn = bro.find_element(By.ID, 'su')
bro.execute_script('arguments[0].click();', search_btn)
time.sleep(3)
bro.close()
- 操作js代码修改12306日期
from selenium import webdriver
from time import sleep
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
# 导入浏览器驱动
executable_path = 'chromedriver.exe'
service = Service(executable_path=executable_path)
driver = webdriver.Chrome(service=service)
driver.get("https://www.12306.cn/index/")
sleep(2)
driver.find_element(By.XPATH, '//*[@id="train_date"]').clear()
driver.find_element(By.XPATH, '//*[@id="train_date"]').send_keys("2023-05-01")
data_js = "document.getElementById('train_date').value='2023-05-01'"
driver.execute_script(data_js)
sleep(3)
driver.close()
- 操作js代码实现视频暂停与播放
from selenium import webdriver
from time import sleep
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 导入浏览器驱动
executable_path = 'chromedriver.exe'
service = Service(executable_path=executable_path)
driver = webdriver.Chrome(service=service)
driver.get(
"https://www.bilibili.com/video/BV1v24y1c7J2/?spm_id_from=333.999.0.0&vd_source=585bb8c2056b6b33343e2dd9e7664bc2")
sleep(4)
video = WebDriverWait(driver, 20, 0.5).until(
EC.presence_of_element_located((By.XPATH, '//*[@id="bilibili-player"]/div/div/div[1]/div[1]/div[2]/div/video')))
url = driver.execute_script("return arguments[0].currentSrc;", video)
print(url)
driver.execute_script("return arguments[0].play()", video)
sleep(1)
driver.execute_script("return arguments[0].pause()", video)
sleep(1)
driver.execute_script("return arguments[0].play()", video)
sleep(3)
driver.quit()
5、注意事项
-
上下文问题:JavaScript代码在当前页面的上下文中执行。如果需要操作特定的元素,确保元素在当前页面中可见。
-
错误处理:执行JavaScript代码时,可能会遇到JavaScript错误。确保你的代码是正确的,并考虑添加错误处理机制。
-
性能考虑:频繁执行大量JavaScript代码可能会影响性能。谨慎使用,并优化代码以提高执行效率。
通过使用execute_script()
方法,你可以在Selenium中灵活地执行JavaScript代码,实现更多复杂的操作,从而提升你的网络爬虫的功能和效率。
六、页面等待
在网络爬虫中,页面等待是一个非常重要的概念,特别是在使用Selenium这样的工具时。页面等待确保你的代码在页面完全加载和渲染后再执行操作,避免因为页面元素尚未加载完成而导致的错误。
1、页面等待的类型
(1)隐式等待(Implicit Wait)
- 隐式等待是设置在查找元素时的等待时间,如果Selenium没有立即找到元素,它会等待一段时间再继续查找。
- 通过设置隐式等待,可以避免在页面元素尚未完全加载时导致的异常。
driver.implicitly_wait(10) # 设置隐式等待时间为10秒
(2)显式等待(Explicit Wait)
- 显式等待是在特定条件满足之前等待的时间。
- 通过指定等待条件和最长等待时间,可以在特定条件成立时继续执行代码。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myElement"))
)
(3)页面加载状态等待
- 等待页面加载完成或特定状态(如
complete
)。
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 10).until(
lambda driver: driver.execute_script("return document.readyState") == "complete"
)
(4)强制等待
- 也叫线程等待, 通过线程休眠的方式完成的等待
- 如等待5秒: Thread sleep(5000)
- 一般情况下不太使用强制等待,主要应用的场景在于不同系统交互的地方。
import time
time.sleep(n) # 阻塞等待设定的秒数之后再继续往下执行
2、页面等待的重要性
-
避免元素未找到错误:等待页面加载确保页面上的元素已经存在,可以避免因为元素未找到而导致的异常。
-
确保操作在正确的时机执行:等待页面加载完全确保你的操作在正确的时机执行,避免操作在页面尚未准备好的情况下执行。
-
提高稳定性和可靠性:页面等待可以提高爬虫的稳定性和可靠性,确保操作在合适的时机执行,减少因页面加载问题导致的错误。
通过合理设置页面等待,可以提高你的爬虫的效率和稳定性,确保你能够成功地获取所需的数据。
3、显示等待和隐式等待的区别
显示等待(explicit wait)和隐式等待(implicit wait)是Selenium中两种不同的等待方式,它们在等待页面元素加载时有一些重要的区别:
-
显示等待(Explicit Wait):
- 显示等待是通过编程方式等待特定条件出现或达成的一种等待方式。
- 显示等待使用 WebDriverWait 类,可以指定等待的最长时间以及轮询间隔时间。
- 显示等待可以针对特定的元素或条件设置等待时间,例如等待元素可见、可点击、存在等。
- 显示等待更灵活,可以根据需要设置不同的等待条件和等待时间,适用于复杂的场景。
-
隐式等待(Implicit Wait):
- 隐式等待是在查找元素时设置的等待时间,它会在整个 WebDriver 实例的生命周期内起作用。
- 隐式等待会在查找元素时等待一定的时间,如果元素在规定时间内找到了,就会立即执行后续操作;如果超时仍未找到元素,就会抛出 NoSuchElement 异常。
- 隐式等待适用于整个 WebDriver 实例的生命周期,对所有查找元素的操作都起作用,但可能会导致不必要的等待时间。
主要区别:
- 显示等待是针对特定的操作设置的等待时间,更精确地控制等待条件和时间。
- 隐式等待是全局性设置,适用于所有元素查找操作,可能会导致不必要的等待时间,并且不够灵活。
一般来说,推荐使用显示等待来确保操作在正确的时机执行,提高稳定性和效率。隐式等待在某些情况下也可以使用,但要注意全局性设置可能会导致不可预料的等待时间。
标签:webdriver,浏览器,selenium,元素,driver,element,browser,find,页面 From: https://www.cnblogs.com/xiao01/p/18115634