Можно ли использовать scrapy для очистки динамического контента с веб-сайтов, использующих AJAX?
Недавно я изучал Python и запускаю руку в создание web-scraper. В нем нет ничего особенного; его единственная цель - получить данные с веб-сайта для ставок и перенести эти данные в Excel.
Большинство проблем решаемы, и у меня небольшая путаница. Однако я сталкиваюсь с огромным препятствием по поводу одной проблемы. Если сайт загружает таблицу лошадей и перечисляет текущие цены ставок, этой информации нет ни в одном исходном файле. Суть в том, что иногда эти данные являются актуальными, и цифры, очевидно, обновляются с какого-то удаленного сервера. В HTML на моем компьютере просто есть дыра, через которую их серверы пропускают все интересные данные, которые мне нужны.
Сейчас мой опыт работы с динамическим веб-контентом невелик, поэтому мне трудно разобраться в этом вопросе.
Я думаю, что Java или Javascript - это ключ, это часто всплывает.
Scraper - это просто механизм сравнения шансов. На некоторых сайтах есть API, но мне это нужно для тех, у кого их нет. Я использую библиотеку scrapy с Python 2.7
Приношу свои извинения, если этот вопрос слишком открытый. Короче говоря, мой вопрос таков: как можно использовать scrapy для очистки этих динамических данных, чтобы я мог их использовать? Чтобы я мог собирать данные о коэффициентах ставок в режиме реального времени?
Смотрите также: Как я могу очистить страницу с динамическим контентом (созданную JavaScript) в Python? для общего случая.
Переведено автоматически
Ответ 1
Вот простой пример scrapy
с AJAX-запросом. Давайте посмотрим сайт rubin-kazan.ru.
Все сообщения загружаются с помощью AJAX-запроса. Моя цель - получить эти сообщения со всеми их атрибутами (автор, дата, ...):
Когда я анализирую исходный код страницы, я не вижу всех этих сообщений, потому что веб-страница использует технологию AJAX. Но я могу с помощью Firebug из Mozilla Firefox (или аналогичного инструмента в других браузерах) проанализировать HTTP-запрос, который генерирует сообщения на веб-странице:
Он перезагружает не всю страницу, а только те части страницы, которые содержат сообщения. Для этого я нажимаю произвольный номер страницы внизу:
And I observe the HTTP request that is responsible for message body:
After finish, I analyze the headers of the request (I must quote that this URL I'll extract from source page from var section, see the code below):
And the form data content of the request (the HTTP method is "Post"):
And the content of response, which is a JSON file:
В котором представлена вся информация, которую я ищу.
С этого момента я должен реализовать все эти знания в 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 вместе с платформой веб-тестирования Selenium, мы сможем сканировать все, что отображается в обычном веб-браузере.
Некоторые моменты, на которые следует обратить внимание:
Для этого у вас должна быть установлена версия Selenium RC на Python, и вы должны правильно настроить 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 с безголовым 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
внутри 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")
Optional Addon:
I wanted the ability to tell different spiders which middleware to use so I implemented this wrapper:
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
for wrapper to work all spiders must have at minimum:
middleware = set([])
to include a middleware:
middleware = set([MyProj.middleware.ModuleName.ClassName])
Преимущество:
Основное преимущество реализации этого способа, а не в spider, заключается в том, что в конечном итоге вы делаете только один запрос. Например, в решении A T: обработчик загрузки обрабатывает запрос, а затем передает ответ spider. Затем spider создает совершенно новый запрос в своей функции parse_page - это два запроса к одному и тому же контенту.