如何用Scrapy实现动态加载(Load More按钮)网站分页?含gelbeseiten实例
Gelbeseiten网站分页爬取解决方案
针对你遇到的Gelbeseiten酒店列表分页问题,结合你提到的AJAX请求和现有Scrapy代码,提供两种可行的分页实现方案:
方案一:Selenium模拟点击分页按钮(简单直接)
适合不想深入分析AJAX参数的场景,直接模拟浏览器点击下一页按钮,加载新页面后继续提取数据。
修改后的完整代码
import scrapy from scrapy_selenium import SeleniumRequest 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 NoSuchElementException class Data2Spider(scrapy.Spider): name = 'data2' def start_requests(self): yield SeleniumRequest( url="https://www.gelbeseiten.de/suche/hotels/n%c3%bcrnberg", callback=self.parse, ) def parse(self, response): # 提取当前页所有酒店的data-realid real_ids = response.xpath("//article/@data-realid").getall() for real_id in real_ids: yield SeleniumRequest( url=f"https://www.gelbeseiten.de/gsbiz/{real_id}", callback=self.parse_data, ) # 处理分页逻辑 driver = response.meta['driver'] try: # 定位下一页按钮(若页面更新需调整选择器) next_btn = driver.find_element(By.XPATH, "//a[@class='mod-Pagination__link mod-Pagination__link--next']") # 检查按钮是否未被禁用 if 'mod-Pagination__link--disabled' not in next_btn.get_attribute('class'): next_btn.click() # 等待新页面的酒店元素加载完成 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.XPATH, "//article[@data-realid]")) ) # 生成下一页的请求 yield SeleniumRequest( url=driver.current_url, callback=self.parse, meta={'driver': driver} ) except NoSuchElementException: # 无下一页,结束爬取 self.logger.info("已爬取所有页面") def parse_data(self, response): yield { 'URL': response.url, 'Title': response.xpath("//div[@class='a']/h1/text()").get(), 'Phone': response.xpath("//a[@class='nolink-black']/span/text()").get(), 'Fax': response.xpath("//div[@class='mod-Kontaktdaten__list-item contains-icon-fax']/span/text()").get(), 'email': response.xpath("normalize-space(//div[@class='mod-Kontaktdaten__list-item contains-icon-email']/a/text())").get(), 'Website': response.xpath("normalize-space(//div[@class='mod-Kontaktdaten__list-item contains-icon-homepage']/a/text())").get() }
方案二:直接调用AJAX接口(高效推荐)
通过分析浏览器网络面板中的ajaxsuche请求,直接发送POST请求获取分页数据,无需加载完整页面,爬取效率大幅提升。
修改后的完整代码
import scrapy import re from scrapy_selenium import SeleniumRequest class Data2Spider(scrapy.Spider): name = 'data2' def start_requests(self): # 先请求初始页面,获取分页所需参数 yield SeleniumRequest( url="https://www.gelbeseiten.de/suche/hotels/n%c3%bcrnberg", callback=self.get_pagination_params, ) def get_pagination_params(self, response): # 提取csrfToken csrf_token = response.xpath("//meta[@name='csrf-token']/@content").get() # 提取searchParams(从页面全局变量中匹配) script_content = response.xpath("//script[contains(text(), 'window.searchParams')]/text()").get() search_params_match = re.search(r'window.searchParams = (\{.*?\});', script_content) search_params = search_params_match.group(1) if search_params_match else '' # 处理第一页数据 real_ids = response.xpath("//article/@data-realid").getall() for real_id in real_ids: yield SeleniumRequest( url=f"https://www.gelbeseiten.de/gsbiz/{real_id}", callback=self.parse_data, ) # 发起第二页的AJAX请求 yield scrapy.Request( url="https://www.gelbeseiten.de/ajaxsuche", method="POST", headers={ 'X-CSRF-Token': csrf_token, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' }, formdata={ 'page': '2', 'size': '20', 'searchParams': search_params }, callback=self.parse_ajax_page, meta={ 'csrf_token': csrf_token, 'search_params': search_params, 'current_page': 2 } ) def parse_ajax_page(self, response): # 解析AJAX返回的JSON数据 data = response.json() # 提取酒店realId hits = data.get('hits', []) for hit in hits: real_id = hit.get('realId') if real_id: yield SeleniumRequest( url=f"https://www.gelbeseiten.de/gsbiz/{real_id}", callback=self.parse_data, ) # 检查是否有下一页 has_next = data.get('pagination', {}).get('hasNextPage', False) if has_next: current_page = response.meta['current_page'] + 1 # 发起下一页请求 yield scrapy.Request( url="https://www.gelbeseiten.de/ajaxsuche", method="POST", headers={ 'X-CSRF-Token': response.meta['csrf_token'], 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' }, formdata={ 'page': str(current_page), 'size': '20', 'searchParams': response.meta['search_params'] }, callback=self.parse_ajax_page, meta={ 'csrf_token': response.meta['csrf_token'], 'search_params': response.meta['search_params'], 'current_page': current_page } ) def parse_data(self, response): yield { 'URL': response.url, 'Title': response.xpath("//div[@class='a']/h1/text()").get(), 'Phone': response.xpath("//a[@class='nolink-black']/span/text()").get(), 'Fax': response.xpath("//div[@class='mod-Kontaktdaten__list-item contains-icon-fax']/span/text()").get(), 'email': response.xpath("normalize-space(//div[@class='mod-Kontaktdaten__list-item contains-icon-email']/a/text())").get(), 'Website': response.xpath("normalize-space(//div[@class='mod-Kontaktdaten__list-item contains-icon-homepage']/a/text())").get() }
注意事项
- 网站可能存在反爬机制,建议在Scrapy的
settings.py中配置合理的USER_AGENT,必要时添加请求延迟或代理。 - AJAX请求的参数(如
searchParams结构)可能随网站更新变化,需定期检查调整。 - 使用Selenium时,确保浏览器驱动版本与本地浏览器版本匹配。
内容的提问来源于stack exchange,提问作者Raisul Islam
相关产品推荐
相关产品推荐

