当前位置: 首页 > news >正文

Python+Selenium自动化登录豆瓣:从环境搭建到实战脚本

1. 项目概述:为什么选择Python+Selenium自动登录豆瓣?

如果你经常需要登录豆瓣去管理自己的豆列、标记看过的电影,或者做一些数据收集工作,手动操作不仅繁琐,还容易出错。这时候,一个能自动帮你完成登录的脚本就显得格外有用。我选择Python和Selenium这个组合来做这件事,原因很简单:Python语法简洁,生态丰富;Selenium则能完美模拟真人操作浏览器,绕过一些简单的反爬机制,对于需要处理JavaScript渲染的网页(比如豆瓣的登录表单)来说,是再合适不过的工具了。

这个项目看似基础,但麻雀虽小,五脏俱全。它涵盖了Web自动化测试和爬虫入门中的几个核心环节:环境搭建、元素定位、模拟交互以及异常处理。通过实现它,你不仅能学会如何让代码“替”你点击和输入,更能深入理解现代Web应用(尤其是带验证或动态加载的)的交互逻辑,为后续更复杂的自动化任务(如自动发帖、定时签到、数据抓取)打下坚实的基础。无论你是刚学完Python语法想找点实战项目的新手,还是需要为某个工作流程添加自动化环节的开发者,这个项目都能提供直接的参考价值。

2. 核心思路与工具选型解析

2.1 为什么是Selenium,而不是Requests?

很多初学者会问,既然只是登录,用requests库直接发POST请求不是更轻量、更快吗?理论上没错,但现实往往更复杂。豆瓣的登录页面可能包含动态生成的令牌(如csrf_token)、经过JavaScript加密的密码字段,或者需要先加载一些前端资源才能构造出正确的请求。直接使用requests模拟登录,需要你手动抓包、分析网络请求、逆向JS加密逻辑,这个过程门槛高且不稳定,一旦网站前端稍有改动,你的脚本就可能失效。

Selenium采取了另一种思路:它直接控制一个真实的浏览器(如Chrome)去加载页面、执行JavaScript,并允许你通过代码来操作这个浏览器里的元素。这意味着,只要你能在浏览器里手动完成登录,理论上就能用Selenium自动化这个流程。它看到的就是最终渲染后的页面,无需关心背后的JS执行细节,极大地降低了复杂度。当然,代价是速度较慢、资源占用更高。但对于登录这类低频、关键且需要处理前端逻辑的操作,Selenium的稳定性和易用性优势非常明显。

2.2 工具链详解:Python, Selenium WebDriver 与 浏览器

  1. Python (3.7+): 项目的编程语言核心。我们需要用它来编写控制逻辑。建议使用3.7及以上版本,以确保对各类库的良好支持。
  2. Selenium库: 通过pip install selenium安装。它是Python与浏览器驱动(WebDriver)通信的客户端库,提供了一系列API(如find_element,click,send_keys)来模拟用户行为。
  3. WebDriver: 这是连接Selenium代码和真实浏览器的桥梁。不同的浏览器需要不同的驱动:
    • Chrome: 需要ChromeDriver。版本必须与你安装的Chrome浏览器版本匹配,这是最常见的坑点之一。
    • Firefox: 需要geckodriver
    • Edge: 需要Microsoft Edge WebDriver。 我们将以Chrome为例,因为它用户基数大,问题也最容易找到解决方案。
  4. 浏览器 (Chrome): 被自动化的对象。确保你电脑上安装了较新版本的Chrome浏览器。

注意:有些教程会提到“无头模式”(Headless Mode),即不显示浏览器图形界面。这对于在服务器上运行非常有用。但在开发调试阶段,强烈建议使用有界面模式,这样你能直观地看到代码每一步的操作,便于定位元素和排查问题。

3. 环境准备与核心依赖安装

3.1 Python环境与Selenium库安装

首先,确保你的Python环境已经就绪。打开命令行(CMD或终端),输入python --versionpython3 --version检查版本。然后使用pip安装Selenium库:

pip install selenium

如果你在国内网络环境下觉得下载慢,可以使用清华镜像源加速:

pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple

3.2 ChromeDriver的下载与配置

