
使用 Scrapy 和 Splash 抓取无限滚动内容
在这里,我将带你了解如何将 Scrapy 与 Splash 配置起来,应对无限滚动,获取动态内容,并处理常见的抓取挑战。让我们一步步深入看看具体怎么做。
为什么要抓取无限滚动内容?
无限滚动常见于电商网站、社交媒体平台和新闻聚合网站,当用户向下滚动时会加载更多内容。基础的 HTML 解析器不足以抓取这类网站,因为新内容只有在滚动操作之后才会出现。像 Splash 这样的无头浏览器就能派上用场,它可以帮助模拟滚动并加载动态内容,从而实现有效抓取。
Scrapy 和 Splash 基础
Scrapy 是一个用 Python 编写的开源网页抓取框架,以速度快、简单易用和可扩展性强而闻名。它提供了一种结构化方式来组织代码并从网站中提取信息。
Splash 是专门为网页抓取而构建的无头浏览器。它可以执行 JavaScript 并渲染 HTML 页面。当它以 Scrapy-Splash 的形式与 Scrapy 集成时,我们就可以抓取依赖 JavaScript 加载内容的网站,例如带有无限滚动的网站。
跳过无限滚动抓取------直接获取数据
下面列出了顶级数据集网站,如果你的项目过于复杂且不想浪费时间,可以看看这些网站:
-
Bright Data --- 覆盖各行业的可定制和预构建数据集。
-
Statista --- 面向商业和研究的海量统计数据与报告。
-
Datarade --- 来自多家提供商的优质数据产品市场。
-
AWS Data Exchange --- 与 AWS 服务集成的第三方数据集。
-
Zyte --- 根据业务需求定制的网页抓取和自定义数据集。
-
Data & Sons --- 用于买卖各类数据集的开放市场。
-
Coresignal --- 拥有大量职位相关数据的人力分析服务。
-
Oxylabs --- 专门的公司数据和网页抓取服务。
-
Bloomberg Enterprise Data Catalog --- 面向企业使用的金融数据。
-
Kaggle --- 面向数据科学的免费公共数据集和工具。
步骤 1:将 Scrapy 与 Splash 配置起来
要开始将 Splash 与 Scrapy 一起使用,请按以下初始设置步骤操作。
1. 安装 Scrapy-Splash 打开终端并安装 scrapy-splash 包:
pip install scrapy-splash
2. 在 Docker 中运行 Splash 由于 Splash 需要 Docker 才能高效运行,请确保你的机器上已安装并正在运行 Docker。使用以下命令拉取 Splash Docker 镜像:
docker pull scrapinghub/splash
然后启动 Splash 服务器:
docker run -it -p 8050:8050 - rm scrapinghub/splash
现在,Splash 可在 http://localhost:8050, 访问,已准备好为你的 Scrapy 爬虫渲染 JavaScript。
步骤 2:编写用于滚动的 Lua 脚本
Splash 的 Lua 脚本功能允许你操控浏览器、向下滚动,并等待新内容加载。下面的 Lua 脚本会滚动到页面底部,等待内容加载,并多次重复这个过程。
用于滚动的 Lua 脚本
function main(splash, args)
splash:go(args.url)
splash:wait(args.wait)
local scroll_to = splash:jsfunc('window.scrollTo')
local get_body_height = splash:jsfunc([[
function() {
return document.body.scrollHeight;
}
]])
local scroll_count = 0
for _ = 1, args.max_scrolls do
scroll_count = scroll_count + 1
scroll_to(0, get_body_height())
splash:wait(args.scroll_delay)
end
return {
html = splash:html(),
scroll_count = scroll_count
}
end
在这个脚本中:
-
splash:go(args.url) 会加载目标 URL。
-
splash:wait(args.wait) 会暂停,以便初始页面元素加载完成。
-
for 循环会多次滚动页面,并在每次滚动后短暂等待(args.scroll_delay),让新内容加载出来。
步骤 3:在 Scrapy Spider 中集成 Lua 脚本
准备好 Lua 脚本后,下一步是设置一个 Scrapy spider 来执行它。这个 spider 会向目标站点发送 Splash 请求,并传入 Lua 脚本。
Spider 代码
import scrapy
from scrapy_splash import SplashRequest
class InfiniteScrollSpider(scrapy.Spider):
name = 'infinite_scroll_spider'
allowed_domains = ['example.com']
start_urls = ['http://example.com/target_page']
lua_script = """
function main(splash, args)
splash:go(args.url)
splash:wait(args.wait)
local scroll_to = splash:jsfunc('window.scrollTo')
local get_body_height = splash:jsfunc([[
function() {
return document.body.scrollHeight;
}
]])
local scroll_count = 0
for _ = 1, args.max_scrolls do
scroll_count = scroll_count + 1
scroll_to(0, get_body_height())
splash:wait(args.scroll_delay)
end
return {
html = splash:html(),
scroll_count = scroll_count
}
end
"""
def start_requests(self):
yield SplashRequest(
self.start_urls[0],
self.parse,
endpoint='execute',
args={
'lua_source': self.lua_script,
'wait': 2,
'scroll_delay': 1,
'max_scrolls': 8
}
)
def parse(self, response):
for item in response.css('.item-selector'):
yield {
'name': item.css('.name::text').get(),
'price': item.css('.price::text').get()
}
说明:
-
该 spider 会发送一个 SplashRequest,其中 lua_source 指向 Lua 脚本。
-
wait、scroll_delay 和 max_scrolls 等参数定义了脚本的滚动行为。
-
parse 函数会从每次滚动迭代中提取商品数据(名称和价格)。
步骤 4:处理分页和"加载更多"按钮
许多无限滚动页面会使用隐藏的"加载更多"按钮,当用户滚动到底部时触发。Splash 的 Lua 脚本可以在该按钮出现时点击"加载更多"按钮来处理这种情况。
用于"加载更多"按钮的修改版 Lua 脚本
function main(splash, args)
splash:go(args.url)
splash:wait(args.wait)
local scroll_to = splash:jsfunc('window.scrollTo')
local get_body_height = splash:jsfunc([[
function() {
return document.body.scrollHeight;
}
]])
local scroll_count = 0
for _ = 1, args.max_scrolls do
scroll_count = scroll_count + 1
scroll_to(0, get_body_height())
splash:wait(args.scroll_delay)
local load_more = splash:select('.load-more-button')
if load_more then
load_more:mouse_click()
splash:wait(1)
end
end
return splash:html()
end
这里,load_more 变量会使用其选择器定位"加载更多"按钮。如果找到该按钮,它会模拟点击,等待内容加载,然后重复滚动。
步骤 5:绕过反机器人防护
无限滚动页面通常会有反机器人措施,包括 CAPTCHA、速率限制和 IP 封禁。绕过这些措施的技术包括:
-
代理轮换:更换 IP 地址可以防止被检测到。ZenRows 和 ScraperAPI 等服务提供 IP 轮换,并且设置成本很低。
-
User-Agent 轮换:通过在每次请求中随机化你的 User-Agent 字符串来避免被检测。
-
无头浏览器:Splash 以无头模式运行,使你的请求看起来更像真实用户流量。
下面是如何使用 ZenRows 实现动态代理的示例:
import scrapy
class InfiniteScrollSpider(scrapy.Spider):
name = 'proxy_spider'
allowed_domains = ['example.com']
def start_requests(self):
proxy = 'http://<YOUR_ZENROWS_API_KEY>@api.zenrows.com:8001'
url = 'http://example.com/target_page'
yield scrapy.Request(
url,
callback=self.parse,
meta={'proxy': proxy}
)
def parse(self, response):
# parsing logic
此示例将 Scrapy 配置为对每个请求使用 ZenRows 作为代理。
步骤 6:整合所有内容
下面是一个无限滚动 Scrapy spider 的完整代码,包含 Splash 集成、Lua 脚本和代理轮换:
import scrapy
from scrapy_splash import SplashRequest
class FullInfiniteScrollSpider(scrapy.Spider):
name = 'full_infinite_scroll'
allowed_domains = ['example.com']
start_urls = ['http://example.com/target_page']
lua_script = """
function main(splash, args)
splash:go(args.url)
splash:wait(args.wait)
local scroll_to = splash:jsfunc('window.scrollTo')
local get_body_height = splash:jsfunc([[
function() {
return document.body.scrollHeight;
}
]])
for _ = 1, args.max_scrolls do
scroll_to(0, get_body_height())
splash:wait(args.scroll_delay)
local load_more = splash:select('.load-more-button')
if load_more then
load_more:mouse_click()
splash:wait(1)
end
end
return splash:html()
end
"""
def start_requests(self):
yield SplashRequest(
self.start_urls[0],
self.parse,
endpoint='execute',
args={
'lua_source': self.lua_script,
'wait': 2,
'scroll_delay': 1,
'max_scrolls': 10
}
)
def parse(self, response):
for item in response.css('.item-selector'):
yield {
'name': item.css('.name::text').get(),
'price': item.css('.price::text').get()
}
结论
在抓取无限滚动页面时,我发现 Scrapy 与 Splash 组合非常有吸引力。这是一套很棒的配置,能让你轻松应对即使是最棘手的动态网站。借助 Splash,我可以处理 JavaScript 渲染,而 Scrapy 则擅长数据提取部分。Splash 的 Lua 脚本让你可以与页面上的元素交互,像用户滚动浏览一样加载更多内容。
有任何问题吗?请在评论里告诉我!
对其他网页抓取指南感兴趣?
想看更多精彩文章,请访问我的主页 --- Data Journal ❤️