讲师中心 微信公众号
AI工具推荐 视频效率加速

Python动态网页库存监控与Discord通知:Selenium实战指南

风丽同学_5923

风丽同学_5923

发布时间:2025-11-24 13:55:31

|

504人浏览过

|

来源于php中文网

原创

Python动态网页库存监控与Discord通知:Selenium实战指南

本教程详细探讨了使用python监控动态加载网页商品库存并发送discord通知的方法。针对传统网络爬虫(如beautifulsoup)在处理javascript渲染内容时的局限性,文章重点介绍了如何利用selenium这一无头浏览器工具来模拟用户行为,有效获取实时库存信息。教程涵盖了环境配置、代码实现、异步集成及最佳实践,旨在帮助读者构建健壮的库存监控系统。

1. 理解动态网页与传统爬虫的局限性

在构建库存监控系统时,我们通常会想到使用Python的requests库获取网页内容,再结合BeautifulSoup进行解析。然而,这种方法对于现代网站而言存在一个显著的局限性:许多网站的内容,尤其是商品库存状态,是通过JavaScript在浏览器端动态加载的。

问题分析: 当我们尝试使用requests.get(url)获取网页内容时,BeautifulSoup只能解析服务器返回的原始HTML源代码。如果商品的库存信息(例如特定尺码是否可选)是在页面加载后由JavaScript异步请求并渲染到DOM中的,那么这些信息将不会出现在requests获取的初始HTML中。

例如,在示例网站(courir.com)上,尺码选项的可用性(如尺码40)并不是直接嵌入在初始HTML中的。通过浏览器开发者工具检查,你会发现:

  • 当某个尺码缺货时,其对应的<li>元素可能带有unselectable类或aria-disabled="true"属性。
  • 当尺码有货时,其<li>元素可能带有selectable类,并且对应的链接(<a>标签)是可点击的。

然而,这些状态的切换和元素的出现,都是在JavaScript执行之后才发生的。因此,单纯依赖soup.find('li', {'class': 'unselectable'})这样的代码,可能无法准确判断尺码40的实时库存状态,因为它无法“看到”JavaScript渲染后的页面。

2. 解决方案:使用Selenium处理动态内容

为了解决BeautifulSoup无法处理动态加载内容的问题,我们需要一个能够模拟真实浏览器行为的工具,即无头浏览器(Headless Browser)。Selenium就是这样一个强大的工具,它允许我们通过编程方式控制浏览器,执行JavaScript,等待元素加载,甚至模拟点击等用户交互。

立即学习“Python免费学习笔记(深入)”;

html-to-pptx-converter
html-to-pptx-converter

使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。

下载

Selenium工作原理: Selenium启动一个真实的浏览器实例(可以是可见的,也可以是无头的),然后通过WebDriver与该浏览器进行通信。这意味着Selenium能够“看到”并操作JavaScript渲染后的完整DOM。

2.1 环境准备

在使用Selenium之前,需要安装相应的库和浏览器驱动:

  1. 安装Selenium库:
    pip install selenium
  2. 安装浏览器驱动: Selenium需要一个与你本地浏览器版本匹配的WebDriver。常用的有:
    • ChromeDriver (for Chrome): 从 ChromeDriver Downloads 下载。
    • GeckoDriver (for Firefox): 从 GeckoDriver Releases 下载。 将下载的驱动文件(例如chromedriver.exe)放到系统的PATH环境变量中,或者在代码中指定其路径。

2.2 构建Selenium库存检查函数

我们将重构check_stock函数,使其使用Selenium来加载页面并检查尺码40的库存状态。为了与原有的asyncio框架兼容,我们将Selenium的同步操作封装在一个异步函数中,并使用loop.run_in_executor在单独的线程中执行。

import asyncio
import aiohttp
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
import time # For sleep in the main loop

# Discord Webhook URL (替换为你的实际URL)
webhook_url = 'YOUR_DISCORD_WEBHOOK_URL'

async def send_webhook_message(content):
    """
    异步发送Discord Webhook消息。
    """
    async with aiohttp.ClientSession() as session:
        try:
            async with session.post(webhook_url, json={"content": content}) as response:
                if response.status == 204:
                    print("Discord message sent successfully.")
                else:
                    print(f"Error sending Discord message. Status code: {response.status}, Response: {await response.text()}")
        except aiohttp.ClientError as e:
            print(f"Error sending Discord message: {e}")

