Scrapy可以用来从使用AJAX的网站上抓取dynamic内容吗?
我最近一直在学习Python,并且正在帮助我build立一个networking刮板。 这根本就不是什么花哨的东西; 它的唯一目的是从一个博彩网站获取数据,并将这些数据存入Excel中。
大部分的问题都是可以解决的,而且我有一个很好的小混乱。 不过,我在一个问题上遇到了很大的障碍。 如果一个网站载入一张马匹表并列出当前的博彩价格,这个信息不在任何源文件中。 线索是这个数据有时是活的,数字从一些远程服务器显然更新。 在我的电脑上的HTML只是有一个漏洞,他们的服务器正在推动我所需要的所有有趣的数据。
现在,我对dynamic网页内容的使用经验很less,所以这件事情让我头脑不清。
我认为Java或Javascript是一个关键,这经常popup。
刮板只是一个赔率比较引擎。 有些网站有API,但是我不需要这些API。 我正在Python 2.7中使用scrapy库
如果这个问题太开放了,我会道歉的。 简而言之,我的问题是:scrapy如何被用来刮掉这个dynamic数据,以便我可以使用它? 这样我可以实时刮这个赔率数据吗?
欢呼的人:)
基于Webkit的浏览器(如Google Chrome或Safari)具有内置的开发者工具。 在Chrome中,您可以打开Menu->Tools->Developer Tools
。 “ Network
选项卡允许您查看有关每个请求和响应的所有信息:
在图片的底部,你可以看到我已经过滤了XHR
请求 – 这些是由JavaScript代码所做的请求。
提示:每次加载页面时都会清除日志,在图片的底部,黑点button将保留日志。
分析请求和响应后,您可以模拟来自您networking爬虫的这些请求并提取有价值的数据。 在许多情况下,获取数据比parsingHTML更容易,因为该数据不包含表示逻辑,并且被格式化为可由JavaScript代码访问。
Firefox有类似的扩展名,它被称为萤火虫 。 有人会认为,萤火虫更强大,但我喜欢webkit的简单。
这是一个使用scrapy和ajax请求的简单例子。 让看到该网站http://www.rubin-kazan.ru/guestbook.html所有消息都加载了一个Ajax请求。; 我的目标是获取这个消息的所有属性(作者,date,…)。
当我分析页面的源代码时,我看不到所有这些消息,因为网页使用ajax技术。 但是我可以用Mozila Firefox中的Firebug(或其他浏览器中的类比工具)来分析在网页上生成消息的Http请求。
为此,我不重新加载所有页面,而只是包含消息的页面部分。 为此,我点击底部的任意数量的页面 并观察负责消息体的HTTP请求
完成后,我分析请求的标题(我必须引用这个URL我将从var部分的源页面中提取,请参阅下面的代码)。
和请求的表单数据内容(Http方法是“Post”)
和响应的内容,这是一个Json文件,
其中提供了我正在寻找的所有信息。
从现在开始,我必须在scrapy中实现所有这些知识。 为此,我们定义蜘蛛。
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
在parsing函数中,我有第一个请求的响应。 在RubiGuessItem中,我有包含所有信息的json文件。
很多时候,当抓取时,我们遇到了问题,页面上呈现的内容是用Javascript生成的,因此scrapy无法抓取它(例如,ajax请求,jQuery疯狂)。
但是,如果您将Scrapy与Webtesting框架Selenium一起使用,那么我们可以抓取正常Web浏览器中显示的任何内容。
有些事情要注意:
-
您必须安装Python版本的Selenium RC才能正常工作,并且必须正确设置Selenium。 这也只是一个模板爬虫。 你可能会变得更疯狂,更高级的东西,但我只是想展示基本的想法。 由于代码现在,你会做任何给定的url的两个请求。 一个请求由Scrapy完成,另一个由Selenium完成。 我确信有办法解决这个问题,这样你就可以让Selenium做唯一的请求,但是我没有去执行这个请求,而且通过两个请求你也可以用Scrapy来抓取页面。
-
这是非常强大的,因为现在你有整个渲染的DOM可供你抓取,你仍然可以使用Scrapy中的所有漂亮的抓取function。 这将使爬行速度变慢,但取决于你需要渲染的DOM多less值得等待。
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/
另一个解决scheme是实现下载处理程序或下载处理程序中间件。 以下是使用selenium和无头phantomjs webdriver的中间件的一个例子:
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'))
我想能够告诉不同的蜘蛛使用哪个中间件,所以我实现了这个包装:
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
settings.py:
DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}
所有的蜘蛛必须至less有包装工作:
middleware = set([])
包括一个中间件:
middleware = set([MyProj.middleware.ModuleName.ClassName])
以这种方式执行而不是在蜘蛛中的主要优点是你只能提出一个请求。 在AT的解决scheme中,例如:下载处理程序处理请求,然后将响应转交给蜘蛛。 蜘蛛然后在它的parse_page函数中发出一个全新的请求 – 这是对相同内容的两个请求。
干杯!
我正在使用一个自定义的下载中间件,但不是很高兴,因为我没有设法使caching工作。
更好的方法是实现一个自定义的下载处理程序。
这里有一个工作的例子。 它看起来像这样:
# 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”文件夹的根目录下的handlers.py文件中,那么你可以添加你的settings.py文件:
DOWNLOAD_HANDLERS = { 'http': 'scraper.handlers.PhantomJSDownloadHandler', 'https': 'scraper.handlers.PhantomJSDownloadHandler', }
嗯,JSparsing的DOM,与scrapycaching,重试等。
我使用Selenium和Firefox Web驱动程序来处理ajax请求。 如果您需要爬虫作为守护进程,速度并不是那么快,但是比任何手动解决scheme都要好得多。 我在这里写了一个简短的教程供参考
scrapy如何被用来刮掉这个dynamic数据,以便我可以使用它?
我想知道为什么没有人只使用Scrapy发布解决scheme。
查看Scrapy团队的SCRAPING SCRINING SCROLLING PAGES的博文。 该示例废弃http://spidyquotes.herokuapp.com/scroll使用无限滚动的网站。;
这个想法是使用浏览器的开发者工具,注意到AJAX请求,然后根据这些信息创build对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)