集成Scrapy与异步库:Scrapy+Playwright自动化爬取动态内容

简介: 集成Scrapy与异步库:Scrapy+Playwright自动化爬取动态内容

一、 为什么选择Scrapy + Playwright?
在技术选型时,我们有必要理解这套组合拳的优势所在:

  1. 强强联合的异步架构:Scrapy基于Twisted,是原生的异步框架;Playwright也提供了完整的异步API。二者的结合天衣无缝,能最大限度地发挥硬件性能,实现高并发爬取。
  2. 无与伦比的浏览器兼容性:Playwright支持Chromium、Firefox和WebKit(Safari),能够精准模拟不同浏览器环境,大大降低了因客户端差异导致爬取失败的风险。
  3. 强大的自动化能力:不仅仅是等待加载,Playwright可以模拟所有用户行为:点击、填写表单、滚动、悬停等,从而触发那些需要用户交互才会显示的数据。
  4. Scrapy的完整生态:我们无需放弃Scrapy的任何核心优势,如灵活的中间件、Item Pipeline、数据导出、请求调度等,仅仅是增强了其下载页面的能力。
    二、 环境搭建与项目初始化
    首先,我们需要安装必要的Python包。
    ```# 安装Scrapy和Playwright
    pip install scrapy playwright

安装Playwright的浏览器驱动(这一步会下载Chromium, Firefox和WebKit)

playwright install

接下来,创建一个新的Scrapy项目。
```scrapy startproject scrapy_playwright_demo
cd scrapy_playwright_demo
scrapy genspider dynamic_example example.com

三、 核心配置:启用Scrapy Playwright中间件
要让Scrapy使用Playwright,核心在于配置下载器中间件。修改项目下的 settings.py 文件。
```# settings.py

启用Scrapy的默认下载器中间件

DOWNLOADER_MIDDLEWARES = {

# 禁用Scrapy默认的RetryMiddleware,以避免与Playwright的重复尝试冲突(非必须)
# 'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,

}

启用Playwright相关的下载处理器和中间件

DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}

启用Playwright的上下文处理器(非常重要)

TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

Playwright相关设置

PLAYWRIGHT_BROWSER_TYPE = "chromium" # 可选 "firefox", "webkit"
PLAYWRIGHT_LAUNCH_OPTIONS = {
"headless": True, # 设置为False可在调试时看到浏览器界面
"timeout": 20 * 1000, # 浏览器启动超时时间(毫秒)
}

可选:配置每个Scrapy作业的浏览器上下文数量

PLAYWRIGHT_MAX_CONTEXTS = 8

四、 构建爬虫:从静态到动态的跨越
现在,我们来修改生成的 dynamic_example.py 爬虫文件。我们将以一个需要JavaScript渲染才能显示内容的假设网站为例。
```   # spiders/dynamic_example.py
import scrapy
from scrapy_playwright.page import PageCoroutine

# 代理配置信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"

# 构建代理服务器URL
proxy_url = f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"

class DynamicExampleSpider(scrapy.Spider):
    name = "dynamic_example"

    # 假设这个页面需要JS加载数据
    start_urls = ["https://quotes.toscrape.com/scroll"] 

    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"), # 等待导航完成

],

},

)

六、 性能优化与最佳实践
1. 控制并发:Playwright比较消耗资源。在 settings.py 中合理设置 CONCURRENT_REQUESTS,例如 8 或 16,避免内存溢出。
2. 善用等待策略:优先使用 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请求到复杂的单页应用,都不在话下。

相关文章
|
2月前
|
数据采集 存储 监控
构建定时监控系统,轻松爬取番茄小说最新章节
构建定时监控系统,轻松爬取番茄小说最新章节
|
8月前
|
数据采集 NoSQL 关系型数据库
Python爬虫去重策略:增量爬取与历史数据比对
Python爬虫去重策略:增量爬取与历史数据比对
|
7月前
|
Web App开发 数据采集 JavaScript
动态网页爬取:Python如何获取JS加载的数据?
动态网页爬取:Python如何获取JS加载的数据?
1196 58
|
17天前
|
数据采集 监控 JavaScript
应对12306反爬虫机制:JS加密参数逆向分析
应对12306反爬虫机制:JS加密参数逆向分析
|
1月前
|
监控 前端开发 网络协议
网站加载速度慢的7大技术原因与解决方案
本文剖析网站加载慢的7大技术根源,涵盖服务器配置、网络传输、前端加载、数据库性能及第三方资源管理,结合真实案例与数据,提出可落地的优化框架,助力提升页面性能、降低延迟,显著改善用户体验与业务收益。转载链接:https://www.ffy.com/latest-news/1917104121064722432
494 10
|
3月前
|
数据采集 存储 API
Scrapy框架实战:大规模爬取华为应用市场应用详情数据
Scrapy框架实战:大规模爬取华为应用市场应用详情数据
|
4月前
|
数据采集 Web App开发 存储
用Python的Requests+BeautifulSoup爬取微博热搜榜及话题内容
用Python的Requests+BeautifulSoup爬取微博热搜榜及话题内容
|
3月前
|
数据采集 运维 监控
构建企业级Selenium爬虫:基于隧道代理的IP管理架构
构建企业级Selenium爬虫:基于隧道代理的IP管理架构
|
5月前
|
存储 Web App开发 前端开发
Python + Requests库爬取动态Ajax分页数据
Python + Requests库爬取动态Ajax分页数据
|
4月前
|
数据采集 存储 NoSQL
Scrapy 框架实战:构建高效的快看漫画分布式爬虫
Scrapy 框架实战:构建高效的快看漫画分布式爬虫