这是最关键也最容易出错的一步。

  1. 查看Chrome版本:打开Chrome浏览器,点击右上角三个点 -> 帮助 -> 关于Google Chrome。记下版本号(例如:124.0.6367.91)。
  2. 下载匹配的ChromeDriver
    • 访问ChromeDriver官方下载站或国内镜像站。
    • 根据你的Chrome主版本号(例如124),找到对应的ChromeDriver版本下载。如果找不到完全一致的,选择版本号最接近且不超过Chrome版本的驱动。
    • 根据你的操作系统(Windows, macOS, Linux)下载对应的压缩包。
  3. 配置ChromeDriver
    • 方法一(推荐,方便管理):将下载的chromedriver.exe(Windows)或chromedriver(macOS/Linux)文件,直接放到你Python脚本所在的项目目录下。这样在代码中指定路径为./chromedriver即可。
    • 方法二(一劳永逸):将chromedriver文件放置到系统环境变量PATH包含的目录中,例如/usr/local/bin(macOS/Linux)或C:\Windows\(Windows)。这样在代码中只需指定chromedriver,系统会自动查找。

实操心得:我强烈推荐方法一。因为不同项目可能需要不同版本的驱动,放在项目目录内可以做到环境隔离。当Chrome自动升级后,你只需要替换项目目录下的这一个驱动文件即可,不影响其他项目。

3.3 验证安装是否成功

创建一个简单的测试脚本test_env.py

from selenium import webdriver from selenium.webdriver.chrome.service import Service # 指定chromedriver的路径(如果放在项目目录下) driver_path = './chromedriver' # 或 'chromedriver'(如果已加入PATH) # 创建Service对象(新版本Selenium推荐方式) service = Service(executable_path=driver_path) # 创建浏览器驱动实例 driver = webdriver.Chrome(service=service) # 尝试打开百度 driver.get("https://www.baidu.com") print(driver.title) # 应该打印出“百度一下,你就知道” # 等待几秒查看效果 import time time.sleep(3) # 关闭浏览器 driver.quit() print("环境测试成功!")

运行这个脚本,如果能看到Chrome浏览器自动打开并访问百度,然后关闭,且命令行输出标题和成功信息,说明你的Selenium环境已经配置正确。

4. 豆瓣登录页面分析与元素定位策略

4.1 手动分析登录流程

