news 2026/9/9 17:22:46

Selenium Web Scraping实战:从环境配置到元素定位的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Selenium Web Scraping实战:从环境配置到元素定位的完整指南

搞Selenium Web Scraping这种事,说多了都是泪。尤其你给自己定了个“从错误到成功”的目标,我太能理解这个状态了——想抓个页面数据,结果一上午全耗在环境搭建和元素定位上,最后浏览器弹出来一个空白的自动化窗口,日志里全是各种英文报错。这不是你技术不行,而是Selenium这个工具太依赖“环境”和“细节”了:浏览器版本、驱动版本、页面加载时序、DOM结构,任何一个环节对不上,整个脚本就废了。

这篇文章我想从自己折腾Selenium做Web Scraping的真实经历出发,把那些我踩过的坑、排查过的报错、最后沉淀下来能直接用的方案,完整梳理一遍。适合刚接触Selenium的爬虫新手,也适合已经在用但经常被各种异常折磨的朋友。里面没有玄学,全都是能落地、能复现的操作。

1. 环境准备:版本匹配是第一道坎

1.1 为什么浏览器驱动必须和浏览器版本严格对应

Selenium的本质不是直接操作浏览器内核,而是通过一个“中间人”协议跟浏览器通信。这个中间人就是driver,比如Chrome对应chromedriver,Firefox对应geckodriver。浏览器和driver之间有一套私有协议,版本不一致的时候,两者对话就是对牛弹琴。

我第一次跑Selenium时,直接pip install selenium然后写了个打开百度首页的脚本,结果报错:

selenium.common.exceptions.SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 114 Current browser version is 120.0.6099.130

这个报错已经写得很清楚了:chromedriver是114版本的,但浏览器已经升级到120了。很多人卡在这一步就慌了,其实解决办法特别简单:去ChromeDriver镜像站下载跟你浏览器主版本号一致的driver。

我后来总结了一套稳妥的匹配流程:

  1. 打开Chrome浏览器,在地址栏输入chrome://version/,找到“Chrome 版本”那一行,记下完整版本号,比如120.0.6099.130
  2. 只需要关注主版本号,也就是第一段数字120
  3. 去chromedriver下载页面,选择120.x.x.x的版本下载,Windows用户选chromedriver_win32.zip,macOS用户选chromedriver_mac64.zip
  4. 解压后把chromedriver可执行文件放到一个固定目录,比如/usr/local/bin或者项目根目录下的drivers/文件夹。

这里有个小技巧:如果你用的是Chrome for Testing版本,可以通过命令行查看当前浏览器版本,也可以用程序自动获取:

import subprocess result = subprocess.run( ['chromedriver', '--version'], capture_output=True, text=True ) print(result.stdout)

不过最省心的方式还是推荐用webdriver-manager这个库,它会自动帮你匹配版本并下载驱动,省去手动管理的麻烦。当然,理解版本匹配的原理依然是必须的,否则出问题都不知道怎么排查。

1.2 Selenium安装与第一个可运行脚本

Selenium库本身的安装很简单:

pip install selenium

但这里有一个坑:如果你用的是新版Selenium(4.x以上),代码写法和老版本有些不同。新版不再建议用executable_path传driver路径,而是推荐直接指定路径或者让Selenium自动识别:

from selenium import webdriver from selenium.webdriver.chrome.service import Service # 新版Selenium 4.x推荐写法 service = Service('/path/to/chromedriver') driver = webdriver.Chrome(service=service) driver.get('https://example.com') print(driver.title) driver.quit()

如果你用了webdriver-manager,代码就是这样的:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get('https://example.com') print(driver.title) driver.quit()

我强烈建议新手直接在项目根目录建一个虚拟环境,把所有依赖装进去,不要污染系统全局Python环境。这不是洁癖,而是Selenium相关依赖经常升级,虚拟环境可以让你随便折腾,坏了就删掉重建。

