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

聖光之護
发布: 2025-11-24 13:55:31
原创
397人浏览过

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免费学习笔记(深入)”;

AI TransPDF
AI TransPDF

高效准确地将PDF文档翻译成多种语言的AI智能PDF文档翻译工具

AI TransPDF 231
查看详情 AI TransPDF

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.124 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,我们可以构建一个高效、实时的库存变动通知系统。在实际应用中,务必注意遵守网站的爬取政策,并采取合理的爬取频率和错误处理机制,确保程序的稳定性和合规性。

以上就是Python动态网页库存监控与Discord通知:Selenium实战指南的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号