def _check_stock_selenium_sync(url, size_to_check):
    """
    同步的Selenium函数,用于检查特定尺码的库存。
    此函数将在单独的线程中运行。
    """
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')  # 无头模式,浏览器不在UI中显示
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument('--disable-gpu') # 禁用GPU硬件加速,在某些环境下可能需要
    options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124124 Safari/537.36') # 设置User-Agent

    # 确保 ChromeDriver 路径正确,如果不在PATH中,请指定 executable_path
    driver = webdriver.Chrome(options=options) 

    is_in_stock = False
    try:
        driver.get(url)
        print(f"Navigated to {url}")

        # 等待尺码选择器元素加载。根据网站结构,这些通常是带有特定类名的<li>或<a>元素
        # 这里我们等待所有包含 'swatchanchor' 类的链接元素出现
        WebDriverWait(driver, 15).until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, "a.swatchanchor"))
        )
        print("Size swatches loaded.")

        # 查找特定尺码(例如“40”)并且是可选择的元素
        # 网站结构通常是:<li class="selectable"><a class="swatchanchor" title="40">...</a></li>
        # 我们可以通过CSS选择器或XPath来定位

        # CSS Selector: 查找父级为 'selectable' 的 'li' 元素,其子级 'a.swatchanchor' 的 title 属性包含 '40'
        # 或者更直接地查找所有 'swatchanchor',然后过滤

        # 尝试查找所有尺码选项
        size_elements = driver.find_elements(By.CSS_SELECTOR, "a.swatchanchor")

        for element in size_elements:
            title = element.get_attribute('title')
            parent_li_class = element.find_element(By.XPATH, "./..").get_attribute('class') # 获取父级<li>的class

            if size_to_check in title:
                print(f"Found size element for {size_to_check} with title: '{title}' and parent class: '{parent_li_class}'")
                if 'selectable' in parent_li_class:
                    is_in_stock = True
                    break
                else:
                    print(f"Size {size_to_check} found but is not selectable (class: {parent_li_class}).")

        if is_in_stock:
            print(f"尺码 {size_to_check} 有货。")
        else:
            print(f"尺码 {size_to_check} 缺货或未找到。")

    except TimeoutException:
        print(f"Timeout waiting for page elements on {url}. Page might not have loaded correctly or elements not found within time.")
    except NoSuchElementException:
        print(f"Specific elements (like parent li) not found for {size_to_check}.")
    except Exception as e:
        print(f"An unexpected error occurred during Selenium operation: {e}")
    finally:
        driver.quit() # 确保关闭浏览器实例
    return is_in_stock

async def check_stock(url, size):
    """
    异步包装器,在单独的线程中运行同步的Selenium库存检查。
    """
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, _check_stock_selenium_sync, url, size)

2.3 整合Discord通知与异步循环

现在,我们将Selenium的库存检查功能集成到原有的异步主循环中,以便定期检查并发送Discord通知。

product_data = [
    {'url': 'https://www.courir.com/fr/p/ugg-tasman-1499533.html', 'size': '40'}, # 目标尺码改为 '40'
]

async def main():
    previous_stock_status = {}  # 存储每个产品的上一次库存状态

    while True:
        result_message = ""

        for product_info in product_data:
            url = product_info['url']
            size = product_info['size']

            print(f"\nChecking stock for {url} (size: {size})...")
            current_stock_status = await check_stock(url, size) # 调用异步的Selenium检查函数

            # 获取上一次的库存状态,如果不存在则默认为False(缺货)
            last_status = previous_stock_status.get(url, {}).get(size, False)

            # 检查:如果之前缺货(last_status为False)且现在有货(current_stock_status为True)
            if not last_status and current_stock_status:
                message = f"? {url} - 尺码 {size} 现在有货啦!"
                result_message += message + "\n"
                print(message)
            elif last_status and not current_stock_status:
                # 如果之前有货,现在缺货,也可以选择发送通知
                message = f"⚠️ {url} - 尺码 {size} 已经售罄。"
                # result_message += message + "\n" # 根据需求决定是否通知缺货
                print(message)
            else:
                print(f"尺码 {size} 状态未改变 ({'有货' if current_stock_status else '缺货'})。")

            # 更新当前产品的库存状态
            if url not in previous_stock_status:
                previous_stock_status[url] = {}
            previous_stock_status[url][size] = current_stock_status

        # 如果有任何库存更新,发送Discord消息
        if result_message.strip(): # 确保消息不为空白
            print("\nSending Discord message...")
            await send_webhook_message(result_message)
            print("Discord message sent.")
        else:
            print("\nNo stock updates to send.")

        # 设置检查间隔
        check_interval_seconds = 600 # 10分钟
        print(f"Waiting {check_interval_seconds} seconds before next check...")
        await asyncio.sleep(check_interval_seconds)

