Заказать JSON по полю, используя скрап

Я создал паука, чтобы очистить проблемы от projecteuler.net. Здесь я завершил свой ответ на связанный вопрос с

Я запускаю это с помощью команды scrapy crawl euler -o euler.json, и он выводит массив неупорядоченных объектов json, каждый из которых соответствует одной проблеме: это хорошо для меня, потому что я собираюсь обработать его с помощью javascript, даже если я думаю, что решение проблемы заказа с помощью скрапа может быть очень простым.

Но, к сожалению, упорядочить элементы для записи в json с помощью scrapy (мне нужен возрастающий порядок по полю id) не так просто. Я изучил каждый компонент (связующее ПО, конвейеры, экспортеры, сигналы и т. Д.), Но никто не кажется полезным для этой цели. Я пришел к выводу, что решения для решения этой проблемы вообще не существует в скрапе (за исключением, может быть, очень сложного трюка), и вы вынуждены заказывать вещи на втором этапе. Вы согласны или у вас есть идея? Я копирую здесь код моего скребка.

Паук:

# -*- coding: utf-8 -*-
import scrapy
from eulerscraper.items import Problem
from scrapy.loader import ItemLoader


class EulerSpider(scrapy.Spider):
    name = 'euler'
    allowed_domains = ['projecteuler.net']
    start_urls = ["https://projecteuler.net/archives"]

    def parse(self, response):
        numpag = response.css("div.pagination a[href]::text").extract()
        maxpag = int(numpag[len(numpag) - 1])

        for href in response.css("table#problems_table a::attr(href)").extract():
            next_page = "https://projecteuler.net/" + href
            yield response.follow(next_page, self.parse_problems)

        for i in range(2, maxpag + 1):
            next_page = "https://projecteuler.net/archives;page=" + str(i)
            yield response.follow(next_page, self.parse_next)

        return [scrapy.Request("https://projecteuler.net/archives", self.parse)]

    def parse_next(self, response):
        for href in response.css("table#problems_table a::attr(href)").extract():
            next_page = "https://projecteuler.net/" + href
            yield response.follow(next_page, self.parse_problems)

    def parse_problems(self, response):
        l = ItemLoader(item=Problem(), response=response)
        l.add_css("title", "h2")
        l.add_css("id", "#problem_info")
        l.add_css("content", ".problem_content")

        yield l.load_item()

Вещь:

import re

import scrapy
from scrapy.loader.processors import MapCompose, Compose
from w3lib.html import remove_tags


def extract_first_number(text):
    i = re.search('\d+', text)
    return int(text[i.start():i.end()])


def array_to_value(element):
    return element[0]


class Problem(scrapy.Item):
    id = scrapy.Field(
        input_processor=MapCompose(remove_tags, extract_first_number),
        output_processor=Compose(array_to_value)
    )
    title = scrapy.Field(input_processor=MapCompose(remove_tags))
    content = scrapy.Field()

2 ответа

Решение

Если бы мне нужно, чтобы мой выходной файл был отсортирован (я предполагаю, что у вас есть веская причина для этого), я бы, вероятно, написал собственный экспортер.

Вот как встроен Scrapy JsonItemExporter реализовано.
С помощью нескольких простых изменений вы можете изменить его, чтобы добавить элементы в список в export_item(), а затем отсортировать элементы и записать файл в finish_exporting(),

Поскольку вы собираете только несколько сотен элементов, недостатки хранения их списка и не записи в файл до завершения сканирования не должны представлять для вас проблемы.

К настоящему времени я нашел рабочее решение с использованием конвейера:

import json

class JsonWriterPipeline(object):

    def open_spider(self, spider):
        self.list_items = []
        self.file = open('euler.json', 'w')

    def close_spider(self, spider):
        ordered_list = [None for i in range(len(self.list_items))]

        self.file.write("[\n")

        for i in self.list_items:
            ordered_list[int(i['id']-1)] = json.dumps(dict(i))

        for i in ordered_list:
            self.file.write(str(i)+",\n")

        self.file.write("]\n")
        self.file.close()

    def process_item(self, item, spider):
        self.list_items.append(item)
        return item

Хотя это может быть неоптимальным, потому что руководство предлагает в другом примере:

Цель JsonWriterPipeline - просто показать, как писать конвейеры элементов. Если вы действительно хотите сохранить все записанные элементы в файл JSON, вам следует использовать экспорт каналов.

Другие вопросы по тегам