
本文详解如何使用 scrapy 集成 playwright 成功抓取依赖 javascript 渲染的动态页面(如 ajax 加载内容),重点解决因选择器误用导致的空白输出问题,并提供可直接运行的完整示例。
本文详解如何使用 scrapy 集成 playwright 成功抓取依赖 javascript 渲染的动态页面(如 ajax 加载内容),重点解决因选择器误用导致的空白输出问题,并提供可直接运行的完整示例。
在使用 Scrapy 与 Playwright 协同爬取动态网页(例如 scrapethissite.com 的 AJAX 页面)时,一个常见却极易被忽视的错误是:直接沿用 Scrapy 原生 CSS 选择器语法(如 ::text)处理 Playwright 渲染后的 DOM 节点。由于 Playwright 的 response 对象(由 scrapy-playwright 提供)本质是基于浏览器上下文的交互式响应,其 .css() 方法仅返回 SelectorList,但此时节点内容可能尚未通过 JS 完全注入文本节点——尤其当目标元素文本由 JavaScript 动态写入(而非静态 HTML)时,::text 伪类将无法匹配,最终返回空字符串。
✅ 正确做法是:改用 Playwright 原生的 locator() API 获取元素,再调用 .inner_text()(推荐)或 .text_content() 方法提取可见文本。该方法会等待元素可交互、文本渲染完成,并自动处理换行/空白归一化,鲁棒性远高于 CSS ::text。
以下是修正后的完整 Spider 示例(兼容 Scrapy 2.11+ 与 scrapy-playwright 2.0+):
使用Playwright API直接进行浏览器自动化。导航网站、与元素交互、提取数据、截图、生成PDF、录制视频,自动化复杂工作流程。比MCP方法更可靠。
import scrapy
from scrapy_playwright.page import PageMethod
class OscarSpider(scrapy.Spider):
name = "OscarSpider"
def start_requests(self):
yield scrapy.Request(
url="https://www.php.cn/link/a99db42aa06a19e4c0e1bc61545b2c58",
callback=self.parse,
meta={
"playwright": True,
"playwright_include_page": True,
"playwright_page_methods": [
# 等待年份按钮加载并点击 2010 年数据
PageMethod("wait_for_selector", "a#2010"),
PageMethod("click", "a#2010"),
# 等待电影列表(tr.film)完全渲染
PageMethod("wait_for_selector", "tr.film", state="attached"),
# 可选:滚动到底部触发懒加载(本例非必需,但通用性强)
PageMethod("evaluate", "window.scrollTo(0, document.body.scrollHeight)"),
# 确保所有异步内容就绪(建议用 wait_for_timeout + 显式 selector 更可靠)
PageMethod("wait_for_timeout", 3000),
],
},
)
async def parse(self, response):
# 使用 Playwright locator 替代 response.css()
film_rows = response.selector.css("tr.film")
for row in film_rows:
# ✅ 关键修正:通过 locator 获取元素并调用 inner_text()
title = response.xpath(f"//tr[@class='film'][{film_rows.index(row)+1}]//td[@class='film-title']").get()
# 更健壮的方式:直接在 response 上用 locator(需确保 playwright_include_page=True)
# 但注意:scrapy-playwright 的 response 对象不直接暴露 page.locator(),
# 因此推荐在 parse 中使用 response.selector + .get() 提取 HTML 后,或改用更清晰的方案:
# ✅ 推荐终极写法(简洁、可靠、符合 Playwright 惯例):
page = response.meta.get("playwright_page")
if page:
# 在 page 上执行定位(需确保 page 未关闭)
rows = await page.query_selector_all("tr.film")
for row in rows:
title_el = await row.query_selector("td.film-title")
nominations_el = await row.query_selector("td.film-nominations")
awards_el = await row.query_selector("td.film-awards")
yield {
"title": (await title_el.inner_text()).strip() if title_el else "",
"nominations": (await nominations_el.inner_text()).strip() if nominations_el else "",
"awards": (await awards_el.inner_text()).strip() if awards_el else "",
}
⚠️ 重要注意事项:
-
playwright_include_page=True是启用response.meta["playwright_page"]的前提,务必开启; -
PageMethod("wait_for_selector", ...)的state参数建议显式设为"attached"或"visible",避免因元素存在但不可见导致超时; - 避免过度依赖
wait_for_timeout;优先使用wait_for_selector等条件等待,提升稳定性和性能; - 若项目需高并发,请在
settings.py中配置TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"并启用PLAYWRIGHT_BROWSER_TYPE = "chromium"(默认); - 运行命令保持不变:
scrapy crawl OscarSpider -O oscar.json,输出将包含结构化 JSON 数据,如{"title": "The King's Speech", "nominations": "12", "awards": "4"}。
通过以上调整,您将彻底解决“空白输出”问题,实现对动态 AJAX 内容的稳定、精准抓取。