第一个脚本跑通之后,先别急着写复杂逻辑,花十分钟做三件小事:

  • 启动浏览器后手动输入几个URL,观察页面加载方式;
  • driver.page_source打印HTML源码,看看跟自己手动查看网页源码有什么区别;
  • driver.save_screenshot('test.png')存一张截图,确保页面真的渲染出来了。

这三步能帮你快速验证环境是否完全正常,排除隐藏在细节里的坑。

2. 元素定位:报错最多的重灾区

2.1 NoSuchElementException的排查顺序

Web Scraping里最常见的报错就是NoSuchElementException。一行find_element甩下去,结果元素找不到,页面白屏还是动态加载慢,谁也说不清。踩了几次坑之后,我给自己定了一套固定的排查顺序,效率高很多。

第一步,先确认页面是不是自己想要的。有时候是登录态失效跳转到了别的页面,有时候是反爬弹窗挡在了前面。在定位元素之前,先打印当前URL和标题:

print(driver.current_url) print(driver.title)

这一步能排除“跑错页面”的天坑。

第二步,确认元素是真的不存在,还是加载太慢。如果用find_element立刻找元素,页面JS还没渲染完,当然找不到。这种情况下优先加显式等待,而不是无脑time.sleep。比如:

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, 'goods-list')) )

第三步,检查定位器本身写得对不对。如果是XPath,先复制到浏览器开发者工具的Console里用$x()验证一下;如果是CSS Selector,用$$()验证。这一步能快速判断是代码问题还是页面问题。

第四步,如果元素确实存在但找不到,看一下它是不是在iframe里。这是另一个高频坑点,后面会详细说。

2.2 StaleElementReferenceException:最让人头疼的幽灵报错

这个报错的中文意思大概是“元素引用已过期”。出现的原因是你先拿到了一个元素对象,但在这之后页面发生了局部刷新或跳转,DOM结构变了,你手里的旧引用就失效了。

典型场景是这样的:

items = driver.find_elements(By.CLASS_NAME, 'item') for item in items: item.click() # 点击后页面刷新了,下一次循环再操作item就报错

报错信息:

selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document

解决办法有几种。最简单的思路是:每次循环内部重新获取元素,而不是复用旧列表。例如:

for i in range(len(driver.find_elements(By.CLASS_NAME, 'item'))): # 每次重新获取 item = driver.find_elements(By.CLASS_NAME, 'item')[i] item.click() # 操作完后回到列表页,等页面加载 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, 'item')) )

还有一种更稳妥的方式是写一个“重试装饰器”,遇到StaleElementReferenceException就自动重新定位元素。这个思路我在做分页抓取时经常用:

import time from selenium.common.exceptions import StaleElementReferenceException def retry_on_stale(func): def wrapper(*args, **kwargs): for _ in range(3): try: return func(*args, **kwargs) except StaleElementReferenceException: time.sleep(0.5) raise return wrapper

2.3 XPath与CSS Selector的实战选择

XPath和CSS Selector是Selenium定位元素最常用的两种方式。很多教程会说XPath更灵活,CSS更快,但在实际抓取任务中,我的选择标准是“哪个更稳定就用哪个”。

XPath的优势在于可以通过文本内容定位元素,这在抓取购物车页面、菜单列表时特别有用。比如热词里提到的“根据菜单名定位元素”,就可以这样写:

menu_item = driver.find_element( By.XPATH, "//li[contains(@class, 'menu-item') and contains(., '购物车')]" )

这里用了contains(., '购物车')来匹配文本内容,比写死完整文本要健壮得多,因为前后可能有多余空格。

CSS Selector的优势是简洁、性能好,而且浏览器原生支持,适合定位没有动态文本的静态结构元素。比如:

element = driver.find_element(By.CSS_SELECTOR, 'div.product-list > div.item:nth-child(2)')

