React动态站点爬取:BeautifulSoup对象与Chrome显示内容不符的问题
首次尝试爬取基于React的Booking.com搜索结果页,目标是收集特定酒店在固定条件下的实时价格。之前用简单CSS选择器就能爬取,但现在网站更新后,需要的元素都带含随机数的data-testid属性,旧代码直接失效。
尝试用Selenium捕获JS渲染的数据,再用BeautifulSoup解析,但返回的内容和浏览器显示完全不符:指定的入住退房日期、4位成人等URL参数没生效,本该排在首位的"cup of tea ensemble"酒店也不在对应位置。想知道爬React驱动的网站是不是常遇到这种情况,该怎么解决才能拿到和浏览器一致的数据。
附上当前使用的代码:
from bs4 import BeautifulSoup import requests from selenium import webdriver from selenium.webdriver.chrome.options import Options booking_url = 'https://www.booking.com/searchresults.ja.html?lang=ja&ss=Takayama&dest_id=6411914&dest_type=hotel&checkin=2022-12-22&checkout=2022-12-23&group_adults=4&no_rooms=1&group_children=0&sb_travel_purpose=leisure' #booking_url = 'https://www.google.co.jp/' options = Options() options.add_argument('--headless') driver = webdriver.Chrome(executable_path='./chromedriver', chrome_options=options) driver.get(booking_url) html = driver.page_source.encode('utf-8') soup = BeautifulSoup(html, 'html.parser') print(soup)
爬React这类动态渲染的网站确实容易遇到这类问题,核心原因大多是反爬拦截、页面未完全渲染或参数未被正确识别,以下是具体解决办法:
优化无头浏览器配置,避免被反爬识别
纯无头模式很容易被网站检测到,返回的内容会和普通浏览器不一致。可以添加模拟真实浏览器的参数:options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36') options.add_argument('--window-size=1920,1080') options.add_argument('--disable-blink-features=AutomationControlled') options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option('useAutomationExtension', False)等待页面完全渲染后再获取源码
React加载数据需要时间,直接调用page_source可能拿到未加载完成的内容。用Selenium的显式等待,等待酒店列表等关键元素出现:from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待酒店列表容器加载完成,超时10秒 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, 'c82435a4b8')) # 替换为实际稳定的类名 ) html = driver.page_source.encode('utf-8')放弃URL参数,手动模拟用户操作填写条件
部分网站会校验请求的上下文,直接传URL参数可能不生效。可以用Selenium手动点击选择日期、填写人数,模拟真实用户流程:# 示例:点击日期选择框,选择入住退房日期(需根据实际元素定位调整) checkin_input = driver.find_element(By.ID, 'checkin_date') checkin_input.clear() checkin_input.send_keys('2022-12-22') checkout_input = driver.find_element(By.ID, 'checkout_date') checkout_input.clear() checkout_input.send_keys('2022-12-23') # 选择4位成人(需根据实际元素定位调整) adult_count_btn = driver.find_element(By.CSS_SELECTOR, 'button[data-testid="occupancy-config"]') adult_count_btn.click() # 点击加号直到人数为4 for _ in range(3): # 默认是1位,需要加3次 driver.find_element(By.CSS_SELECTOR, 'button[aria-label="Increase number of Adults"]').click() driver.find_element(By.CSS_SELECTOR, 'button[data-testid="occupancy-config-done"]').click() # 点击搜索按钮 driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()优化元素定位策略
避开带随机数的data-testid,优先用稳定的类名、元素文本或层级结构定位。比如要找目标酒店,可以先定位所有酒店名称的容器,再通过文本匹配:# 解析页面后找目标酒店 hotels = soup.find_all('div', class_='c82435a4b8') # 稳定的酒店容器类名 for hotel in hotels: hotel_name = hotel.find('h3', class_='d2fee87262').text.strip() # 稳定的酒店名称类名 if hotel_name == 'cup of tea ensemble': # 提取价格等信息 price = hotel.find('span', class_='f6431b446c').text.strip() print(f"目标酒店价格:{price}") break
内容的提问来源于stack exchange,提问作者pomme

