Scrapy Playwright爬取Google Flights点击切日期返回重复值问题
问题根源
你的代码重复返回初始日期,是三个逻辑错误导致的:
- 循环中始终读取初始请求返回的
response.text:Scrapy的response是首次请求完成时的页面固定快照,后续通过Playwright操作页面切换日期后,这个快照不会自动同步更新,所以每次提取到的都是首次加载的7月3日数据。 - 点击操作后未等待页面渲染:点击日期相关按钮后,页面需要时间加载新日期对应的航班数据、更新日期输入框值,你没有等待DOM更新完成就直接提取内容,读到的必然是旧数据。
- 点击目标错误:你代码里写的长层级选择器是打开日期选择面板的按钮,不是直接切换到下一天的箭头按钮,点击后并不会自动切换日期。
修复方案
修复逻辑遵循以下步骤:
- 初始页面加载完成后,先提取第一天的日期和对应航班数据
- 后续每轮循环,点击日期栏带
Next date标识的下一天切换按钮(这类带语义化aria-label的选择器比长层级CSS选择器稳定得多,不会因为前端调整DOM层级失效) - 点击后等待页面加载完成:通过等待日期输入框的值更新、航班列表加载完成两个条件,确认新日期的内容已经渲染完毕
- 每次确认加载完成后,通过Playwright的
page.content()获取当前最新的页面HTML,再做解析提取,不要复用初始的response对象 - 所有日期采集完成后关闭Playwright页面,避免资源泄漏
修复后的可运行代码如下:
import scrapy from scrapy_playwright.page import PageCoroutine from bs4 import BeautifulSoup class PwExSpider(scrapy.Spider): name = "pw_ex" headers = { "authority": "www.google.com", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-language": "en,ru;q=0.9", "cache-control": "max-age=0", "referer": "https://www.google.com/", "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="100", "Yandex";v="22"', "sec-ch-ua-arch": '"x86"', "sec-ch-ua-bitness": '"64"', "sec-ch-ua-full-version": '"22.5.0.1879"', "sec-ch-ua-full-version-list": '" Not A;Brand";v="99.0.0.0", "Chromium";v="100.0.4896.143", "Yandex";v="22.5.0.1879"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-model": '""', "sec-ch-ua-platform": '"Linux"', "sec-ch-ua-platform-version": '"5.4.0"', "sec-ch-ua-wow64": "?0", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.143 Safari/537.36", } def start_requests(self): yield scrapy.Request( "https://www.google.com/travel/flights/search?tfs=CBwQAhooagwIAxIIL20vMDE3N3oSCjIwMjItMDctMDNyDAgDEggvbS8wNmM2MhooagwIAxIIL20vMDZjNjISCjIwMjItMDctMjJyDAgDEggvbS8wMTc3enABggELCP___________wFAAUgBmAEB&tfu=EgYIARABGAA&curr=EUR", headers=self.headers, meta=dict( playwright=True, playwright_include_page=True, playwright_page_coroutines=[ # 等待航班列表标题加载完成,确认初始页面渲染完毕 PageCoroutine("wait_for_selector", "h3.zBTtmb.ZSxxwc"), ], ), ) async def parse(self, response): page = response.meta["playwright_page"] total_days = 5 # 先提取第一天的数据 current_html = await page.content() soup = BeautifulSoup(current_html, "html.parser") # 用placeholder属性定位出发日期输入框,比索引定位更稳定 date_input = soup.find("input", attrs={"placeholder": "Departure"}) current_date = date_input["value"] if date_input else "" yield {"search_date": current_date} # 循环采集剩余4天数据 for _ in range(total_days - 1): # 定位下一天切换按钮 next_btn = await page.wait_for_selector('button[aria-label="Next date"]', timeout=5000) await next_btn.click() # 等待日期值更新,确认页面切换完成 await page.wait_for_function( """(oldDate) => { const input = document.querySelector('input[placeholder="Departure"]'); return input && input.value !== oldDate; }""", arg=current_date, timeout=10000 ) # 额外等待航班列表渲染完成 await page.wait_for_selector("h3.zBTtmb.ZSxxwc", state="visible") # 获取最新页面内容解析 current_html = await page.content() soup = BeautifulSoup(current_html, "html.parser") date_input = soup.find("input", attrs={"placeholder": "Departure"}) current_date = date_input["value"] if date_input else "" # 可在此处添加对应日期的航班数据提取逻辑 yield {"search_date": current_date} # 采集完成关闭页面释放资源 await page.close()
注意事项
- 如果初始加载遇到Cookie同意弹窗,需要在等待航班列表加载前增加弹窗按钮的点击逻辑,否则后续按钮点击会被弹窗遮挡失效
- 若网络环境较差,可以适当调大
timeout参数的数值,避免等待超时抛出错误 - 提取航班数据时,必须基于每次拿到的最新
current_html做解析,不要复用旧的soup对象
内容的提问来源于stack exchange,提问作者dougj
相关产品推荐
相关产品推荐