在写代码之前,我们必须像侦探一样,先手动走一遍登录流程,观察关键节点。

  1. 打开豆瓣登录页(https://www.douban.com),你会发现它默认展示的是“密码登录”标签页。有时也可能直接是二维码登录,我们需要点击切换到密码登录。
  2. 输入框和按钮不是简单的<input>,它们通常被包裹在多层<div>中,并且有动态的classid。我们不能凭感觉猜,必须借助浏览器开发者工具。

4.2 使用开发者工具定位元素

以Chrome为例:

  1. 在登录页面,按F12打开开发者工具。
  2. 点击左上角的箭头图标(或按Ctrl+Shift+C),然后将鼠标移动到“账户”输入框上并点击。
  3. 开发者工具的“Elements”面板会自动定位到对应的HTML代码。你会发现这个输入框的HTML可能长这样:
    <input type="text" name="form_email" placeholder="邮箱/手机号/用户名" class="account-form-input" ...>
    或者它的id可能是“email”
  4. 我们需要找到能唯一、稳定标识这个元素的属性。id是最佳选择,因为它通常是唯一的。如果没有id,那么name属性(如form_email)也是一个很好的选择。如果都没有,我们可能需要使用class配合其他定位方式,但class可能被多个元素共享,且容易随前端样式更新而改变。

4.3 Selenium的八大元素定位方法

Selenium提供了多种定位元素的方法,对应到driver.find_element(By.XXX, “value”)

  1. By.ID: 通过元素的id属性定位。driver.find_element(By.ID, “email”)优先级最高,因为ID通常唯一。
  2. By.NAME: 通过元素的name属性定位。driver.find_element(By.NAME, “form_email”)
  3. By.CLASS_NAME: 通过元素的class属性定位。driver.find_element(By.CLASS_NAME, “account-form-input”)。注意:如果class有多个值(如class=“form-input error”),需要传入完整的字符串或其中一个唯一的值。
  4. By.TAG_NAME: 通过标签名定位,如input,button。通常不单独使用,因为页面同类标签太多。
  5. By.LINK_TEXT / By.PARTIAL_LINK_TEXT: 通过超链接的完整文本或部分文本来定位链接(<a>标签)。
  6. By.CSS_SELECTOR: 通过CSS选择器定位。功能强大且灵活,是除了ID和NAME之外最常用的方法。例如,定位nameform_email的输入框:driver.find_element(By.CSS_SELECTOR, “input[name=‘form_email’]”)
  7. By.XPATH: 通过XML路径语言定位。功能最强大,几乎可以定位任何元素,但表达式可能较复杂。例如:driver.find_element(By.XPATH, “//input[@placeholder=‘邮箱/手机号/用户名’]”)

注意事项:豆瓣这类网站的前端结构可能会调整。今天能用的idclass,明天可能就变了。因此,在编写定位代码时,优先选择IDNAME,其次考虑结构相对稳定的CSS_SELECTORXPATH。避免使用那些看起来是随机生成或与样式强相关的class名。

5. 完整自动化登录脚本实现与逐行解析

下面,我们将构建一个健壮的自动登录脚本,并加入详细的注释和错误处理。

5.1 基础版本脚本

import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class DoubanAutoLogin: def __init__(self, username, password): """ 初始化,设置用户名、密码,并启动浏览器。 """ self.username = username self.password = password # 1. 设置ChromeDriver路径 self.driver_path = './chromedriver' # 根据你的实际位置修改 # 2. 创建Service对象 self.service = Service(executable_path=self.driver_path) # 3. 配置浏览器选项(可选) self.options = webdriver.ChromeOptions() # 如果想以无头模式运行(不显示浏览器界面),取消下面这行的注释 # self.options.add_argument('--headless') # 4. 实例化浏览器驱动 self.driver = webdriver.Chrome(service=self.service, options=self.options) # 设置隐式等待(全局等待元素出现的最大时间) self.driver.implicitly_wait(10) def login(self): """ 执行登录的主流程。 """ try: # 步骤1:打开豆瓣首页 print("正在打开豆瓣首页...") self.driver.get("https://www.douban.com") time.sleep(2) # 等待页面加载,可替换为显式等待 # 步骤2:检查并切换到密码登录标签(如果当前不是) # 豆瓣有时默认是二维码登录,我们需要找到“密码登录”标签并点击 try: # 尝试通过链接文本定位“密码登录”标签 password_login_tab = self.driver.find_element(By.LINK_TEXT, "密码登录") password_login_tab.click() print("已切换到密码登录模式。") time.sleep(1) except NoSuchElementException: # 如果没找到这个链接,可能当前已经是密码登录模式,继续即可 print("当前已在密码登录页面或元素定位方式已变,请检查。") # 这里可以加入更复杂的判断或使用其他定位方式 pass # 步骤3:定位用户名/邮箱输入框并输入 # 使用多种定位策略,提高容错率 print("正在定位用户名输入框...") # 先尝试用NAME定位 try: username_input = self.driver.find_element(By.NAME, "form_email") except NoSuchElementException: # 如果NAME不行,尝试用ID或CSS_SELECTOR # 你需要根据当前页面实际情况调整选择器 username_input = self.driver.find_element(By.CSS_SELECTOR, "input[type='text']") username_input.clear() # 清空可能存在的默认值 username_input.send_keys(self.username) print("用户名输入完成。") # 步骤4:定位密码输入框并输入 print("正在定位密码输入框...") try: password_input = self.driver.find_element(By.NAME, "form_password") except NoSuchElementException: password_input = self.driver.find_element(By.CSS_SELECTOR, "input[type='password']") password_input.clear() password_input.send_keys(self.password) print("密码输入完成。") # 步骤5:定位并点击登录按钮 print("正在定位登录按钮...") # 登录按钮可能是一个input[type='submit']或者一个button try: login_button = self.driver.find_element(By.CSS_SELECTOR, "input[type='submit']") except NoSuchElementException: login_button = self.driver.find_element(By.XPATH, "//button[contains(text(), '登录豆瓣')]") login_button.click() print("已点击登录按钮。") # 步骤6:等待登录完成,并进行验证 # 使用显式等待,等待某个登录成功后的元素出现,例如用户头像或“我的豆瓣”链接 print("等待登录结果...") wait = WebDriverWait(self.driver, 10) # 最多等10秒 try: # 假设登录成功后,页面会出现一个包含用户名的元素,例如“我的豆瓣” success_element = wait.until( EC.presence_of_element_located((By.LINK_TEXT, "我的豆瓣")) ) print(f"登录成功!当前页面标题:{self.driver.title}") # 可以进一步操作,比如跳转到个人主页 # success_element.click() # time.sleep(3) except TimeoutException: # 如果等待超时,可能是登录失败 print("登录可能失败,或成功后的页面元素未找到。") # 可以在这里截图保存当前页面,便于排查 self.driver.save_screenshot('login_failed.png') print("已保存错误截图:login_failed.png") # 步骤7:登录成功后,保持会话一段时间,方便观察或进行后续操作 time.sleep(5) except Exception as e: # 捕获其他未知异常 print(f"登录过程中发生未知错误:{e}") self.driver.save_screenshot('error_snapshot.png') finally: # 步骤8:无论成功与否,最后都关闭浏览器(根据需求调整) # 如果后续还有操作,可以注释掉 quit() input("按回车键结束程序并关闭浏览器...") self.driver.quit() print("浏览器已关闭。") if __name__ == "__main__": # !!! 重要:请在此处替换为你自己的豆瓣账号和密码 !!! USERNAME = "your_email@example.com" PASSWORD = "your_password" login_bot = DoubanAutoLogin(USERNAME, PASSWORD) login_bot.login()

5.2 关键代码段解析与优化建议

  1. 使用类封装:将登录逻辑封装在DoubanAutoLogin类中,使代码结构更清晰,易于维护和扩展(例如后续可以添加自动发广播、标记电影等方法)。
  2. 异常处理:使用try...except块捕获NoSuchElementException(元素未找到)和TimeoutException(等待超时)。这是自动化脚本健壮性的关键。一旦定位失败,脚本不会直接崩溃,而是可以执行备用方案或记录错误。
  3. 等待策略
    • 隐式等待driver.implicitly_wait(10)设置了一个全局等待时间。在查找任何元素时,如果元素没有立即出现,WebDriver会轮询查找,最多等10秒。这避免了因网络或JS加载慢导致的“元素未找到”错误。
    • 显式等待WebDriverWait配合expected_conditions是更精确的等待方式。我们用它来等待“登录成功”的特定元素出现。这比单纯用time.sleep(固定秒数)更高效、更可靠。
  4. 多定位策略备用:对于关键元素(如输入框),我们提供了备用的定位方式(先NAME,后CSS_SELECTOR)。这能有效应对网站前端微调。
  5. 截图功能:在登录失败或发生异常时,使用driver.save_screenshot()保存当前页面截图。这是极其重要的调试手段,你可以直观地看到脚本执行到哪一步时页面状态不对。

6. 进阶技巧与稳定性提升方案

基础脚本能跑通,但要用于实际环境,尤其是长时间或周期性运行,还需要考虑更多。

6.1 处理验证码与人机验证

这是自动登录最大的挑战。豆瓣在检测到异常登录行为(如高频请求、异地IP)时,可能会触发验证码(图形、滑块、点选等)或更复杂的验证。

  • 策略一:规避触发
    • 控制频率:不要过于频繁地执行登录操作。在脚本中加入随机延时(time.sleep(random.uniform(1, 3)))模拟真人操作间隔。
    • 使用Cookie:首次手动登录成功后,通过driver.get_cookies()获取并保存Cookie到文件。下次运行时,先加载Cookie (driver.add_cookie()),然后直接访问需要登录的页面(如“我的豆瓣”),如果Cookie未过期,则无需再次走登录流程。这能最大程度避免触发验证。
    # 保存Cookie示例 import pickle cookies = driver.get_cookies() with open('douban_cookies.pkl', 'wb') as f: pickle.dump(cookies, f) # 加载Cookie示例 driver.get("https://www.douban.com") # 先访问域名 with open('douban_cookies.pkl', 'rb') as f: cookies = pickle.load(f) for cookie in cookies: driver.add_cookie(cookie) driver.refresh() # 刷新页面使Cookie生效
  • 策略二:人工干预。如果只是偶尔运行,可以在脚本中检测到验证码出现时(例如,通过判断特定图片或文字是否存在),暂停程序,提示用户手动输入,然后继续。
  • 策略三:接入打码平台(成本较高)。对于图形验证码,可以截图后调用第三方打码平台的API进行识别。但这涉及额外费用和网络请求,且不一定能保证100%成功率。

6.2 使用用户数据目录保持登录状态

每次启动Selenium都会打开一个全新的、无痕的浏览器会话。我们可以通过指定Chrome的用户数据目录(User Data Directory),让Selenium使用一个已经登录过的浏览器配置文件,从而实现“免登录”。

from selenium.webdriver.chrome.options import Options options = Options() # 指定你电脑上Chrome用户数据目录的路径 # Windows通常类似:C:\\Users\\YourName\\AppData\\Local\\Google\\Chrome\\User Data # macOS: /Users/YourName/Library/Application Support/Google/Chrome/ # Linux: /home/YourName/.config/google-chrome/ options.add_argument(r"user-data-dir=C:\\Users\\YourName\\AppData\\Local\\Google\\Chrome\\User Data") # 指定使用哪个配置文件(‘Default’是默认配置文件) options.add_argument('profile-directory=Default') driver = webdriver.Chrome(service=service, options=options)

重要警告:使用此方法前,请先手动用Chrome(关闭所有窗口)在该配置文件下登录一次豆瓣。并且,不要同时运行多个使用同一用户数据目录的Chrome实例,否则会导致数据损坏。

6.3 应对动态加载与iframe

有些网站的登录框可能嵌套在<iframe>(内联框架)里。Selenium默认操作的是主页面,需要先切换到对应的iframe才能定位里面的元素。

# 假设登录框在一个iframe里,且该iframe有id='loginIframe' driver.switch_to.frame('loginIframe') # 现在可以定位iframe内的元素了 driver.find_element(By.NAME, "username").send_keys("test") # 操作完成后,切换回主页面 driver.switch_to.default_content()

豆瓣目前的登录页似乎没有使用iframe,但掌握这个方法对处理其他网站非常有用。

7. 常见问题排查与实战调试技巧

即使代码看起来没问题,运行时也可能遇到各种“坑”。下面是我在实践中总结的一些常见问题及其解决方法。

7.1 ChromeDriver版本不匹配

症状:启动时报错,提示“This version of ChromeDriver only supports Chrome version XX”或“session not created”。

解决:这是最常见的问题。严格按照前文所述,检查并下载与你的Chrome浏览器主版本号匹配的ChromeDriver。如果Chrome自动更新了,记得同步更新项目目录下的驱动文件。

7.2 元素定位失败(NoSuchElementException)

症状:脚本报错selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element

排查步骤

  1. 检查页面是否加载完成:在find_element前加入等待(显式或隐式)。可能是网络慢,元素还没渲染出来。
  2. 检查定位表达式:在浏览器开发者工具的Console里,用JavaScript测试你的定位表达式是否有效。例如,对于CSS选择器,可以输入document.querySelector(“input[name=‘form_email’]”)看是否能返回元素。
  3. 检查元素是否在iframe内:如果是,需要先switch_to.frame
  4. 检查元素属性是否动态变化:有些网站的idclass是随机生成的。这时需要寻找更稳定的父级元素,或使用包含部分文本的XPath(如//input[contains(@placeholder, ‘邮箱’)])。
  5. 查看页面结构是否已更新:网站改版了。你需要重新用开发者工具分析新的页面结构,更新定位代码。

7.3 脚本执行太快,被检测为机器人

症状:登录失败,跳转到验证码页面,或者直接提示“操作过于频繁”。

解决

  • 增加随机延迟:在关键操作(如点击、输入)之间插入随机等待时间。
    import random def random_sleep(min_t=1, max_t=3): time.sleep(random.uniform(min_t, max_t)) username_input.send_keys(username) random_sleep(0.5, 1.5) # 输入用户名后随机等待0.5到1.5秒 password_input.send_keys(password) random_sleep(0.5, 1.5) login_button.click()
  • 模拟真人输入:使用send_keys逐个字符输入,而不是一次性输入整个字符串(虽然Selenium本身是一次性发送,但可以拆分成循环)。
  • 使用更“人类”的行为模式:比如在点击前,先让鼠标移动到元素上(ActionChains)。

7.4 登录成功后,后续操作无法保持登录状态

症状:登录脚本显示成功,但紧接着访问其他需要登录的页面(如“我的豆瓣”)时,发现又回到了未登录状态。

原因与解决:这通常是因为每次driver.get()或打开新标签页,都被视为一次新的会话。但实际上,只要不调用driver.quit(),Cookie在同一个driver实例的各个页面间是共享的。确保你的后续操作都在同一个driver实例下进行,而不是重新创建了一个新的driver

7.5 高效调试:活用截图与日志

当脚本在后台或无头模式下运行时,你看不到浏览器界面。此时,截图打印日志是你的眼睛。

  • 在关键步骤后截图:如登录前、输入后、点击后、异常捕获时。
    self.driver.save_screenshot(f'step_{step_count}.png')
  • 详细打印日志:使用printlogging模块输出当前执行到的步骤、定位到的元素信息等。
    print(f"[INFO] 尝试定位元素,使用选择器: {locator}") element = driver.find_element(By.XPATH, locator) print(f"[INFO] 元素定位成功,文本内容: {element.text[:50]}...")

把这些技巧融入到你的脚本中,就能构建出一个不仅能用,而且足够健壮、易于维护的自动化登录工具。从豆瓣登录这个小项目出发,你可以将这套方法迁移到几乎所有需要交互的Web自动化场景中。

http://www.jsqmd.com/news/1188329/

相关文章:

  • JOJO飙马野郎动画化技术与Netflix流媒体实践解析
  • 危险化学品车辆检测数据集,用于目标检测训练 如何使用YOLO模型深度学习训练危险化学品车辆检测数据集
  • Skidfuscator vs Zelix KlassMaster:开源Java混淆器性能对比分析
  • Qwen2-7B_rai_1.7.1_hybrid量化策略详解:AWQ/Group 128/Asymmetric/BFP16激活的完整教程
  • 3步打造专业内容:开源AI写作工具的终极效率革命
  • EM3080-W条形码解码器与PIC32的工业应用解析
  • Astro Theme Pure部署指南:Vercel、Netlify和GitHub Pages对比
  • Comfy引擎ECS使用指南:hecs集成与实体管理技巧
  • C++ CUDA开发实战:从环境搭建到向量加法性能优化
  • 终极指南:3步掌握Sol macOS启动器的核心开发与扩展
  • Chain33跨链技术实现:原子交换与跨链通信的解决方案
  • Velero数据移动架构深度解析:Kubernetes备份恢复的技术演进与实践
  • 2026故宫长城带娃游导游怎么选?3位讲解型向导实测与亲子路线解析 - 随峰国旅
  • 【Springboot毕设全套源码+文档】基于springboot深圳市体育中心体育赛事管理系统的设计与实现(丰富项目+远程调试+讲解+定制)
  • Copilot使用统计报告(内部白皮书级首发):92%的开发者未用对这4个隐藏统计维度
  • VB6与VC++混合编程实战:DLL交互与字符串处理详解
  • MusicFree歌词系统终极实战指南:智能多源聚合与高级配置完全解析
  • 如何用Snap Hutao原神工具箱提升你的游戏体验:7个实用技巧
  • 终端命令生成进入“可信时代”:OpenAI官方未公布的CLI插件协议解析,配合本地LLM实现离线、审计、回滚三重保障
  • 2026惠州黄金回收避坑指南正规门店实测推荐 - 余生黄金回收
  • GeoDNS 智能解析验证:各地解析到不同 IP 的测速方法
  • 会议笔记与行动项处理流程
  • 比亚迪与CK社交媒体互动:品牌营销策略与国际化启示
  • Vision Transformer (ViT) 如何超越CNN:全局注意力与跨模态优势解析
  • PaintingLight与其他光照工具对比:优势、局限性与适用场景
  • 2026浙江公考机构深度测评:浙江粉笔综合实力领跑,本土化教研与AI赋能构筑差异化优势 - 天涯视角
  • 论文AI率超标?7天紧急降重与答辩应对指南
  • 南宁市场监管部门提醒:出手黄金前先做5件事—查资质、比报价、认无损、看大盘、留凭证 - 奢侈品回收中心
  • 构建全链路SQL注入防御体系:从参数化查询到纵深防护
  • 粉笔模考大赛数据分析:55万人同场竞技,你的水平在哪?