关于XPath,我有三个独家经验:

  • 绝对路径不要写,比如/html/body/div[1]/div[2]/div[3]/...,页面结构一变就崩。用相对XPath,从有id或class的地方开始写。
  • 多用//而不是单斜杠/,因为单斜杠要求严格的父子关系,动态生成页面很容易导致层级变化。
  • 善用XPath轴,比如following-sibling::preceding-sibling::,在抓取表格数据时能解决不少棘手的定位问题。

3. 等待策略:告别time.sleep盲等

3.1 三种等待方式的对比与选择

很多新手写Selenium爬虫时,最喜欢用的就是time.sleep(5),直接睡5秒,管它页面加载完没有。这个方法的缺点很明显:如果页面2秒就加载完了,白白等3秒;如果遇到网络波动10秒才加载完,5秒不够用,照样报错。

Selenium官方提供了三种等待机制,我做了个对比表:

等待方式实现方式作用范围推荐场景
强制等待time.sleep(n)当前线程,全局几乎不推荐,极端场景备用
隐式等待driver.implicitly_wait(n)WebDriver实例全局定位元素时的兜底策略
显式等待WebDriverWait+Expected Conditions指定元素/条件动态加载、复杂交互场景

隐式等待的机制是:每次调用find_element时,如果元素没有立刻出现,WebDriver会在指定时间内轮询查找,直到超时。它在一定程度上缓解了加载慢的问题,但它不解决“元素已存在但不可用”的问题,也不解决“元素被遮挡无法点击”的问题。

显式等待则更精细,它让代码“等某个条件成立再继续执行”。最佳实践是:设一个合理的隐式等待(比如3秒)作为基础兜底,然后在需要的地方用显式等待精确控制关键步骤的时序。

3.2 显式等待的常用条件与自定义条件

expected_conditions(EC)模块里内置了很多常用条件,我最常用的有这几个:

# 等待元素出现(不一定可见) EC.presence_of_element_located((By.ID, 'id')) # 等待元素可见 EC.visibility_of_element_located((By.CLASS_NAME, 'class')) # 等待元素可点击(可见且启用) EC.element_to_be_clickable((By.XPATH, '//button')) # 等待某个元素消失(比如loading动画消失) EC.invisibility_of_element_located((By.ID, 'loading')) # 等待页面标题包含某关键词 EC.title_contains('关键词')

有时候内置条件不够用,还可以自定义等待条件。比如,我在抓取一个购物车的总价时,等了很久元素都出现了,但里面的文本一直没变,因为页面还在异步刷新价格。我写了一个自定义条件:

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class total_price_changed: def __init__(self, element_id, old_price): self.element_id = element_id self.old_price = old_price def __call__(self, driver): element = driver.find_element('id', self.element_id) current_price = element.text.strip() return current_price != self.old_price # 用法 WebDriverWait(driver, 10).until( total_price_changed('total-price', '¥0.00') )

这种自定义等待条件的思路非常实用,在遇到异步渲染、轮询刷新的场景时,比固定等待高效太多。

4. 动态网页与多窗口处理的实战技巧

4.1 iframe与多标签页的正确切换姿势

iframe(内联框架)是Web Scraping的经典拦路虎。元素明明就在HTML源码里,但find_element就是找不到。原因很简单:元素根本不在当前主文档里,而是在一个嵌在页面里的子文档中。

处理方式分三步:

# 1. 先切换到iframe iframe = driver.find_element(By.TAG_NAME, 'iframe') driver.switch_to.frame(iframe) # 2. 操作iframe里的元素 element = driver.find_element(By.ID, 'some-id') # 3. 操作完切回主文档 driver.switch_to.default_content()

switch_to.frame支持传入iframe的index、name或WebElement对象。我在调试时一般先把页面上所有的iframe列出来:

iframes = driver.find_elements(By.TAG_NAME, 'iframe') for i, frame in enumerate(iframes): print(i, frame.get_attribute('src'), frame.get_attribute('id'))

多标签页的切换也是高频需求。你点击一个链接后浏览器开了新标签页,但Selenium的driver还停留在旧页面。这时需要手动切换:

