Python Selenium自动化采集抖音评论:从原理到实战的完整指南
1. 项目概述与核心价值
最近在数据分析和市场调研的圈子里,讨论热度一直居高不下的一个话题就是:如何高效、合规地获取社交媒体平台上的公开数据。抖音,作为当下内容生态的绝对核心,其评论区里蕴藏着用户最真实的情感反馈、消费意向和舆论风向。无论是做品牌口碑监测、竞品分析,还是内容策略优化,这些评论数据都是第一手的宝贵原料。然而,手动复制粘贴的时代早已过去,面对动辄成千上万的评论,我们需要更聪明的工具。
这就是“Python Selenium实现抖音评论数据自动化采集”项目诞生的背景。它不是一个简单的脚本合集,而是一套完整的、模拟真人操作的解决方案。核心思路是利用Selenium这个浏览器自动化工具,驱动一个真实的浏览器(如Chrome)去访问抖音网页版,像真人一样滚动页面、点击“展开更多评论”、逐条抓取评论内容及其关联信息(如用户昵称、点赞数、发布时间等)。与直接调用API(往往有严格限制且变动频繁)或解析移动端数据包(技术门槛和风险较高)相比,Selenium模拟浏览器操作的方式,在应对反爬机制(如滑块验证、行为检测)时更具灵活性和鲁棒性,更贴近普通用户访问网页的路径,相对更“温和”。
这个项目适合谁呢?首先,是有一定Python基础,希望将自动化技能应用到实际数据获取场景的开发者或数据分析师。其次,是从事社交媒体运营、市场研究或学术调研,急需数据支撑但受限于手动采集效率的从业者。即使你是编程新手,只要跟着步骤一步步来,也能理解其核心逻辑并运行起来。接下来,我将拆解整个项目的设计思路、关键技术细节、完整实现步骤以及我踩过无数坑后总结的实战经验。
2. 整体设计与核心思路拆解
在动手写代码之前,我们必须想清楚整个自动化流程应该如何设计,以及为什么要选择Selenium而不是其他工具。一个健壮的采集系统,其设计必须兼顾效率、稳定性和对目标网站(抖音)的友好度。
2.1 为什么是Selenium + 网页版?
抖音的数据接口主要有移动端API和网页版( www.douyin.com )两条路径。移动端API加密复杂、更新频繁,且直接调用易触发风控。而网页版是为普通用户设计的,交互逻辑清晰。Selenium的核心价值在于它能完整模拟一个真实用户在浏览器中的所有操作:打开网页、登录(如果需要)、滚动、点击按钮、等待元素加载。这对于需要触发JavaScript才能加载更多内容的“无限滚动”页面(如抖音评论列表)来说是天然匹配的。
注意 :任何自动化采集行为都应严格遵守目标网站的
robots.txt协议和服务条款。本项目仅用于学习自动化技术和数据分析方法,所有操作应模拟人类正常浏览的间隔与频率,严禁用于大规模、高频次的恶意爬取,以免对目标服务器造成压力或导致自身账号/IP受限。
2.2 核心流程设计
一个完整的评论采集流程,可以抽象为以下几个核心环节,它们构成了我们代码的主干:
- 环境启动与初始化 :配置Selenium WebDriver,启动一个可控的浏览器实例。这里的关键是选择合适的浏览器驱动(如ChromeDriver)和设置合理的浏览器选项,以优化性能和模拟真人环境。
- 目标页面导航与加载 :使用WebDriver打开指定的抖音视频页面。难点在于如何处理页面初期的各种弹窗(如登录提示、青少年模式提示)以及确保视频下方的评论区域成功加载。
- 评论列表的展开与滚动 :抖音评论默认只加载一部分,需要模拟点击“展开更多评论”或不断向下滚动鼠标滚轮来触发加载更多。这是一个循环过程,需要判断何时评论已全部加载完毕或达到我们设定的采集数量上限。
- 评论元素的定位与解析 :当评论列表加载出来后,我们需要使用Selenium提供的定位方法(如XPath, CSS Selector)精确地找到每一条评论的HTML元素,然后从中提取出我们需要的数据字段。
- 数据清洗与持久化存储 :提取到的原始文本可能包含多余的空格、换行或表情符号(通常以
<img>标签或特殊class表示),需要进行清洗。最后,将结构化的数据保存到文件(如CSV、JSON)或数据库中。 - 异常处理与稳健性增强 :网络波动、元素加载超时、页面结构微调都会导致脚本中断。必须引入完善的异常处理(try-except)、显式等待(WebDriverWait)和重试机制,确保脚本能应对常见问题并继续运行或优雅退出。
2.3 工具选型与考量
- Python :作为胶水语言,在数据处理(pandas)、HTTP请求(requests)、自动化(Selenium)等领域有极其丰富的库生态,是此类任务的首选。
- Selenium :版本建议使用4.x及以上,其API更加现代和规范。它提供了对多种浏览器的支持。
- 浏览器与驱动 :首选Chrome/Chromium,因为其用户基数大,相应的反爬策略可能更“常规”。需要下载与本地Chrome浏览器版本匹配的
ChromeDriver。也可以使用无头模式(Headless)运行,节省资源,但某些情况下可能更容易被识别,初期调试建议先用有头模式。 - 解析库 :虽然Selenium可以获取元素文本,但结合
BeautifulSoup可以更方便地解析复杂的HTML结构。不过,对于此项目,Selenium自身的find_element和get_attribute方法基本够用。 - 等待策略 :这是Selenium爬虫稳定性的 生命线 。必须摒弃固定的
sleep,转而使用WebDriverWait配合expected_conditions进行显式等待,确保元素真正出现、可点击后再进行操作。
3. 核心细节解析与实操要点
理解了整体框架,我们深入每个环节,看看其中有哪些“魔鬼细节”和必须掌握的技巧。
3.1 环境配置的避坑指南
安装Selenium很简单: pip install selenium 。难点在于浏览器驱动的管理。
ChromeDriver的版本匹配 :这是新手最容易出错的地方。你必须确保下载的 ChromeDriver 版本号与电脑上已安装的Chrome浏览器主版本号完全一致。可以去Chrome的“关于”页面查看版本,然后到官方仓库或国内镜像站下载对应版本的驱动。
驱动路径的三种设置方法 :
- 将下载的
chromedriver.exe放在系统PATH环境变量包含的目录下(如Python的Scripts文件夹)。 - 在代码中指定绝对路径:
driver = webdriver.Chrome(executable_path=‘/path/to/chromedriver’)(Selenium 4中此参数已变更,建议使用Service对象)。 - (推荐)使用
webdriver-manager库自动管理驱动。安装pip install webdriver-manager,然后在代码中:
这种方式可以自动下载和匹配正确版本的驱动,省去手动管理的麻烦。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)
3.2 浏览器启动选项的优化设置
直接 webdriver.Chrome() 启动的浏览器有很多特征会被网站检测为自动化脚本。我们需要通过 Options 对象进行伪装和优化。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
# 常用优化选项
chrome_options.add_argument('--disable-blink-features=AutomationControlled') # 禁用自动化控制标志
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) # 移除“正受到自动测试软件控制”提示
chrome_options.add_experimental_option('useAutomationExtension', False) # 禁用自动化扩展
chrome_options.add_argument('--disable-gpu') # 部分环境下可避免一些问题
chrome_options.add_argument('--no-sandbox') # Linux环境下有时需要
chrome_options.add_argument('--disable-dev-shm-usage') # 解决共享内存问题
# 如果需要无头模式,取消下面这行的注释
# chrome_options.add_argument('--headless')
# 初始化驱动
driver = webdriver.Chrome(options=chrome_options)
实操心得 :在开发调试阶段,务必使用有头模式(不加
--headless),这样你能直观地看到浏览器的操作过程,便于定位问题。上线或批量运行时再考虑无头模式以提升性能。
3.3 等待的艺术:显式等待 vs. 隐式等待
Selenium的等待机制是稳定性的核心,理解其区别至关重要。
- 隐式等待(Implicit Wait) :
driver.implicitly_wait(10)设置一个全局的超时时间。在查找任何元素时,如果元素没有立即出现,WebDriver会轮询DOM最多10秒。它简单但不精确,对元素“可点击”、“可见”等条件无效。 - 显式等待(Explicit Wait) :针对某个特定元素和条件进行等待,是最推荐的方式。它使用
WebDriverWait和expected_conditions(EC)。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 等待最多20秒,直到ID为‘comment’的元素出现在DOM中
wait = WebDriverWait(driver, 20)
comment_section = wait.until(EC.presence_of_element_located((By.ID, “comment”)))
# 等待直到“展开更多评论”按钮可点击
more_button = wait.until(EC.element_to_be_clickable((By.XPATH, “//div[contains(text(), ‘展开更多评论’)]”)))
more_button.click()
关键技巧 :为不同的操作定义不同的等待时间。例如,页面初始加载可以等久一点(20秒),而点击按钮后的局部刷新可以等短一点(5秒)。合理设置超时时间能避免脚本无谓地长时间等待。
3.4 元素定位:XPath与CSS Selector的实战选择
抖音的网页结构复杂,class名可能随机生成。定位元素时,XPath因其强大的灵活性成为首选。
- 通过文本内容定位 :
//div[contains(text(), ‘点赞’)]定位包含“点赞”二字的div。 - 通过属性定位 :
//div[@class=‘comment-item’]或//div[contains(@class, ‘comment-list’)](部分匹配class)。 - 层级定位 :
//div[@id=‘root’]//div[@class=‘comment’]//p从根元素向下寻找。
如何获取XPath ?最实用的方法是使用浏览器开发者工具(F12)。在Elements标签页,找到目标元素,右键 -> Copy -> Copy full XPath。但注意,直接复制的XPath可能过长且脆弱(依赖绝对路径),最好能根据其关键特征(如特定的class片段、文本、邻近元素)编写相对简洁且稳定的XPath。
注意事项 :页面结构可能随时更新。今天能用的XPath,明天可能就失效了。因此,代码中最好将关键元素的定位表达式作为变量定义在文件开头,方便统一修改。同时,定位策略上应尽量选择那些相对稳定、语义化的属性,避免使用自动生成的长串随机字符class。
4. 实操过程与核心环节实现
现在,我们进入实战环节,用代码将上述思路串联起来。假设我们要采集一个特定抖音视频下的前500条评论。
4.1 初始化与页面访问
import time
import pandas as pd
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
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
def init_driver():
"""初始化并返回一个配置好的WebDriver实例"""
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(‘--disable-blink-features=AutomationControlled’)
chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”])
chrome_options.add_experimental_option(‘useAutomationExtension’, False)
# 可选:添加用户数据目录,避免每次登录(注意隐私)
# chrome_options.add_argument(r“--user-data-dir=C:\Users\YourName\AppData\Local\Google\Chrome\User Data”)
# chrome_options.add_argument(“--profile-directory=Default”)
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# 设置一个隐式等待作为兜底,但主要依靠显式等待
driver.implicitly_wait(5)
return driver
def get_video_page(driver, video_url):
"""导航到目标视频页面并等待核心内容加载"""
print(f“正在访问视频页面: {video_url}”)
driver.get(video_url)
# 等待页面主体加载,可以是一个标志性元素,比如视频播放器或标题
try:
# 这里以等待页面标题包含‘抖音’为例,实际需根据页面调整
WebDriverWait(driver, 15).until(EC.title_contains(‘抖音’))
# 关闭可能的初始弹窗(登录提示、青少年模式等)
close_popups(driver)
print(“页面加载成功,弹窗已处理。”)
except TimeoutException:
print(“页面加载超时,请检查网络或URL。”)
return False
return True
def close_popups(driver):
"""尝试关闭常见的初始弹窗"""
time.sleep(2) # 给弹窗一点出现的时间
popup_selectors = [
“button[class*=‘close’]”, # 通用关闭按钮
“.dy-account-close”, # 抖音登录弹窗关闭按钮(示例,需实际查看)
“//div[text()=‘我知道了’]”, # 青少年模式提示
]
for selector in popup_selectors:
try:
if selector.startswith(‘//’):
btn = driver.find_element(By.XPATH, selector)
else:
btn = driver.find_element(By.CSS_SELECTOR, selector)
btn.click()
time.sleep(0.5)
print(f“已关闭弹窗: {selector}”)
except:
pass
4.2 评论加载与滚动逻辑
这是整个脚本最核心也最易出问题的部分。抖音评论是动态加载的。
def load_all_comments(driver, max_comments=500):
"""循环展开或滚动,直到加载出足够数量的评论"""
print(“开始加载评论...”)
all_comments = []
last_height = driver.execute_script(“return document.documentElement.scrollHeight”)
attempts = 0
max_attempts = 20 # 防止无限滚动
while len(all_comments) < max_comments and attempts < max_attempts:
# 1. 先尝试找到并点击“展开更多评论”按钮(如果存在)
try:
# 注意:抖音的按钮文字和结构可能变化,这里是一个示例XPath
more_button = WebDriverWait(driver, 3).until(
EC.element_to_be_clickable((By.XPATH, “//div[contains(@class, ‘more’) and contains(text(), ‘展开’)]”))
)
more_button.click()
print(“点击了‘展开更多评论’。”)
time.sleep(1.5) # 等待新评论加载
except TimeoutException:
# 没有“展开”按钮,或已全部展开,则执行滚动
pass
# 2. 模拟滚动到页面底部,触发滚动加载
driver.execute_script(“window.scrollTo(0, document.documentElement.scrollHeight);”)
time.sleep(2) # 等待滚动后新内容加载,这个时间很关键,可根据网络调整
# 3. 检查是否有新内容加载
new_height = driver.execute_script(“return document.documentElement.scrollHeight”)
if new_height == last_height:
# 高度未变,可能已无更多内容,或需要其他交互
attempts += 1
print(f“滚动后高度未变化,尝试次数 {attempts}/{max_attempts}”)
# 可以尝试滚动到评论区域内的某个特定容器
# driver.execute_script(“document.querySelector(‘.comment-list’).scrollTop += 1000;”)
time.sleep(1)
else:
last_height = new_height
attempts = 0 # 重置尝试计数
# 4. 每次循环后都尝试抓取一次当前可见的评论
current_comments = extract_comments_from_page(driver)
new_count = len(current_comments) - len(all_comments)
if new_count > 0:
all_comments = current_comments # 更新列表
print(f“已加载 {len(all_comments)} 条评论。”)
else:
print(“本次滚动未发现新评论。”)
# 安全中断:如果连续几次滚动都没新评论,可能真的到底了
if attempts > 5:
print(“连续多次滚动未加载新评论,可能已加载全部评论。”)
break
return all_comments[:max_comments] # 返回不超过目标数量的评论
4.3 评论元素的定位与数据提取
我们需要从每条评论的HTML元素中提取出所需字段。这需要仔细分析抖音评论区的DOM结构。
def extract_comments_from_page(driver):
"""从当前页面中提取所有评论项的数据"""
comments_data = []
# 定位所有评论项的外层容器。这个选择器是关键,需要根据实际页面调整。
# 使用浏览器检查工具,找到包裹单条评论的重复出现的元素。
# 示例:可能是 div[class*='comment-item'] 或 section[class*='comment-list'] > div
comment_items = driver.find_elements(By.XPATH, “//div[contains(@class, ‘comment-item’)]”)
for item in comment_items:
try:
# 用户昵称
username_elem = item.find_element(By.XPATH, “.//span[contains(@class, ‘nickname’)]”)
username = username_elem.text.strip()
except NoSuchElementException:
username = “N/A”
try:
# 评论正文。注意,可能有多行或包含表情(表情可能是图片或特殊span)
content_elem = item.find_element(By.XPATH, “.//p[contains(@class, ‘content’)] | .//div[contains(@class, ‘content’)]”)
# 获取元素的innerText,它能更好地处理换行和隐藏元素
content = content_elem.get_attribute(‘innerText’).strip()
# 或者用 text 属性,但可能不包含隐藏部分
# content = content_elem.text.strip()
except NoSuchElementException:
content = “N/A”
try:
# 点赞数
like_elem = item.find_element(By.XPATH, “.//span[contains(@class, ‘like-count’)]”)
like_count = like_elem.text.strip()
# 处理“赞”字和数字,如“1.2w”
if ‘赞’ in like_count:
like_count = like_count.replace(‘赞’, ‘’).strip()
if ‘w’ in like_count.lower():
# 简单处理“万”,实际可做更精确的转换
like_count = str(float(like_count.lower().replace(‘w’, ‘’)) * 10000)
except NoSuchElementException:
like_count = “0”
try:
# 发布时间
time_elem = item.find_element(By.XPATH, “.//span[contains(@class, ‘publish-time’)]”)
publish_time = time_elem.text.strip()
except NoSuchElementException:
publish_time = “N/A”
# 可以继续提取用户ID、头像链接、回复数等
# user_id = item.get_attribute(‘data-user-id’) # 如果元素上有data属性
comments_data.append({
“username”: username,
“content”: content,
“like_count”: like_count,
“publish_time”: publish_time,
# “user_id”: user_id,
})
return comments_data
4.4 数据保存与主函数串联
最后,我们将数据保存为CSV文件,并编写主函数来串联整个流程。
def save_to_csv(data, filename=“douyin_comments.csv”):
"""将评论数据列表保存为CSV文件"""
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding=‘utf-8-sig’) # ‘utf-8-sig’解决Excel中文乱码
print(f“数据已保存至 {filename}, 共 {len(data)} 条记录。”)
def main():
# 目标视频URL
video_url = “https://www.douyin.com/video/xxxxxxxxxxxxxxxxx” # 请替换为实际视频URL
driver = None
try:
driver = init_driver()
if get_video_page(driver, video_url):
# 等待评论区域出现
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, “//div[contains(@class, ‘comment-list’)]”))
)
# 加载评论
comments = load_all_comments(driver, max_comments=200) # 目标200条
# 提取并保存
if comments:
save_to_csv(comments)
else:
print(“未采集到任何评论。”)
else:
print(“页面访问失败,程序退出。”)
except Exception as e:
print(f“程序运行过程中发生错误: {e}”)
import traceback
traceback.print_exc()
finally:
if driver:
print(“采集完成,关闭浏览器。”)
driver.quit()
if __name__ == “__main__”:
main()
5. 常见问题与排查技巧实录
即使代码逻辑正确,在实际运行中你依然会遇到各种各样的问题。下面是我在多次实战中总结的典型问题及其解决方案。
5.1 元素定位失败(NoSuchElementException)
这是最常见的问题。
-
原因1:页面未完全加载 。
- 排查 :在定位代码前增加显式等待(
WebDriverWait),确保目标元素已经出现在DOM中并且是可见、可交互的状态。 - 技巧 :使用
EC.presence_of_element_located(元素存在)和EC.visibility_of_element_located(元素可见)组合判断。有时元素存在但被隐藏,后者更可靠。
- 排查 :在定位代码前增加显式等待(
-
原因2:XPath/CSS Selector写错了或过时了 。
- 排查 :在浏览器的开发者工具Console中,使用
$x(‘你的XPath’)(XPath)或document.querySelectorAll(‘你的CSS选择器’)(CSS)测试你的定位表达式,看是否能找到元素。 - 技巧 :编写更健壮的定位器。避免使用绝对路径和包含长串随机字符的class。多用
contains()函数进行部分匹配,或利用元素的层级关系和相对稳定的属性(如data-*属性)。
- 排查 :在浏览器的开发者工具Console中,使用
-
原因3:元素在iframe或shadow DOM内 。
- 排查 :检查目标元素是否位于
<iframe>标签内。抖音的某些组件可能使用Shadow DOM。 - 解决 :对于iframe,需要使用
driver.switch_to.frame(frame_reference)切换到对应的frame后才能定位其中的元素。对于Shadow DOM,需要使用JavaScript穿透。
- 排查 :检查目标元素是否位于
5.2 页面结构频繁变动
抖音的前端代码更新是常态。
- 策略 :将核心元素的定位表达式(XPath、CSS Selector)作为配置变量放在代码开头,方便统一修改。
- 设计 :编写更通用的提取函数。例如,不依赖固定的class名,而是通过元素在评论项中的相对位置(如“第二个
<span>标签”)来提取信息,但这降低了可读性和精确性。更好的方法是定期维护和更新选择器。 - 监控 :可以设置一个简单的监控,当连续多次定位失败时,发送通知或记录日志,提示需要检查页面结构。
5.3 被检测为自动化脚本或触发验证码
- 现象 :页面跳出滑块验证码,或直接提示“操作过于频繁”。
- 预防 :
- 启用完整的浏览器特征 :如我们之前设置的
--disable-blink-features=AutomationControlled等选项。 - 模拟人类行为 :在操作之间加入随机延迟(
time.sleep(random.uniform(1, 3))),避免精确的定时操作。鼠标移动轨迹也可以模拟,但Selenium原生支持较弱,可考虑使用ActionChains进行简单模拟。 - 使用代理IP :如果采集量很大,可以考虑使用代理IP池来轮换IP地址,降低单个IP的请求频率。
- 降低采集频率 :这是最有效的方法。不要试图在几分钟内抓取数万条数据。设定合理的间隔,让脚本“慢”下来。
- 启用完整的浏览器特征 :如我们之前设置的
- 应对 :一旦出现验证码,简单的Selenium脚本很难自动破解。可以考虑:
- 暂停脚本,手动处理验证码后继续。
- 集成第三方打码平台API(涉及额外成本和服务)。
- 切换到需要登录的会话(已登录用户可能风控等级不同),但需妥善保管cookie。
5.4 无限滚动无法触发或卡住
- 检查点 :
- 滚动目标 :确保
scrollTo操作的目标是正确的。有时需要滚动评论列表内部的容器,而不是整个页面。使用document.querySelector(‘.comment-list’).scrollTop += 1000。 - 等待时间 :
time.sleep(2)中的2秒可能不够。网络慢时,需要延长等待时间,或者使用显式等待来检测新评论元素是否出现。 - 加载触发器 :除了滚动,是否有点击“加载更多”按钮的步骤?代码中是否遗漏?
- 页面高度判断逻辑 :
scrollHeight在内容动态加载时可能不准确。可以改为判断评论列表的children长度是否增加。
- 滚动目标 :确保
5.5 数据提取不完整或包含乱码
- 内容提取 :使用
.text属性有时会丢失动态加载或隐藏的内容。优先尝试.get_attribute(‘innerText’)或.get_attribute(‘textContent’)。 - 编码问题 :确保保存文件时使用
utf-8-sig编码,这是为了兼容Excel等软件。在Python中读取时,使用utf-8即可。 - 表情符号处理 :抖音评论中的表情通常是图片或特殊字体。
.text可能无法获取,得到的可能是空或占位符。如果需要精确获取,可能需要解析<img>标签的alt属性,但这非常复杂且易变。对于大多数文本分析场景,忽略或将其替换为[表情]是更实际的做法。
5.6 性能优化与资源管理
- 无头模式 :生产环境使用
--headless可以大幅减少内存和CPU占用。 - 关闭不必要的功能 :
--disable-images、--disable-javascript(但抖音严重依赖JS,不能禁用)可以加速,但可能影响页面正常功能。 - 及时退出 :务必在
finally块或异常处理中调用driver.quit(),确保浏览器进程被关闭,防止内存泄漏。 - 复用浏览器会话 :对于需要登录且登录状态重要的场景,可以使用
--user-data-dir指定用户数据目录,让Selenium复用已有的Chrome用户配置,避免每次重新登录。但要注意多实例冲突和隐私问题。
整个项目最考验人的不是编码,而是耐心调试和对网页交互逻辑的细致观察。每一个成功的自动化脚本背后,都是无数次与动态页面、反爬机制斗智斗勇的结果。建议从一个简单的视频页面开始,逐步增加功能(如处理登录、滚动、提取多字段),并使用 try...except 包裹每一个可能失败的步骤,并打印详细的日志,这样当脚本出错时,你能快速定位到问题发生的具体环节。
更多推荐
所有评论(0)