网站首页 > 技术文章 正文
大多数现代网站都使用客户端 JavaScript 框架,例如 React、Vue 或 Angular。在没有服务器端渲染的情况下从动态网站抓取数据通常需要执行 JavaScript 代码。
我已经抓取了数百个网站,而且我总是使用 Scrapy。Scrapy 是一个流行的 Python 网页抓取框架。与其他 Python 抓取库(例如 Beautiful Soup)相比,Scrapy 帮助您根据一些最佳实践来构建代码。Scrapy 负责并发、收集统计数据、缓存、处理重试逻辑和许多其他问题。
在本文中,我比较了使用 Scrapy 执行 JavaScript 的最流行的解决方案,包括如何扩展无头浏览器,并介绍了与 ScrapingBee API 的开源集成以支持 JavaScript 和代理轮换。
使用 Scrapy 抓取动态网站
使用 Scrapy 抓取客户端呈现的网站曾经很痛苦。我经常自己检查浏览器网络工具上的 API 请求并从 JavaScript 变量中提取数据。虽然这些 hack 可能适用于某些网站,但我发现这些代码比传统的 XPATH 更难理解和维护。但要直接从 HTML 中抓取客户端数据,您首先需要执行 JavaScript 代码。
用于无头浏览器的 Scrapy 中间件
无头浏览器是没有图形用户界面的网络浏览器。我使用了三个库来使用 Scrapy 执行 JavaScript:scrapy-selenium、scrapy-splash 和 scrapy-scrapingbee。
所有三个库都集成为 Scrapy下载器中间件。一旦在您的项目设置中进行配置,您就不会从您的spiders产生一个正常的 Scrapy 请求,而是产生一个 SeleniumRequest、SplashRequest 或 ScrapingBeeRequest。
使用 Selenium 在 Scrapy 中执行
在本地,您可以使用带有scrapy-selenium中间件的 Scrapy 与无头浏览器交互。Selenium 是一个与浏览器交互的框架,通常用于测试应用程序、网页抓取和截屏。
Selenium 需要一个Web 驱动程序来与浏览器交互。例如,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
}
在你的spiders中,你可以产生一个 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,截取屏幕截图的屏幕截图和用于执行自定义 JavaScript 脚本的脚本。
在生产中,scrapy-selenium 的主要问题是没有简单的方法来设置Selenium 网格以在远程机器上运行多个浏览器实例。接下来,我将比较两种使用 Scrapy 大规模执行 JavaScript 的解决方案。
使用 Splash 在 Scrapy 中执行 JavaScript
Splash是一种带有 API 的 Web 浏览器即服务。它由 Scrapy 的主要贡献者 Scrapinghub 维护,并通过scrapy-splash中间件与 Scrapy 集成。它也可以由 Scrapinghub 托管。
Splash 创建于 2013 年,在无头 Chrome 和其他主要无头浏览器于 2017 年发布之前。从那时起,其他流行的项目(如 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 中编码才能与网站交互。由于这两个问题,在我的最后一个抓取项目中,我决定为 ScrapingBee API 创建一个中间件。
使用 ScrapingBee 在 Scrapy 中执行 JavaScript
ScrapingBee是一个 Web 抓取 API,可以为您处理无头浏览器和代理。ScrapingBee 使用最新的无头 Chrome 版本并支持 JavaScript 脚本。
与其他两个中间件一样,您可以简单地使用 pip 安装scrapy-scrapingbee中间件。
pip install scrapy-scrapingbee
首先,您需要创建一个 ScrapingBee 帐户以获取 API 密钥。然后你可以根据你的项目设置中的 ScrapingBee 计划添加下载器中间件并设置并发。
SCRAPINGBEE_API_KEY = 'REPLACE-WITH-YOUR-API-KEY'
DOWNLOADER_MIDDLEWARES = {
'scrapy_scrapingbee.ScrapingBeeMiddleware': 725,
}
CONCURRENT_REQUESTS = 1
然后你可以从 ScrapingBeeSpider 继承你的蜘蛛并产生一个 ScrapingBeeRequest。
from scrapy_scrapingbee import ScrapingBeeSpider, ScrapingBeeRequest
class HttpbinSpider(ScrapingBeeSpider):
name = 'httpbin'
start_urls = [
'https://httpbin.org',
]
def start_requests(self):
for url in self.start_urls:
yield ScrapingBeeRequest(url)
def parse(self, response):
…
ScrapingBeeRequest 采用可选的 params 参数来执行 js_snippet,在返回响应之前设置自定义等待,或者使用 wait_for 在 HTML 代码中等待 CSS 或 XPATH 选择器。
在某些网站中,当您滚动浏览页面时,HTML 会异步加载。您可以使用下面的 JavaScript 片段滚动到页面末尾。
JS_SNIPPET = 'window.scrollTo(0, document.body.scrollHeight);'
yield ScrapingBeeRequest(url, params={
'js_snippet': JS_SNIPPET,
# 'wait': 3000,
# 'wait_for': '#swagger-ui',
})
ScrapingBee 收集了其他常见的 JavaScript 片段,以便与ScrapingBee 文档中的网站进行交互。
在幕后,scraping-scrapingbee 中间件将原始请求转换为转发到 ScrapingBee API 的请求,并对 URL 查询字符串中的每个参数进行编码。API 端点记录在您的 Scrapy 日志中,并且 api_key 被 ScrapingBeeSpider 隐藏。
2020-06-22 12:32:07 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://app.scrapingbee.com/api/v1/?api_key=HIDDEN&url=https://httpbin.org&js_snippet=d2luZG93LnNjcm9sbFRvKDAsIGRvY3VtZW50LmJvZHkuc2Nyb2xsSGVpZ2h0KTs=&wait=3000> (referer: None)
在您的蜘蛛的解析方法中,中间件将 response.url 解析为传递给 ScrapingBeeRequest 的原始 URL。
def parse(self, response):
assert response.url == 'https://httpbin.org'
使用 ScrapingBee 的另一个优点是您可以使用以下参数访问不同国家的住宅代理和开箱即用的代理轮换。
yield ScrapingBeeRequest(url, params={
'premium_proxy': True,
'country_code': 'fr',
})
使用 Scrapy 缓存和并发来更快地抓取
Scrapy 在底层使用了 Twisted,这是一个异步网络框架。Twisted 使 Scrapy 快速并且能够同时抓取多个页面。但是,要执行 JavaScript 代码,您需要使用真正的浏览器或无头浏览器来解析请求。无头浏览器有两个挑战:它们速度较慢且难以扩展。
在无头浏览器中执行 JavaScript 并等待所有网络调用每页可能需要几秒钟。抓取多个页面时,它会使抓取器显着变慢。希望 Scrapy 提供缓存来加速生产运行的开发和并发请求。
在本地,在开发爬虫时,您可以使用 Scrapy 的内置缓存系统。由于响应存储在计算机上的隐藏文件夹 .scrapy/httpcache 中,它将使后续运行更快。您可以在项目设置中激活 HttpCacheMiddleware:
HTTPCACHE_ENABLED = True
无头浏览器的另一个问题是它们会为每个请求消耗内存。在生产环境中,您需要一个可以处理多个浏览器的环境。要同时发出多个请求,您可以修改项目设置:
CONCURRENT_REQUESTS = 1
使用 ScrapingBee 时,请记住根据您的 ScrapingBee 计划设置并发。
结论
我比较了三个使用 Scrapy 渲染和执行 JavaScript 的 Scrapy 中间件。Selenium 允许您在所有主要的无头浏览器中使用 Python 与 Web 浏览器进行交互,但可能难以扩展。Splash 可以使用 Docker 在本地运行或部署到 Scrapinghub,但依赖于自定义浏览器实现,您必须在 Lua 中编写脚本。ScrapingBee 使用最新的 Chrome 无头浏览器,允许您在 JavaScript 中执行自定义脚本,并为最难抓取的网站提供代理轮换。
scrapy-selenium | scrapy-splash | scrapy-scrapingbee | |
本地运行 | 是的 | 是的,使用 Docker | 不 |
远程扩展 | 使用Selenium Grid | 使用 Scrapinghub | 使用ScrapingBee |
脚本语言 | JavaScript、Python | lua | JavaScript |
浏览器支持 | Chrome, Firefox, Edge, Safari | Splash | Latest Headless Chrome |
代理轮换 | 不 | 由另一项服务 Crawlera 提供 | 是的,由相同的中间件提供 |
猜你喜欢
- 2024-10-24 Service Workers - JS API 简介(servicedescriptor)
- 2024-10-24 web性能优化的15条实用技巧(web应用性能优化思路)
- 2024-10-24 如何在 Service Worker 重新启动时重用信息
- 2024-10-24 Python在selenium里面注入JavaScript程序的方法
- 2024-10-24 requireJS 实战(requirejs define)
- 2024-10-24 面试妥了!2020 爬虫面试题目合集(爬虫面试经历)
- 2024-10-24 Nest.js 从零到壹系列(一):项目创建&路由设置&模块
- 2024-10-24 JS小知识,分享 7 个高频的工具函数,也许你用的上
- 2024-10-24 如何使用Playwright优化测试性能(play—player)
- 2024-10-24 JavaScript开发人员都应知道的异步迭代,你会了吗?
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)