
一、 为什么选择Scrapy + Playwright?在技术选型时,我们有必要理解这套组合拳的优势所在:强强联合的异步架构:Scrapy基于Twisted,是原生的异步框架;Playwright也提供了完整的异步API。二者的结合天衣无缝,能最大限度地发挥硬件性能,实现高并发爬取。无与伦比的浏览器兼容性:Playwright支持Chromium、Firefox和WebKit(Safari),能够精准模拟不同浏览器环境,大大降低了因客户端差异导致爬取失败的风险。强大的自动化能力:不仅仅是等待加载,Playwright可以模拟所有用户行为:点击、填写表单、滚动、悬停等,从而触发那些需要用户交互才会显示的数据。Scrapy的完整生态:我们无需放弃Scrapy的任何核心优势,如灵活的中间件、Item Pipeline、数据导出、请求调度等,仅仅是增强了其下载页面的能力。二、 环境搭建与项目初始化首先,我们需要安装必要的Python包。
接下来,创建一个新的Scrapy项目。
三、 核心配置:启用Scrapy Playwright中间件要让Scrapy使用Playwright,核心在于配置下载器中间件。修改项目下的 settings.py 文件。
四、 构建爬虫:从静态到动态的跨越现在,我们来修改生成的 dynamic_example.py 爬虫文件。我们将以一个需要JavaScript渲染才能显示内容的假设网站为例。
# spiders/dynamic_example.py
def start_requests(self):
for url in self.start_urls:
# 关键:使用 `meta` 字典中的 `playwright` 键来启用Playwright处理
yield scrapy.Request(
url,
meta={
"playwright": True,
# 可以指定使用不同的浏览器,覆盖全局设置
# "playwright_browser_type": "firefox",
# Playwright代理配置
"playwright_context_kwargs": {
"proxy": {
"server": proxy_url,
# 可选:设置代理认证方式
# "username": proxyUser,
# "password": proxyPass,
}
},
# 最重要的一部分:定义页面加载后需要执行的Playwright操作
"playwright_page_coroutines": [
# 向下滚动到页面底部,触发无限加载
PageCoroutine("wait_for_selector", "div.quote"), # 先等待首个元素出现
PageCoroutine("evaluate", "window.scrollBy(0, document.body.scrollHeight)"),
# 等待可能的网络请求或新内容出现,可以重复多次
PageCoroutine("wait_for_timeout", 2000), # 等待2秒
PageCoroutine("evaluate", "window.scrollBy(0, document.body.scrollHeight)"),
PageCoroutine("wait_for_timeout", 2000),
],
# 可选:为这个请求单独设置一个上下文
# "playwright_context": "my_context",
},
callback=self.parse,
errback=self.errback_close_page, # 错误处理
)
async def parse(self, response):
"""解析页面,提取数据"""
self.logger.info(f"Parsing page: {response.url}")
# 此时,response.body包含了由Playwright渲染后的完整HTML
quotes = response.css('div.quote')
for quote in quotes:
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
# 示例:如何点击"下一页"并继续用Playwright处理
# next_page = response.css('li.next a::attr(href)').get()
# if next_page is not None:
# next_page_url = response.urljoin(next_page)
# yield scrapy.Request(
# next_page_url,
# meta={
# "playwright": True,
# "playwright_context_kwargs": {
# "proxy": {
# "server": proxy_url,
# }
# },
# "playwright_page_coroutines": [
# PageCoroutine("wait_for_selector", "div.quote"),
# ],
# },
# callback=self.parse
# )
async def errback_close_page(self, failure):
"""错误回调函数,确保发生错误时页面被关闭"""
page = failure.request.meta.get("playwright_page")
if page:
await page.close()
self.logger.error(f"Request failed: {failure.request.url} - {failure.value}")五、 高级技巧:处理复杂交互与上下文管理对于更复杂的场景,例如需要登录、处理弹窗或管理多个独立会话,我们可以使用Playwright上下文。
# 在settings.py或自定义中间件中配置默认上下文
PLAYWRIGHT_DEFAULT_NAVIGATION_TIMEOUT = 30 * 1000
PLAYWRIGHT_CONTEXTS = {
# 创建一个名为"persistent_context"的上下文,它会在整个爬虫过程中持续存在
"persistent_context": {
"context_args": {
"ignore_https_errors": True,
# 可以设置视口大小、User-Agent等
"viewport": {"width": 1920, "height": 1080},
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
},
"persistent": True, # 关键:使上下文持久化
}
}
# 在爬虫的请求中指定使用这个上下文
# yield scrapy.Request(
# url,
# meta={
# "playwright": True,
# "playwright_context": "persistent_context", # 使用持久化上下文
# "playwright_page_coroutines": [
# # 例如:先点击登录按钮,然后填写表单
# PageCoroutine("click", "button#login-button"),
# PageCoroutine("fill", "input#username", "my_username"),
# PageCoroutine("fill", "input#password", "my_password"),
# PageCoroutine("click", "button#submit-login"),
# PageCoroutine("wait_for_navigation"), # 等待导航完成
# ],
# },
# )六、 性能优化与最佳实践控制并发:Playwright比较消耗资源。在 settings.py 中合理设置 CONCURRENT_REQUESTS,例如 8 或 16,避免内存溢出。善用等待策略:优先使用 wait_for_selector、wait_for_function 等智能等待,而非固定的 wait_for_timeout,这样能更快地继续执行。及时清理:在 spider_closed 信号中关闭所有浏览器上下文,确保资源被正确释放。
# 在爬虫类中添加
def closed(self, reason):
from scrapy_playwright.utils import get_playwright_contexts
for context_name, context in get_playwright_contexts().items():
self.logger.info(f"Closing context: {context_name}")
context.close()错误处理:网络不稳定、元素未找到等情况很常见,务必在协程和解析函数中做好异常捕获和处理。七、 总结通过将Scrapy与Playwright集成,我们构建的爬虫同时具备了Scrapy的工业级强度与Playwright的浏览器级模拟能力。这套方案能够应对当今Web开发中绝大多数复杂的动态内容加载场景,从简单的Ajax请求到复杂的单页应用,都不在话下。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。