
本教程详细探讨了使用python监控动态加载网页商品库存并发送discord通知的方法。针对传统网络爬虫(如beautifulsoup)在处理javascript渲染内容时的局限性,文章重点介绍了如何利用selenium这一无头浏览器工具来模拟用户行为,有效获取实时库存信息。教程涵盖了环境配置、代码实现、异步集成及最佳实践,旨在帮助读者构建健壮的库存监控系统。
1. 理解动态网页与传统爬虫的局限性
在构建库存监控系统时,我们通常会想到使用Python的requests库获取网页内容,再结合BeautifulSoup进行解析。然而,这种方法对于现代网站而言存在一个显著的局限性:许多网站的内容,尤其是商品库存状态,是通过JavaScript在浏览器端动态加载的。
问题分析: 当我们尝试使用requests.get(url)获取网页内容时,BeautifulSoup只能解析服务器返回的原始HTML源代码。如果商品的库存信息(例如特定尺码是否可选)是在页面加载后由JavaScript异步请求并渲染到DOM中的,那么这些信息将不会出现在requests获取的初始HTML中。
例如,在示例网站(courir.com)上,尺码选项的可用性(如尺码40)并不是直接嵌入在初始HTML中的。通过浏览器开发者工具检查,你会发现:
- 当某个尺码缺货时,其对应的
- 元素可能带有unselectable类或aria-disabled="true"属性。
- 当尺码有货时,其
- 元素可能带有selectable类,并且对应的链接(标签)是可点击的。
然而,这些状态的切换和元素的出现,都是在JavaScript执行之后才发生的。因此,单纯依赖soup.find('li', {'class': 'unselectable'})这样的代码,可能无法准确判断尺码40的实时库存状态,因为它无法“看到”JavaScript渲染后的页面。
2. 解决方案:使用Selenium处理动态内容
为了解决BeautifulSoup无法处理动态加载内容的问题,我们需要一个能够模拟真实浏览器行为的工具,即无头浏览器(Headless Browser)。Selenium就是这样一个强大的工具,它允许我们通过编程方式控制浏览器,执行JavaScript,等待元素加载,甚至模拟点击等用户交互。
立即学习“Python免费学习笔记(深入)”;
Selenium工作原理: Selenium启动一个真实的浏览器实例(可以是可见的,也可以是无头的),然后通过WebDriver与该浏览器进行通信。这意味着Selenium能够“看到”并操作JavaScript渲染后的完整DOM。
2.1 环境准备
在使用Selenium之前,需要安装相应的库和浏览器驱动:
-
安装Selenium库:
pip install selenium
-
安装浏览器驱动:
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}")
# 等待尺码选择器元素加载。根据网站结构,这些通常是带有特定类名的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,我们可以构建一个高效、实时的库存变动通知系统。在实际应用中,务必注意遵守网站的爬取政策,并采取合理的爬取频率和错误处理机制,确保程序的稳定性和合规性。










