0

Как использовать несколько запросов и передавать данные между ними в Scrapy на Python?

59

Описание проблемы:

У меня есть объект item, который я хочу передать через несколько страниц, чтобы сохранить данные в одном элементе.

Мой объект item определен следующим образом:

class DmozItem(Item):
    title = Field()
    description1 = Field()
    description2 = Field()
    description3 = Field()

У меня есть три описания, которые находятся на трех отдельных страницах. Я хочу реализовать что-то вроде следующего:

Текущий код работает хорошо для функции parseDescription1:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []
    request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1)
    request.meta['item'] = item
    return request 

def parseDescription1(self, response):
    item = response.meta['item']
    item['desc1'] = "test"
    return item

Однако я хочу выполнить что-то подобное:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []

    request1 = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1)
    request1.meta['item'] = item

    request2 = Request("http://www.example.com/lin2.cpp", callback=self.parseDescription2)
    request2.meta['item'] = item

    request3 = Request("http://www.example.com/lin3.cpp", callback=self.parseDescription3)
    request3.meta['item'] = item

    return request1, request2, request3 

def parseDescription1(self, response):
    item = response.meta['item']
    item['desc1'] = "test"
    return item

def parseDescription2(self, response):
    item = response.meta['item']
    item['desc2'] = "test2"
    return item

def parseDescription3(self, response):
    item = response.meta['item']
    item['desc3'] = "test3"
    return item

Проблема заключается в том, что, когда я пытаюсь отправить несколько запросов, объект item не сохраняет данные должным образом, и в конце концов возвращается неполный/неправильный объект. Как мне правильно организовать код для передачи одного и того же объекта item через несколько запросов, чтобы гарантировать, что все данные будут собраны корректно?

3 ответ(ов)

0

Нет проблем. Вот исправленная версия вашего кода:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []

    item = {}  # Объявляем item до первого запроса

    request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1, meta={'item': item})
    yield request

    request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription2, meta={'item': item})
    yield request

    yield Request("http://www.example.com/lin1.cpp", callback=self.parseDescription3, meta={'item': item})

def parseDescription1(self, response):
    item = response.meta['item']
    item['desc1'] = "test"
    return item

def parseDescription2(self, response):
    item = response.meta['item']
    item['desc2'] = "test2"
    return item

def parseDescription3(self, response):
    item = response.meta['item']
    item['desc3'] = "test3"
    return item

Обратите внимание на то, что я добавил объявление переменной item в page_parser перед первым запросом. В противном случае, когда вы используете item в запросах, он будет неинициализирован, что вызовет ошибку. Теперь код должен работать правильно.

0

Для того, чтобы гарантировать порядок выполнения запросов/колбеков и чтобы в итоге возвращался только один элемент, вам нужно организовать цепочку ваших запросов следующим образом:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []

    request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1)
    request.meta['item'] = Item()
    return [request]

def parseDescription1(self, response):
    item = response.meta['item']
    item['desc1'] = "test"
    return [Request("http://www.example.com/lin2.cpp", callback=self.parseDescription2, meta={'item': item})]

def parseDescription2(self, response):
    item = response.meta['item']
    item['desc2'] = "test2"
    return [Request("http://www.example.com/lin3.cpp", callback=self.parseDescription3, meta={'item': item})]

def parseDescription3(self, response):
    item = response.meta['item']
    item['desc3'] = "test3"
    return [item]

Каждая функция колбек возвращает итерируемый объект, содержащий элементы или запросы. Запросы ставятся в очередь, а элементы обрабатываются в вашем пайплайне для элементов.

Если вы вернете элемент из каждого из колбеков, в вашем пайплайне окажется 4 элемента в различных состояниях завершенности. Однако, если вы вернете следующий запрос, вы сможете гарантировать порядок выполнения запросов и то, что в конце выполнения у вас будет ровно один элемент.

0

Все предложенные ответы имеют свои плюсы и минусы. Я просто добавлю еще один вариант, чтобы продемонстрировать, как это стало проще благодаря изменениям в кодовой базе (как в Python, так и в Scrapy). Теперь нам не нужно использовать meta, и вместо этого мы можем использовать cb_kwargs (т.е. именованные параметры, которые передаются в функцию обратного вызова).

Таким образом, вместо того, чтобы делать это:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []

    request = Request("http://www.example.com/lin1.cpp",
                      callback=self.parseDescription1)
    request.meta['item'] = Item()
    return [request]

def parseDescription1(self,response):
    item = response.meta['item']
    item['desc1'] = "test"
    return [Request("http://www.example.com/lin2.cpp",
                    callback=self.parseDescription2, meta={'item': item})]
...

Теперь мы можем сделать так:

def page_parser(self, response):
    sites = hxs.select('//div[@class="row"]')
    items = []

    yield response.follow("http://www.example.com/lin1.cpp",
                          callback=self.parseDescription1,
                          cb_kwargs={"item": Item()})

def parseDescription1(self,response, item):
    item['desc1'] = "Больше данных из этого нового ответа"
    yield response.follow("http://www.example.com/lin2.cpp",
                          callback=self.parseDescription2,
                          cb_kwargs={'item': item})
...

И если по какой-либо причине вам нужно обработать несколько ссылок с одной и той же функцией, мы можем заменить

yield response.follow(a_single_url,
                      callback=some_function,
                      cb_kwargs={"data": to_pass_to_callback})

на

yield from response.follow_all([many, urls, to, parse],
                               callback=some_function,
                               cb_kwargs={"data": to_pass_to_callback})
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь