
本教程旨在指导如何使用python监测网站上特定尺寸商品的库存状态,并在库存更新时通过discord发送通知。文章将深入探讨传统网络爬虫(如beautifulsoup)在处理javascript动态加载内容时的局限性,并介绍如何利用selenium等无头浏览器工具来克服这些挑战,实现对动态生成库存信息的准确抓取与实时监控。
1. 引言:库存监测与Discord通知的需求
在电子商务日益普及的今天,许多用户希望能够实时追踪特定商品的库存状态,尤其是一些热门或限量发售的商品。当商品重新有货时,通过自动化工具及时收到通知,可以大大提高抢购成功的几率。本教程将以监测特定尺寸(例如“40码”)的商品库存为例,并演示如何将库存更新信息发送至Discord频道。
2. 初始尝试与BeautifulSoup的局限性
最初的设想是使用requests库获取网页内容,并结合BeautifulSoup进行HTML解析,以查找库存信息。以下是用户尝试的初始代码结构:
import discord
from bs4 import BeautifulSoup
import requests
import aiohttp
import asyncio
webhook_url = 'YOUR_DISCORD_WEBHOOK_URL' # 替换为你的Discord Webhook URL
async def send_webhook_message(content):
"""
通过Discord Webhook发送消息。
"""
async with aiohttp.ClientSession() as session:
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}")
async def check_stock_initial(url, size):
"""
初步尝试检查库存,但存在局限性。
"""
try:
response = requests.get(url)
response.raise_for_status() # 检查HTTP请求是否成功
soup = BeautifulSoup(response.text, 'html.parser')
# 尝试查找表示库存状态的元素
stock_element = soup.find('li', {'class': 'unselectable'})
# 这里的逻辑是判断如果找到'unselectable'则表示无货,否则可能是有货。
# 但这并不能精确到特定尺寸,且未考虑动态内容。
return stock_element is None
except requests.RequestException as e:
print(f"Error making request to the website: {e}")
return False
# ... (后续的main函数和异步运行逻辑)问题分析:
- 'unselectable' 与 'selectable': 在许多网站上,表示商品有货或无货的HTML元素通常会通过类名(如selectable或unselectable)来区分。unselectable通常意味着无货,而selectable则意味着有货。因此,仅仅查找unselectable并不能直接判断特定尺寸是否有货。
- 特定尺寸的识别: 要检查特定尺寸(如“40码”)的库存,我们需要定位到代表该尺寸的元素,并判断其状态。通常,这些尺寸选项会以
- 、或其他标签的形式存在,并通过title属性或文本内容来标识尺寸。
- 动态加载内容的核心问题: 最关键的问题在于,目标网站(如courir.com)上的库存信息,尤其是不同尺寸的可用性,是通过JavaScript在页面加载完成后动态生成的。requests库只能获取到服务器最初返回的HTML源代码,而无法执行JavaScript来渲染完整的页面。这意味着,在BeautifulSoup解析的HTML中,我们可能根本找不到那些由JavaScript生成的、包含库存信息的元素。
通过浏览器开发者工具(通常按F12打开),我们可以观察到,当页面加载完成后,表示尺寸选择的元素可能具有如下结构:
立即学习“Python免费学习笔记(深入)”;
然而,这些带有selectable类和title="40"的元素,在requests获取的原始HTML中是缺失的。
3. 解决方案:使用无头浏览器处理动态内容
为了解决JavaScript动态加载内容的问题,我们需要使用无头浏览器(Headless Browser)。无头浏览器是一种没有图形用户界面的浏览器,它可以在后台运行,模拟真实用户的行为(如加载页面、执行JavaScript、点击元素等),从而获取到完全渲染后的页面内容。
常用的无头浏览器工具有:
- Selenium: 一个功能强大的Web自动化测试框架,支持多种浏览器(Chrome, Firefox等),并提供Python绑定。
- Playwright: Microsoft开发的新一代Web自动化库,支持Chromium, Firefox, WebKit,性能优异。
- Puppeteer (Node.js): Google开发的Node.js库,用于控制Chrome/Chromium。
本教程将以Selenium为例,演示如何获取动态加载的库存信息。
3.1 环境准备
-
安装Selenium:
pip install selenium
-
下载浏览器驱动: 根据你使用的浏览器(如Chrome)版本,下载对应的WebDriver。
- ChromeDriver: https://www.php.cn/link/687b8dedbbf281200b402ba6fe58232d
- 将下载的驱动文件(例如chromedriver.exe)放置在系统PATH中,或指定其完整路径。
3.2 使用Selenium检查库存
现在,我们将重写check_stock函数,使其使用Selenium来加载和解析页面。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
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, WebDriverException
import time # 用于等待
# 配置ChromeDriver路径 (如果未添加到系统PATH)
# service = Service(executable_path='/path/to/chromedriver') # 替换为你的chromedriver路径
async def check_stock_selenium(url, target_size):
"""
使用Selenium检查特定尺寸的库存。
"""
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 启用无头模式,不显示浏览器界面
options.add_argument('--disable-gpu') # 禁用GPU加速(Linux环境下推荐)
options.add_argument('--no-sandbox') # 禁用沙箱模式(Linux环境下推荐)
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')
driver = None
try:
# 初始化WebDriver
# driver = webdriver.Chrome(service=service, options=options) # 如果指定了service
driver = webdriver.Chrome(options=options) # 如果chromedriver在PATH中
driver.get(url)
# 等待页面元素加载完成。这里等待直到表示尺寸选择的某个元素出现。
# 通常,尺寸选择器会有一个共同的父容器或类名。
# 我们可以等待一个通用且稳定的元素,然后查找其内部的特定尺寸。
# 示例:等待一个class为'product-size-selector'的div出现
WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'ul.product-size-list'))
)
# 查找特定尺寸的元素。
# CSS选择器:查找 class 为 'selectable' 的 li 元素,
# 在其内部查找 class 为 'swatchanchor' 且 title 包含目标尺寸的 a 元素。
# 注意:这里的选择器需要根据实际网站的HTML结构进行调整。
size_element_selector = f"li.selectable a.swatchanchor[title*='{target_size}']"
try:
# 尝试查找目标尺寸的“有货”元素
size_40_available = driver.find_element(By.CSS_SELECTOR, size_element_selector)
# 如果找到,则表示该尺寸有货
print(f"Size {target_size} is in stock!")
return True
except NoSuchElementException:
# 如果没有找到,则表示该尺寸无货
print(f"Size {target_size} is out of stock.")
return False
except TimeoutException:
print(f"Timeout waiting for page elements to load on {url}")
return False
except WebDriverException as e:
print(f"WebDriver error: {e}")
return False
except Exception as e:
print(f"An unexpected error occurred: {e}")
return False
finally:
if driver:
driver.quit() # 确保关闭浏览器实例
3.3 整合到异步监测循环
现在,我们可以将新的check_stock_selenium函数整合到原有的异步监测循环中。
# ... (send_webhook_message 函数保持不变)
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']
# 使用Selenium检查库存
current_stock_status = await check_stock_selenium(url, size)
# 检查库存状态是否发生变化
if not previous_stock_status.get(url, {}).get(size, False) and current_stock_status:
result_message += f"商品 {url} 的 {size} 码现在有货!\n"
# 更新当前库存状态
previous_stock_status.setdefault(url, {})[size] = current_stock_status
if result_message: # 只有当有更新时才发送Discord消息
print(result_message.strip())
await send_webhook_message(result_message)
print("已发送Discord消息。")
else:
print("无库存更新。")
print("等待10分钟进行下一次检查...")
await asyncio.sleep(600) # 睡眠10分钟 (600秒)
if __name__ == "__main__":
asyncio.run(main())4. 注意事项与最佳实践
- 网站结构变化: 动态网站的HTML结构可能会频繁变动,导致CSS选择器失效。定期检查并更新选择器是必要的维护工作。
-
反爬机制: 网站可能会有反爬虫机制,如IP封锁、验证码等。
- User-Agent: 使用真实的User-Agent头。
- 代理IP: 考虑使用代理IP池来分散请求,避免IP被封。
- 请求频率: 控制请求频率,模拟人类浏览行为,避免过于频繁的请求。asyncio.sleep是关键。
- Headless检测: 有些网站能检测出无头浏览器。可以通过设置更多浏览器选项(如禁用自动化标志、伪装为真实浏览器)来规避。
- 错误处理: 增加更健壮的错误处理机制,例如针对NoSuchElementException、TimeoutException等。
- 资源消耗: 无头浏览器比requests消耗更多的系统资源(CPU和内存)。在部署时需考虑服务器性能。
- 异步编程: aiohttp和asyncio在处理网络请求和并发任务时非常高效,但需要确保所有I/O操作都是异步的。Selenium本身是同步的,但在async函数中调用它不会阻塞整个事件循环,只是会等待其完成。
- Discord Webhook安全性: 妥善保管你的Discord Webhook URL,避免泄露。
5. 总结
通过本教程,我们了解了在处理JavaScript动态加载内容的网站时,传统网络爬虫(如BeautifulSoup)的局限性。为了准确获取特定尺寸商品的库存信息,我们必须转向使用Selenium等无头浏览器工具来模拟用户行为,执行JavaScript并渲染完整页面。结合异步编程和Discord Webhook,我们可以构建一个高效、实用的库存监测和通知系统。然而,持续维护和应对网站反爬机制是此类自动化工具长期运行的关键。










