大多数现代网站都使用客户端的JavaScript框架,如React、Vue或Angular。在没有服务器端渲染的情况下,从动态网站上爬取 数据往往需要执行JavaScript代码。
我已经爬取 了数百个网站,我总是使用Scrapy。Scrapy是一个流行的Python网页爬取框架。与其他Python爬取库(如Beautiful Soup)相比,Scrapy可以使你根据一些最佳实践来构造你的代码。作为交换,Scrapy照顾到了并发性、收集统计资料、缓存、处理重审逻辑和其他许多方面。
如果你是Scrapy的新手,你可能应该从阅读这个教程开始,它将教会你Scrapy的所有基础知识。
在这篇文章中,我比较了用Scrapy执行JavaScript的最流行的解决方案,如何扩展无头浏览器,以支持JavaScript和代理轮换。
用Scrapy爬取动态网站
用Scrapy爬取客户端渲染的网站过去是很痛苦的。我经常发现自己在浏览器网络工具上检查API请求,并从JavaScript变量中提取数据。虽然这些黑客手段在某些网站上可能有效,但我发现这些代码比传统的XPATHs更难理解和维护。但要直接从HTML中爬取客户端数据,你首先需要执行JavaScript代码。
用于无头浏览器的Scrapy中间件
无头浏览器是一种没有图形用户界面的网络浏览器。我使用了三个库来用Scrapy执行JavaScript:scrapy-selenium、scrapy-splash。
这三个库都被整合为Scrapy下载器的中间件。一旦在你的项目设置中配置好,你的蜘蛛就不会产生正常的Scrapy请求,而是产生一个SeleniumRequest 或 SplashRequest。
用Selenium在Scrapy中执行JavaScript
在本地,你可以通过scrapy-selenium中间件与无头浏览器进行交互。Selenium是一个与浏览器交互的框架,通常用于测试应用程序、网络爬取和截图。
Selenium需要一个网络驱动来与浏览器互动。例如,Firefox需要你安装geckodriver。然后你可以在你的Scrapy项目设置中配置Selenium。
from shutil import which SELENIUM_DRIVER_NAME = 'firefox' SELENIUM_DRIVER_EXECUTABLE_PATH = which('geckodriver') SELENIUM_DRIVER_ARGUMENTS=['-headless'] DOWNLOADER_MIDDLEWARES = { 'scrapy_selenium.SeleniumMiddleware': 800 }
在你的蜘蛛中,你可以产生一个SeleniumRequest:
from scrapy_selenium import SeleniumRequest yield SeleniumRequest(url, callback=self.parse)
Selenium允许你用Python和JavaScript与浏览器交互。驱动对象可以从Scrapy响应中访问。有时候,在你点击一个按钮之后,检查HTML代码是很有用的。在本地,你可以用ipdb调试器设置一个断点来检查HTML响应。
def parse(self, response): driver = response.request.meta['driver'] driver.find_element_by_id('show-price').click() import ipdb; ipdb.set_trace() print(driver.page_source)
否则,Scrapy XPATH和CSS选择器可以从响应对象中访问,以便从HTML中选择数据。
def parse(self, response): title = response.selector.xpath( '//title/@text' ).extract_first()
SeleniumRequest需要一些额外的参数,例如在返回响应前等待的wait_time,等待HTML元素的wait_until,拍摄截图的screenshot和执行自定义JavaScript脚本的script。
在生产中,scrapy-selenium的主要问题是,没有琐碎的方法来设置Selenium网格,让多个浏览器实例在远程机器上运行。接下来,我将比较两种用Scrapy大规模执行JavaScript的解决方案。
用Splash在Scrapy中执行JavaScript
Splash是一个带有API的网络浏览器服务。它由Scrapinghub维护,是Scrapy的主要贡献者,通过scrapy-splash中间件与Scrapy集成。它也可以由Scrapinghub托管。
Splash创建于2013年,在2017年无头的Chrome和其他主要无头浏览器发布之前。从那时起,其他流行的项目,如PhantomJS,已经停止使用,转而使用Firefox、Chrome和Safari无头浏览器。
你可以用Docker在本地运行一个Splash的实例。
docker run -p 8050:8050 scrapinghub/splash`
配置Splash中间件需要添加多个中间件并在项目设置中改变HttpCompressionMiddleware的默认优先级。
SPLASH_URL = 'http://192.168.59.103:8050' DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } SPIDER_MIDDLEWARES = { 'scrapy_splash.SplashDeduplicateArgsMiddleware': 100, } DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
然后你可以产生一个带有可选的参数wait和lua_source的SplashRequest:
from scrapy_splash import SplashRequest yield SplashRequest(url, callback=self.parse, args={ 'wait': 0.5, 'lua_source': script })
Splash是一个流行的解决方案,因为它已经推出了很长时间,但它有两个主要问题:它使用一个自定义的无头浏览器,并且需要用Lua编码来与网站互动。
利用Scrapy的缓存和并发性来加快爬取 速度
Scrapy在引擎盖下使用Twisted,这是一个异步网络框架。Twisted使得Scrapy速度很快,并且能够同时爬取多个页面。然而,要执行JavaScript代码,你需要用一个真正的浏览器或无头浏览器来解决请求。无头浏览器有两个挑战:它们速度较慢且难以扩展。
在无头浏览器中执行JavaScript并等待所有的网络调用,每个页面可能需要几秒钟。当爬取 多个页面时,会使爬取器的速度明显变慢。希望Scrapy能提供缓存,以加快开发和生产运行的并发请求。
在本地,当开发一个刮削器时,你可以使用Scrapy的内置缓存系统。这将使后续的运行更快,因为响应被存储在你的计算机上的一个隐藏文件夹.scrapy/httpcache中。你可以在你的项目设置中激活HttpCacheMiddleware。
HTTPCACHE_ENABLED = True
无头浏览器的另一个问题是,它们对每个请求都会消耗内存。在生产中,你需要一个可以处理多个浏览器的环境。为了同时进行多个请求,你可以修改你的项目设置。
CONCURRENT_REQUESTS = 1[文中代码源自Scrapingbee]
总 结
我比较了两个Scrapy中间件,用Scrapy渲染和执行JavaScript。Selenium允许你在所有主要的无头浏览器中使用Python与网络浏览器交互,但很难扩展。Splash可以用Docker在本地运行,也可以部署到Scrapinghub,但它依赖于自定义的浏览器实现,而且你必须用Lua编写脚本。
scrapy-selenium | scrapy-splash | |
---|---|---|
在当地运行 | 是 | 是的,使用Docker |
远程扩展 | 使用Selenium网格 | 与Scrapinghub合作 |
脚本语言 | JavaScript, Python | Lua |
浏览器支持 | Chrome, Firefox, Edge, Safari | Splash |
代理IP轮换 | 没有 | 由另一项服务Crawlera提供 |