#python-3.x #scrapy
#python-3.x #scrapy
Вопрос:
class MySiteSpider(scrapy.Spider):
name = 'MySite'
allowed_domains = ['example.com']
start_urls = ['http://example.com/']
def parse(self, response):
links = LinkExtractor(unique=True).extract_links(response)
for link in links:
yield response.follow(link, callback=self.parse)
pass
if response.css('.product-page-content'):
id_ = response.css('#id::text').extract_first()
item = MyItem()
item['id'] = id
item['urls'] = [response.url]
# Sorting some data
yield item
Проблема в том, что иногда я получаю страницу с другим, url
но одинаковым id_
, и в такой ситуации мне нужно добавить этот новый URL к старому элементу с помощью old id_
/ Что-то вроде:
if response.css('.product-page-content'):
id_ = response.css('#id::text').extract_first()
if this_id_already_processed:
old_item['url'].append(response.url)
else
item = MyItem()
item['id'] = id
item['urls'] = [response.url]
# Sorting some data
yield item
Ответ №1:
Интересный случай. Есть несколько способов, которыми вы можете это сделать, но основным препятствием в этом случае будет память.
Когда scrapy выдает элемент, он экспортирует его в любой имеющийся у вас результат (стандартный вывод, json, csv и т.д.), И тогда все готово. Если вы хотите объединить свои элементы таким образом, вам нужно либо выполнить постобработку выходных данных, либо сохранить все в памяти.
-
Вывод после обработки,
если у вас есть файлы вoutput.csv
, вы могли бы просто запустить скрипт над результатами, чтобы объединить ваши элементы. Смотрите сигнал spider_closed — когда spider закрывается, открывайтеouput.json
и корректируйте содержимое -
Конвейеры
При таком подходе вы могли бы хранить все элементы в памяти и обрабатывать их по мере работы сканера:import json class MyPipeline: items = {} def process_item(self, item): if item['id'] in self.items: self.items['id']['urls'].append(item['url']) else: self.items[item['id']] = item return item close_spider(self, spider): with open('output.json') as f: f.write(json.dumps(self.items))
-
Экспортеры каналов
Это было бы почти идентично конвейерному подходу — сохраняйте элементы в памяти перед экспортом
Если ваш spider маленький, используйте # 2, в противном случае # 1 — гораздо более эффективный и чистый подход к использованию памяти.
Ответ №2:
Вы не можете сделать это простым способом из-за того, как работает Scrapy. Он обрабатывает запросы асинхронно, выдавая элементы один за другим, сам по себе он не ведет никакой истории. Что вы могли бы сделать, так это использовать некоторый буфер элементов в spider и использовать сигналы для сброса всех элементов в конце обхода.
Смотрите этот фиктивный пример:
import json
import scrapy
from scrapy import signals
class QuotesSpider(scrapy.Spider):
name = 'quotes'
start_urls = ['http://quotes.toscrape.com/page/1/']
items = []
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = super(QuotesSpider, cls).from_crawler(crawler, *args, **kwargs)
crawler.signals.connect(spider.spider_closed, signal=signals.spider_closed)
return spider
def parse(self, response):
for quote in response.xpath('//div[@class="quote"]'):
item = {
'text': quote.xpath('normalize-space(./span[@class="text"])').extract_first()
}
self.items.append(item)
def spider_closed(self, spider):
with open('items.json', 'wt') as f:
json.dump(self.items, f)