# 点击后等待新标签页出现 WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2)) # 获取所有窗口句柄 all_handles = driver.window_handles # 切换到最后新打开的标签页 driver.switch_to.window(all_handles[-1])

这里有个细节:window_handles的顺序不一定是打开时间顺序,某些浏览器扩展也可能会开自己的窗口。最稳妥的方式是,在打开新页面之前先记录当前窗口句柄,然后在新句柄集合里取差集,找到真正的新窗口。

4.2 处理JS弹出框与滚动加载页面

页面里的alert弹窗、confirm确认框、prompt输入框,都会阻塞脚本的执行。Selenium的switch_to.alert可以接管这些原生弹窗:

alert = driver.switch_to.alert print(alert.text) # 获取弹窗文本 alert.accept() # 点击确认 # alert.dismiss() # 点击取消

滚动加载是另一个高频场景。很多电商网站和社交平台采用“无限滚动”加载更多内容,你拉到底部才会加载新的数据。Selenium处理这种场景的通用思路是循环执行JavaScript向下滚动,同时判断内容是否增长:

import time last_height = driver.execute_script("return document.body.scrollHeight") while True: # 滚动到底部 driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 等待加载 time.sleep(2) new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_height

当然,这里的time.sleep可以换成显式等待,等待新加载出来元素的出现。

4.3 浏览器反检测的合规思路

做Web Scraping,不可避免会碰到网站的反爬机制。有一些网站会检测当前浏览器是否是自动化工具,检测点包括window.navigator.webdriver属性、浏览器指纹、请求头等。

网上流传很多“隐藏自动化特征”的方案,比如加上--disable-blink-features=AutomationControlled参数,或者用JavaScript覆盖navigator.webdriver属性。这些做法本质上是在对抗网站的访问控制策略,我不太推荐把它作为常规手段使用。做爬虫数据采集,底线是尊重目标网站的robots.txt规则,控制请求频率,不要对目标服务器造成压力,也不要采集涉及个人隐私或版权保护的数据。

如果只是个人学习、研究、测试自动化脚本,建议在本地搭建测试页面来练手,或者选择允许爬虫的公开数据集网站。这既安全又合规,也能达到学习目的。

5. 实操项目全流程:购物车页面自动化采集

5.1 从需求到脚本拆解

用一个综合案例把前面的知识点串起来。假设要写一个脚本,自动打开一个电商网站的购物车页面,获取商品列表(包含商品名、价格、数量),然后计算总价并截图存档。这个需求很典型,涵盖了元素定位、等待、数据提取、截图这几个核心环节。

整体流程我拆成四步:

  1. 打开购物车页面,等待页面完全加载;
  2. 定位商品列表容器,提取每个商品的名称、价格、数量;
  3. 定位总价元素,校验计算总价和页面显示总价是否一致;
  4. 保存结果到CSV,并截图留档。

第二步是核心,购物车页面通常是一个列表,每个商品项包含多个子元素。用CSS Selector定位所有商品项,然后对每一项做二次定位是最稳定的方式:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get('https://example.com/cart') # 等待商品项出现 WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, 'div.cart-item')) ) # 获取所有商品项 items = driver.find_elements(By.CSS_SELECTOR, 'div.cart-item') products = [] for item in items: try: name = item.find_element(By.CSS_SELECTOR, 'span.item-name').text price = item.find_element(By.CSS_SELECTOR, 'span.item-price').text qty = item.find_element(By.CSS_SELECTOR, 'input.item-qty').get_attribute('value') products.append({ 'name': name, 'price': price, 'qty': qty, }) except Exception as e: print(f'解析单个商品失败: {e}') continue # 打印结果 for product in products: print(product)

这里有个很重要的细节:我在内层循环加了try-except。因为页面结构不可能每个商品项都完全一致,万一某一行结构不同,不应该让整个抓取流程中断。这也是Web Scraping脚本健壮性的关键。

5.2 数据提取与保存时的注意事项

