Формирование экспорта .json в Scrapy

Просто быстрый вопрос о форматировании экспорта json в Scrapy. Мой экспортированный файл выглядит так.

{"pages": {"title": "x", "text": "x", "tags": "x", "url": "x"}}
{"pages": {"title": "x", "text": "x", "tags": "x", "url": "x"}}
{"pages": {"title": "x", "text": "x", "tags": "x", "url": "x"}}

Но хотелось бы именно в таком формате. Каким-то образом мне нужно получить всю остальную информацию в разделе «страницы».

{"pages": [
     {"title": "x", "text": "x", "tags": "x", "url": "x"},
     {"title": "x", "text": "x", "tags": "x", "url": "x"},
     {"title": "x", "text": "x", "tags": "x", "url": "x"}
]}

Я не очень разбираюсь в скрэпи или питоне, но все остальное я сделал в своем пауке, кроме формата экспорта. Это мой конвейер.py, который я только что заработал.

from scrapy.exporters import JsonItemExporter
import json

class RautahakuPipeline(object):

    def open_spider(self, spider):
        self.file = open('items.json', 'w')

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.file.write(line)
        return item

Это элементы в моем файле spider.py, которые мне нужно извлечь.

        items = []
        for title, text, tags, url in zip(product_title, product_text, product_tags, product_url):
            item = TechbbsItem()
            item['pages'] = {}
            item['pages']['title'] = title
            item['pages']['text'] = text
            item['pages']['tags'] = tags
            item['pages']['url'] = url
            items.append(item)
        return items

Любая помощь приветствуется, так как это последнее препятствие в моем проекте.

РЕДАКТИРОВАТЬ

items = {'pages':[{'title':title,'text':text,'tags':tags,'url':url} for title, text, tags, url in zip(product_title, product_text, product_tags, product_url)]}

Это извлекает .json в этом формате

{"pages": [{"title": "x", "text": "x", "tags": "x", "url": "x"}]} {"pages": [{"title": "x", "text": "x", "tags": "x", "url": "x"}]} {"pages": [{"title": "x", "text": "x", "tags": "x", "url": "x"}]}

Это становится лучше, но мне по-прежнему нужны только одни «страницы» в начале файла и все остальное внутри массива под ним.

РЕДАКТИРОВАТЬ 2

Я думаю, что мой Spider.py является причиной того, что «страницы» добавляются к каждой строке в файле .json, и я должен был изначально опубликовать весь его код. Вот.

# -*- coding: utf-8 -*-
import scrapy
from urllib.parse import urljoin

class TechbbsItem(scrapy.Item):
    pages = scrapy.Field()
    title = scrapy.Field()
    text= scrapy.Field()
    tags= scrapy.Field()
    url = scrapy.Field()

class TechbbsSpider(scrapy.Spider):
    name = 'techbbs'
    allowed_domains = ['bbs.io-tech.fi']
    start_urls = ['https://bbs.io-tech.fi/forums/prosessorit-emolevyt-ja-muistit.73/?prefix_id=1' #This is a list page full of used pc-part listings
             ]
    def parse(self, response): #This visits product links in the product list page
        links = response.css('a.PreviewTooltip::attr(href)').extract()
        for l in links:
            url = response.urljoin(l)
            yield scrapy.Request(url, callback=self.parse_product)
        next_page_url = response.xpath('//a[contains(.,"Seuraava ")]/@href').extract_first()
        if next_page_url:
           next_page_url =  response.urljoin(next_page_url) 
           yield scrapy.Request(url=next_page_url, callback=self.parse)

    def parse_product(self, response): #This extracts data from inside the links
        product_title = response.xpath('normalize-space(//h1/span/following-sibling::text())').extract()
        product_text = response.xpath('//b[contains(.,"Hinta:")]/following-sibling::text()[1]').re('([0-9]+)')
        tags = "tags" #This is just a placeholder
        product_tags = tags
        product_url = response.xpath('//html/head/link[7]/@href').extract()

        items = []
        for title, text, tags, url in zip(product_title, product_text, product_tags, product_url):
            item = TechbbsItem()
            item['pages'] = {}
            item['pages']['title'] = title
            item['pages']['text'] = text
            item['pages']['tags'] = tags
            item['pages']['url'] = url
            items.append(item)
        return items

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

