in

如何用Scrapy爬取JavaScript生成的页面?

如何用Scrapy爬取JavaScript生成的页面

大多数现代网站都使用客户端的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-seleniumscrapy-splash
在当地运行是的,使用Docker
远程扩展使用Selenium网格与Scrapinghub合作
脚本语言JavaScript, PythonLua
浏览器支持Chrome, Firefox, Edge, SafariSplash
代理IP轮换没有由另一项服务Crawlera提供

 

blank

Written by 爬取 大师

阿里P12级别选手,能够突破各种反爬, 全能的爬取大师,擅长百万级的数据抓取!没有不能爬,只有你不敢想,有爬取项目可以联系我邮箱 [email protected] (带需求和预算哈, 不然多半不回复)