# 运行主函数
if __name__ == "__main__":
    asyncio.run(main())

3. 注意事项与最佳实践

  • WebDriver路径: 确保chromedriver或geckodriver可执行文件位于系统的PATH中,或者在webdriver.Chrome()或webdriver.Firefox()中通过executable_path参数明确指定其路径。
  • 无头模式: 在生产环境中,强烈建议使用options.add_argument('--headless')来运行Selenium,这样浏览器就不会在后台打开可见窗口,节省资源。
  • 等待策略: 动态加载内容需要时间。使用WebDriverWait和expected_conditions(如EC.presence_of_element_located或EC.visibility_of_element_located)是确保元素加载后再尝试查找的最佳实践,而不是使用硬编码的time.sleep()。
  • 元素定位: 选择器(CSS Selector或XPath)应尽可能健壮,避免使用过于依赖页面结构变化的定位方式。通过浏览器开发者工具仔细检查目标元素的属性(class, id, title, data-属性等)来构建可靠的选择器。
  • User-Agent: 设置User-Agent可以模拟真实浏览器,减少被网站识别为爬虫的风险。
  • 错误处理: 增加try-except块来捕获TimeoutException、NoSuchElementException以及其他可能的网络或Selenium相关的异常,提高程序的健壮性。
  • 爬虫道德与频率:
    • robots.txt: 在爬取任何网站之前,请检查其robots.txt文件(例如:https://www.courir.com/robots.txt),了解网站的爬取规则。
    • 服务条款: 遵守网站的服务条款。
    • 爬取频率: 设置合理的检查间隔(例如10分钟或更长),避免对网站服务器造成过大负担,防止IP被封禁。
  • 资源消耗: Selenium会启动一个完整的浏览器实例,相比requests+BeautifulSoup,资源消耗(CPU、内存)更大。在服务器上部署时,请考虑服务器性能。
  • 异步与同步: Selenium本身是同步的。为了将其集成到asyncio事件循环中,我们使用了loop.run_in_executor(None, sync_function, ...),这会将同步函数放到一个默认的线程池中执行,从而不阻塞主事件循环。

4. 总结

通过本教程,我们了解了在处理JavaScript动态加载内容的网站时,传统爬虫工具如BeautifulSoup的局限性。而Selenium作为强大的无头浏览器自动化工具,能够模拟真实用户行为,有效获取这些动态内容,从而实现精确的库存监控。结合Python的asyncio和Discord Webhook,我们可以构建一个高效、实时的库存变动通知系统。在实际应用中,务必注意遵守网站的爬取政策,并采取合理的爬取频率和错误处理机制,确保程序的稳定性和合规性。

热门AI工具

更多
立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
chrome什么意思
chrome什么意思

chrome是浏览器的意思,由Google开发的网络浏览器,它在2008年首次发布,并迅速成为全球最受欢迎的浏览器之一。本专题为大家提供chrome相关的文章、下载、课程内容,供大家免费下载体验。

2706

2023.08.11

chrome无法加载插件怎么办
chrome无法加载插件怎么办

chrome无法加载插件可以通过检查插件是否已正确安装、禁用和启用插件、清除插件缓存、更新浏览器和插件、检查网络连接和尝试在隐身模式下加载插件方法解决。更多关于chrome相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

5279

2023.11.06

Chrome开启无痕模式
Chrome开启无痕模式

本专题整合了Chrome无痕模式开启相关内容,阅读专题下面的文章了解更多详细教程。

3767

2026.03.24

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

673

2023.11.13

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

3896

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

306

2025.12.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4178

2023.08.10

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

6521

2024.08.14

OpenJDK JVM参数怎么调优
OpenJDK JVM参数怎么调优

Java应用运行缓慢,如何调整JVM参数?本专题介绍OpenJDK JVM性能调优的基本流程,涵盖性能指标确认、运行数据采集、内存配置和垃圾回收分析,帮助你先定位瓶颈,再通过对照测试验证效果,避免直接套用参数而影响应用稳定性。

60

2026.10.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 1.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 6万人学习

CSS教程
CSS教程

共754课时 | 97.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn