scrapy 可以用来从使用 AJAX 的网站抓取动态内容吗?
- 2024-11-29 08:42:00
- admin 原创
- 113
问题描述:
我最近在学习 Python,并尝试着开发一个网络爬虫。它没什么特别的;它的唯一目的是从博彩网站获取数据并将这些数据放入 Excel 中。
大多数问题都是可以解决的,我正忙得不可开交。然而,有一个问题让我遇到了巨大的障碍。如果一个网站加载了一张赛马表并列出了当前的投注价格,则此信息不在任何源文件中。线索是这些数据有时是实时的,数字显然是从某个远程服务器更新的。我电脑上的 HTML 只是有一个漏洞,他们的服务器正在推送我需要的所有有趣数据。
现在我对动态网络内容的经验很少,所以这件事是我很难理解的。
我认为 Java 或 Javascript 是关键,这个经常出现。
抓取工具只是一个赔率比较引擎。有些网站有 API,但我需要这个来处理没有 API 的网站。我使用的是 Python 2.7 的 scrapy 库
如果这个问题太开放,我深表歉意。简而言之,我的问题是:如何使用 scrapy 来抓取这些动态数据,以便我可以使用?以便我可以实时抓取这些投注赔率数据?
另请参阅:如何使用 Python 抓取包含动态内容(由 JavaScript 创建)的页面?了解一般情况。
解决方案 1:
这是一个 scrapy
使用 AJAX 请求的简单示例。让我们看看网站rubin-kazan.ru。
所有消息都通过 AJAX 请求加载。我的目标是获取这些消息及其所有属性(作者、日期等):
当我分析页面源代码时,我看不到所有这些消息,因为该网页使用了 AJAX 技术。但我可以使用 Mozilla Firefox 中的 Firebug(或其他浏览器中的等效工具)来分析在网页上生成消息的 HTTP 请求:
它不会重新加载整个页面,而只会重新加载包含消息的页面部分。为此,我点击底部的任意数量的页面:
我观察了负责消息正文的 HTTP 请求:
完成后,我分析了请求的标头(我必须引用这个 URL,我将从 var 部分的源页面中提取,请参阅下面的代码):
以及请求的表单数据内容(HTTP 方法是“Post”):
响应的内容(JSON 文件)如下:
它提供了我正在寻找的所有信息。
从现在起,我必须在 scrapy 中实现所有这些知识。为此,我们来定义一下 spider:
class spider(BaseSpider):
name = 'RubiGuesst'
start_urls = ['http://www.rubin-kazan.ru/guestbook.html']
def parse(self, response):
url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem,
formdata={'page': str(page + 1), 'uid': ''})
def RubiGuessItem(self, response):
json_file = response.body
在函数中parse
,我有第一个请求的响应。RubiGuessItem
我有包含所有信息的 JSON 文件。
解决方案 2:
基于 Webkit 的浏览器(如 Google Chrome 或 Safari)具有内置的开发者工具。您可以在 Chrome 中打开它Menu->Tools->Developer Tools
。该Network
选项卡允许您查看有关每个请求和响应的所有信息:
在图片的底部,您可以看到我已经将请求过滤为XHR
- 这些是由 javascript 代码发出的请求。
提示:每次加载页面时都会清除日志,图片下方的黑点按钮会保存日志。
分析完请求和响应后,您可以模拟来自网络爬虫的这些请求并提取有价值的数据。在许多情况下,获取数据比解析 HTML 更容易,因为该数据不包含表示逻辑,并且已格式化为可通过 javascript 代码访问。
Firefox 有类似的扩展,称为firebug。有些人会认为 firebug 更强大,但我喜欢 webkit 的简单性。
解决方案 3:
很多时候,我们在抓取时会遇到问题,页面上呈现的内容是用 Javascript 生成的,因此 scrapy 无法对其进行抓取(例如 ajax 请求、jQuery 疯狂)。
但是,如果您将 Scrapy 与 Web 测试框架 Selenium 一起使用,那么我们就可以抓取普通 Web 浏览器中显示的任何内容。
需要注意的事项:
您必须安装 Python 版本的 Selenium RC 才能使其工作,并且必须正确设置 Selenium。此外,这只是一个模板爬虫。您可以更疯狂、更高级地使用这些东西,但我只想展示基本思想。按照现在的代码,您将对任何给定的 URL 执行两个请求。一个请求由 Scrapy 发出,另一个请求由 Selenium 发出。我相信有办法解决这个问题,这样您就可以让 Selenium 执行唯一的请求,但我没有费心去实现它,通过执行两个请求,您也可以使用 Scrapy 爬取页面。
这非常强大,因为现在您可以抓取整个渲染的 DOM,并且仍然可以使用 Scrapy 中所有不错的抓取功能。当然,这会使抓取速度变慢,但根据您对渲染的 DOM 的需求程度,等待可能是值得的。
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request
from selenium import selenium
class SeleniumSpider(CrawlSpider):
name = "SeleniumSpider"
start_urls = ["http://www.domain.com"]
rules = (
Rule(SgmlLinkExtractor(allow=('.html', )), callback='parse_page',follow=True),
)
def __init__(self):
CrawlSpider.__init__(self)
self.verificationErrors = []
self.selenium = selenium("localhost", 4444, "*chrome", "http://www.domain.com")
self.selenium.start()
def __del__(self):
self.selenium.stop()
print self.verificationErrors
CrawlSpider.__del__(self)
def parse_page(self, response):
item = Item()
hxs = HtmlXPathSelector(response)
#Do some XPath selection with Scrapy
hxs.select('//div').extract()
sel = self.selenium
sel.open(response.url)
#Wait for javscript to load in Selenium
time.sleep(2.5)
#Do some crawling of javascript created content with Selenium
sel.get_text("//div")
yield item
# Snippet imported from snippets.scrapy.org (which no longer works)
# author: wynbennett
# date : Jun 21, 2011
参考:http://snipplr.com/view/66998/
解决方案 4:
另一个解决方案是实现下载处理程序或下载处理程序中间件。(有关下载器中间件的更多信息,请参阅scrapy 文档)以下是使用 selenium 和 headless phantomjs webdriver 的示例类:
1)在脚本中定义类middlewares.py
。
from selenium import webdriver
from scrapy.http import HtmlResponse
class JsDownload(object):
@check_spider_middleware
def process_request(self, request, spider):
driver = webdriver.PhantomJS(executable_path='D:phantomjs.exe')
driver.get(request.url)
return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))
2)JsDownload()
将类添加到变量DOWNLOADER_MIDDLEWARE
中settings.py
:
DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}
3)整合HTMLResponse
within your_spider.py
。解码响应主体将获得所需的输出。
class Spider(CrawlSpider):
# define unique name of spider
name = "spider"
start_urls = ["https://www.url.de"]
def parse(self, response):
# initialize items
item = CrawlerItem()
# store data as items
item["js_enabled"] = response.body.decode("utf-8")
可选插件:
我希望能够告诉不同的蜘蛛使用哪个中间件,因此我实现了这个包装器:
def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
msg = '%%s %s middleware step' % (self.__class__.__name__,)
if self.__class__ in spider.middleware:
spider.log(msg % 'executing', level=log.DEBUG)
return method(self, request, spider)
else:
spider.log(msg % 'skipping', level=log.DEBUG)
return None
return wrapper
为了使包装器正常工作,所有蜘蛛必须至少具备:
middleware = set([])
包含中间件:
middleware = set([MyProj.middleware.ModuleName.ClassName])
优点:
以这种方式实现而不是在蜘蛛程序中实现它的主要优点是,您最终只需发出一个请求。例如,在 A T 的解决方案中:下载处理程序处理请求,然后将响应交给蜘蛛程序。然后,蜘蛛程序在其 parse_page 函数中发出一个全新的请求——这是对相同内容的两个请求。
解决方案 5:
我正在使用自定义下载器中间件,但对它不太满意,因为我无法使缓存与其一起工作。
更好的方法是实现自定义下载处理程序。
这里有一个可行的示例。它看起来像这样:
# encoding: utf-8
from __future__ import unicode_literals
from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure
class PhantomJSDownloadHandler(object):
def __init__(self, settings):
self.options = settings.get('PHANTOMJS_OPTIONS', {})
max_run = settings.get('PHANTOMJS_MAXRUN', 10)
self.sem = defer.DeferredSemaphore(max_run)
self.queue = queue.LifoQueue(max_run)
SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)
def download_request(self, request, spider):
"""use semaphore to guard a phantomjs pool"""
return self.sem.run(self._wait_request, request, spider)
def _wait_request(self, request, spider):
try:
driver = self.queue.get_nowait()
except queue.Empty:
driver = webdriver.PhantomJS(**self.options)
driver.get(request.url)
# ghostdriver won't response when switch window until page is loaded
dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
dfd.addCallback(self._response, driver, spider)
return dfd
def _response(self, _, driver, spider):
body = driver.execute_script("return document.documentElement.innerHTML")
if body.startswith("<head></head>"): # cannot access response header in Selenium
body = driver.execute_script("return document.documentElement.textContent")
url = driver.current_url
respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
resp = respcls(url=url, body=body, encoding="utf-8")
response_failed = getattr(spider, "response_failed", None)
if response_failed and callable(response_failed) and response_failed(resp, driver):
driver.close()
return defer.fail(Failure())
else:
self.queue.put(driver)
return defer.succeed(resp)
def _close(self):
while not self.queue.empty():
driver = self.queue.get_nowait()
driver.close()
假设您的抓取工具名为“scraper”。如果您将上述代码放在“scraper”文件夹根目录下的 handlers.py 文件中,则可以将其添加到您的 settings.py 中:
DOWNLOAD_HANDLERS = {
'http': 'scraper.handlers.PhantomJSDownloadHandler',
'https': 'scraper.handlers.PhantomJSDownloadHandler',
}
瞧,JS 解析了 DOM,带有 scrapy 缓存、重试等。
解决方案 6:
如何使用 scrapy 来抓取这些动态数据以便我可以使用它?
我很奇怪为什么没有人发布仅使用 Scrapy 的解决方案。
查看 Scrapy 团队的博客文章SCRAPING INFINITE SCROLLING PAGES。示例抓取了使用无限滚动的http://spidyquotes.herokuapp.com/scroll网站。
我们的想法是使用浏览器的开发人员工具并注意 AJAX 请求,然后根据该信息创建 Scrapy 请求。
import json
import scrapy
class SpidyQuotesSpider(scrapy.Spider):
name = 'spidyquotes'
quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
start_urls = [quotes_base_url % 1]
download_delay = 1.5
def parse(self, response):
data = json.loads(response.body)
for item in data.get('quotes', []):
yield {
'text': item.get('text'),
'author': item.get('author', {}).get('name'),
'tags': item.get('tags'),
}
if data['has_next']:
next_page = data['page'] + 1
yield scrapy.Request(self.quotes_base_url % next_page)
解决方案 7:
从外部 URL 生成的数据是 API 以 POST 方法调用 HTML 响应。
import scrapy
from scrapy.crawler import CrawlerProcess
class TestSpider(scrapy.Spider):
name = 'test'
def start_requests(self):
url = 'https://howlongtobeat.com/search_results?page=1'
payload = "queryString=&t=games&sorthead=popular&sortd=0&plat=&length_type=main&length_min=&length_max=&v=&f=&g=&detail=&randomize=0"
headers = {
"content-type":"application/x-www-form-urlencoded",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
}
yield scrapy.Request(url,method='POST', body=payload,headers=headers,callback=self.parse)
def parse(self, response):
cards = response.css('div[class="search_list_details"]')
for card in cards:
game_name = card.css('a[class=text_white]::attr(title)').get()
yield {
"game_name":game_name
}
if __name__ == "__main__":
process =CrawlerProcess()
process.crawl(TestSpider)
process.start()
解决方案 8:
我认为 2022 年应该提及一些更现代的替代方案,并且我想列出该问题中更受欢迎的答案中讨论的方法的一些优缺点。
最佳答案和其他几个答案讨论了使用浏览器
dev tools
或数据包捕获软件来尝试识别响应中的模式url
,并尝试重新构建它们以用作scrapy.Request
。
* 优点:在我看来,这仍然是最好的选择,并且当它可用时,它比传统方法(即使用`xpath`和`css`选择器从 HTML 中提取内容)更快,而且通常更简单。
* 缺点:不幸的是,这仅在一小部分动态网站上可用,并且网站通常都有安全措施使使用此策略变得困难。
使用
Selenium Webdriver
前面答案中多次提到的另一种方法。
* 优点:易于实现,并集成到 scrapy 工作流中。此外,还有大量示例,如果您使用第三方扩展,则几乎不需要配置,例如`scrapy-selenium`
* 缺点:速度很慢!scrapy 的主要功能之一是异步工作流,因此可以在几秒钟内轻松抓取数十甚至数百个页面。使用 selenium 可以大大减少这一时间。
有两种新方法绝对值得考虑,scrapy-splash
和scrapy-playwright
。
scrapy-splash:
一个 scrapy 插件,将由scrapy 开发人员创建和维护的 javascript 渲染服务splash集成到 scrapy 工作流中。可以使用 从 pypi 安装该插件
pip3 install scrapy-splash
,而 splash 需要在自己的进程中运行,最容易从 docker 容器运行。
scrapy-剧作家:
Playwright 是一种浏览器自动化工具,类似于
selenium
,但没有使用 selenium 所带来的速度严重下降。Playwright 可以毫无问题地融入异步 scrapy 工作流,发送请求的速度与单独使用 scrapy 一样快。它也比 selenium 更容易安装和集成。该scrapy-playwright
插件也由 scrapy 的开发人员维护,通过 pypi 安装后,就像在终端中pip3 install scrapy-playwright
运行一样简单。playwright install
每个插件的 github 页面https://github.com/scrapy-plugins/scrapy-playwright和https://github.com/scrapy-plugins/scrapy-splash上可以找到更多详细信息和许多示例。
ps 根据我的经验,这两个项目在 Linux 环境中运行得更好。对于 Windows 用户,我建议将其与Windows Subsystem for Linux (wsl)一起使用。
解决方案 9:
是的,Scrapy 可以抓取动态网站,通过 JavaScript 呈现的网站。
有两种方法可以抓取此类网站。
你可以使用它
splash
来渲染 Javascript 代码,然后解析渲染后的 HTML。你可以在这里找到文档和项目Scrapy splash、git如前所述,通过监控
network calls
,是的,您可以找到获取数据的 API 调用,并在您的 scrapy spider 中模拟该调用可能会帮助您获取所需的数据。
解决方案 10:
我使用 Selenium 和 Firefox Web 驱动程序处理 ajax 请求。如果您需要将爬虫程序作为守护进程,那么速度就没那么快,但比任何手动解决方案都要好得多。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)