用Selenium拿到的文本数据往往是带各种格式的,直接存到文件里后续处理会很痛苦。所以提取阶段就要做好清洗:

  • 价格数据尽量用正则提取数字部分,去掉货币符号和空格;
  • 数量字段如果是input输入框,需要get_attribute('value')而不是.text
  • 文本前后有大量空白时,统一用.strip()

清洗之后保存为CSV,用Python标准库就能搞定:

import csv with open('cart_items.csv', 'w', newline='', encoding='utf-8-sig') as f: writer = csv.DictWriter(f, fieldnames=['name', 'price', 'qty']) writer.writeheader() writer.writerows(products)

最后别忘了截图存档。Selenium的截图功能在排查问题时特别有用,脚本跑完后存一张图,可以直观确认当时页面的真实状态:

driver.save_screenshot('final_cart.png')

5.3 脚本运行完一定记得释放资源

不管脚本成功还是失败,最后都要执行driver.quit(),关闭浏览器并清理临时进程。如果脚本中途崩溃导致driver没退出,后台会残留一堆chromedriver进程,占用内存,干扰下一次运行。

我习惯用try-finally或者with方式保证资源释放:

driver = webdriver.Chrome() try: # 业务逻辑 pass finally: driver.quit()

还有一种更优雅的方式是用上下文管理器。新版Selenium的WebDriver本身支持with语句,退出时自动关闭。这样写不仅简洁,也降低了忘记关闭浏览器的概率。

6. 常见问题与排查技巧实录

6.1 高频报错速查表

把我在实际抓取中遇到的高频报错和排查方向整理成了表格,方便你对照查询:

报错信息可能原因排查方向
NoSuchElementException元素定位器错误、页面未加载完成、元素在iframe中先打印页面源码,再检查等待条件,最后看是否需要切换iframe
StaleElementReferenceException操作时DOM发生刷新,旧元素引用失效每次重新获取元素,或用重试装饰器
SessionNotCreatedExceptiondriver版本与浏览器版本不匹配去下载对应主版本号的driver,或用webdriver-manager自动匹配
TimeoutException显式等待条件超时未满足确认条件表达式是否正确,页面是否确实出现了目标元素
ElementClickInterceptedException目标元素被其他元素遮挡,无法点击driver.execute_script("arguments[0].click();", element)强制点击,或先关闭遮挡弹层
UnexpectedAlertPresentException页面弹出JS弹窗阻塞了后续操作switch_to.alert.accept()处理弹窗
InvalidSelectorException定位器语法写错了检查XPath或CSS Selector语法,先在开发者工具里验证
WebDriverException: unknown error浏览器崩溃、参数配置错误、网络问题查看完整错误堆栈,检查浏览器启动参数

6.2 最强的三个调试技巧

第一招:把页面源码保存到本地。定位元素找不到、数据解析出错的时候,先执行driver.page_source,把HTML存成文件,再用编辑器搜索关键文本,看看元素是不是真的存在。这比反复猜测快得多。

with open('page_source.html', 'w', encoding='utf-8') as f: f.write(driver.page_source)

第二招:获取元素时打印所有候选元素列表。有时候你觉得某个class名是唯一的,但页面上可能有好几个。把find_elements的结果遍历打印出来,看看数量是不是跟预期一致:

elements = driver.find_elements(By.CSS_SELECTOR, 'div.card') print(f'找到 {len(elements)} 个元素') for el in elements: print(el.text[:50])

第三招:善用断点。在写复杂脚本时,可以在关键步骤加input("按回车继续...")或者time.sleep(3),手动检查浏览器当前状态。不过这只适用于调试阶段,正式运行时记得删掉。

6.3 反爬策略下的稳健收集心态