Я хотел бы получить экспорт .json в точную форму, упомянутую в исходном вопросе. Se, в начале файла будет {"pages": [, затем все строки элементов с отступом {"title": "x", "text": "x", "tags": "x", "url": "x"}, и в конце ]}


person Janne Salmi    schedule 03.04.2018    source источник


Ответы (3)


С точки зрения использования памяти это не очень хорошая практика, но можно сохранить объект и записать его в конце процесса:

class RautahakuPipeline(object):

    def open_spider(self, spider):
        self.items = { "pages":[] }
        self.file = null # open('items.json', 'w')

    def close_spider(self, spider):
        self.file = open('items.json', 'w')
        self.file.write(json.dumps(self.items))
        self.file.close()

    def process_item(self, item, spider):            
        self.items["pages"].append(dict(item))
        return item

Затем, если проблема связана с памятью (в любом случае, к ней следует относиться с вниманием), попробуйте написать файл json следующим образом:

class RautahakuPipeline(object):

    def open_spider(self, spider):
        self.file = open('items.json', 'w')
        header='{"pages": ['
        self.file.write(header)

    def close_spider(self, spider):
        footer=']}'
        self.file.write(footer)
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.file.write(line)
        return item

Я надеюсь, что это помогает.

person Evhz    schedule 05.04.2018
comment
Благодарю вас! Ваше первое предложение сработало отлично! Единственное, к чему я хочу придраться, это то, что каждый элемент находится в одной строке. Куда мне добавить \n или это делается каким-то другим способом? - person Janne Salmi; 05.04.2018
comment
Поскольку json.dumps записывается за один раз, вы не можете написать \n. В этом случае попробуйте второй вариант. - person Evhz; 05.04.2018

Использование понимания списков. Я не знаю, как выглядят ваши данные, но на игрушечном примере:

product_title = range(1,10)
product_text = range(10,20)
product_tags = range(20,30)
product_url = range(30,40)

item = {'pages':[{'title':title,'text':text,'tags':tags,'url':url} 
  for title, text, tags, url in zip(product_title, product_text, product_tags, product_url)]}

Я получаю этот результат:

{'pages': [{'tags': 20, 'text': 10, 'title': 1, 'url': 30},
{'tags': 21, 'text': 11, 'title': 2, 'url': 31},
{'tags': 22, 'text': 12, 'title': 3, 'url': 32},
{'tags': 23, 'text': 13, 'title': 4, 'url': 33},
{'tags': 24, 'text': 14, 'title': 5, 'url': 34},
{'tags': 25, 'text': 15, 'title': 6, 'url': 35},
{'tags': 26, 'text': 16, 'title': 7, 'url': 36},
{'tags': 27, 'text': 17, 'title': 8, 'url': 37},
{'tags': 28, 'text': 18, 'title': 9, 'url': 38}]}
person jjsantoso    schedule 03.04.2018
comment
Я поделился всем кодом своего паука, потому что его структура, вероятно, была причиной того, что ваше решение не сработало для меня. Не могли бы вы взглянуть на мою вторую правку. - person Janne Salmi; 04.04.2018

person    schedule
comment
Запуск паука выдает ошибку 'AttributeError: dict' object has no attribute 'append' - person Janne Salmi; 04.04.2018
comment
@JanneSalmi исправлено - person JacobIRR; 04.04.2018
comment
Я поделился всем кодом своего паука, потому что его структура, вероятно, была причиной того, что ваше решение не сработало для меня. Не могли бы вы взглянуть на мою вторую правку. - person Janne Salmi; 04.04.2018