做Web Scraping,别指望一个脚本能通吃所有网站。每个网站的前端结构、加载策略、反爬机制都不同,大部分时间都在适配。我见过很多人因为连续被反爬拦截而崩溃,其实可以换个思路:

  • 把抓取目标拆小,先抓一个页面再扩展到全站;
  • 抓取频率要控制,不要并发太高,避免给对方服务器造成压力;
  • 遇到验证码、登录墙等强反爬,优先考虑官方API或者合规的数据服务商;
  • 数据到手后要校验完整性,不能只靠脚本跑一遍就完事。

合规是最重要的底线。爬虫本身是个工具,关键看怎么用。用来学习、研究、处理自己拥有访问权限的数据,完全合理;但用来批量抓取他人隐私数据、商业机密,或者对目标网站造成严重影响,绝对不行。

7. 从错误到成功:最后的实践经验

回到标题说的“从错误到成功”。我自己的真实历程是:第一天,连浏览器都打不开;第二天,学会了定位元素但被StaleElement整崩溃;第三天,终于能稳定抓取一个完整页面;一周后,已经能处理iframe、多标签页、滚动加载、反爬校验这些复杂场景。

这个过程里最重要的不是背代码,而是掌握一套排查问题的思路。遇到报错不要慌,先看报错信息,再对照页面实际状态,一步一步缩小范围。Selenium的报错信息其实写得很清楚,只是很多人习惯直接复制到搜索引擎,而不去仔细读它。

如果你现在正卡在某个报错上,我的建议是先关掉所有复杂的脚本,用最基础的三行代码重新开始,确认环境没问题之后,再逐步叠加功能。这个“最小可运行”的思路,能帮你快速定位是新加的功能出了问题,还是原来的基础就有隐患。

另外,多看看浏览器的开发者工具。Selenium爬虫本质上是“模拟人在浏览器里操作”,你手动操作时怎么查看元素、怎么点击、怎么等待加载,脚本里就怎么实现。把手动操作流程走一遍再写代码,成功率会高很多。

最后分享一个我自己写Selenium脚本的工作习惯:每完成一个小功能点就运行一次,而不是把所有代码写完再统一调试。这样做的好处是,出错时能立刻知道是哪一步的问题。一个人折腾了几天跑通一个抓取脚本的那种成就感,值得体验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 17:21:32

ShareX 5 分钟上手指南:截图、录屏、自动上传一次搞定

ShareX 5 分钟上手指南:截图、录屏、自动上传一次搞定 【免费下载链接】ShareX ShareX is a free and open-source application that enables users to capture or record any area of their screen with a single keystroke. It also supports uploading images, t…

作者头像 李华
网站建设 2026/9/9 17:20:31

USB转串口指纹模块实战:从驱动安装到指纹录入比对全流程

简介:这是一套面向开发者的指纹识别解决方案,定位在驱动层到应用层的完整衔接,特别适合需要集成指纹登录、考勤或门禁系统的B/S架构项目。压缩包共42个文件,包含7个dll驱动库、3个exe安装向导、4个pdf技术文档、5个js前端脚本以及…

作者头像 李华
网站建设 2026/9/9 17:19:59

STM32定时器TRGO触发ADC+DMA精准采样实战指南

简介:这套STM32F1平台的定时器触发ADC采集并自动DMA搬运方案,面向有一定嵌入式基础、希望提升数据采集实时性的开发者,解决定时采样、外设联动与CPU负载之间的矛盾。压缩包共6个文件,以3个C源文件和3个头文件组成,分别…

作者头像 李华
网站建设 2026/9/9 17:19:27

修复 ESP-IDF I2C 读取崩溃:FIFO 溢出与信号量竞态的三步补丁

修复 ESP-IDF I2C 读取崩溃:FIFO 溢出与信号量竞态的三步补丁 【免费下载链接】esp-idf Espressif IoT Development Framework. Official development framework for Espressif SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf E (4812) i2…

作者头像 李华
网站建设 2026/9/9 17:19:21

【JAVA课程设计/毕业设计】基于SpringBoot的农户种植作物信息服务管理系统的设计与实现 基于SpringBoot的作物品种与种植信息管理系统的设计与实现【附源码、数据库、万字文档】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华